diff --git a/ble_spam/application.fam b/ble_spam/application.fam index ae09352db0e..ca76f3a9ae8 100644 --- a/ble_spam/application.fam +++ b/ble_spam/application.fam @@ -9,7 +9,7 @@ App( fap_file_assets="assets", fap_author="@Willy-JL @ECTO-1A @Spooks4576", fap_weburl="https://github.com/Next-Flip/Momentum-Apps/tree/dev/ble_spam", - fap_version="6.2", + fap_version="6.3", fap_description="Flood BLE advertisements to cause spammy and annoying popups/notifications", fap_icon_assets="icons", fap_icon_assets_symbol="ble_spam", diff --git a/ble_spam/ble_spam.c b/ble_spam/ble_spam.c index a53d335bd27..6b1df6b0918 100644 --- a/ble_spam/ble_spam.c +++ b/ble_spam/ble_spam.c @@ -149,7 +149,7 @@ static Attack attacks[] = { #define ATTACKS_COUNT ((signed)COUNT_OF(attacks)) -static uint16_t delays[] = {20, 50, 100, 200, 500}; +static uint16_t delays[] = {30, 50, 100, 200, 500}; typedef struct { Ctx ctx; @@ -628,6 +628,11 @@ static void lock_timer_callback(void* _ctx) { furi_timer_set_thread_priority(FuriTimerThreadPriorityNormal); } +static bool custom_event_callback(void* _ctx, uint32_t event) { + State* state = _ctx; + return scene_manager_handle_custom_event(state->ctx.scene_manager, event); +} + static void tick_event_callback(void* _ctx) { State* state = _ctx; bool advertising; @@ -668,6 +673,7 @@ int32_t ble_spam(void* p) { state->ctx.view_dispatcher = view_dispatcher_alloc(); view_dispatcher_enable_queue(state->ctx.view_dispatcher); view_dispatcher_set_event_callback_context(state->ctx.view_dispatcher, state); + view_dispatcher_set_custom_event_callback(state->ctx.view_dispatcher, custom_event_callback); view_dispatcher_set_tick_event_callback(state->ctx.view_dispatcher, tick_event_callback, 100); view_dispatcher_set_navigation_event_callback(state->ctx.view_dispatcher, back_event_callback); state->ctx.scene_manager = scene_manager_alloc(&scene_handlers, &state->ctx); diff --git a/ble_spam/protocols/continuity.c b/ble_spam/protocols/continuity.c index 169b1bc28fe..79f89177b81 100644 --- a/ble_spam/protocols/continuity.c +++ b/ble_spam/protocols/continuity.c @@ -821,7 +821,7 @@ static void pp_model_callback(void* _ctx, uint32_t index) { switch(index) { case 0: payload->mode = PayloadModeRandom; - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); break; case pp_models_count + 1: scene_manager_next_scene(ctx->scene_manager, SceneContinuityPpModelCustom); @@ -832,14 +832,14 @@ static void pp_model_callback(void* _ctx, uint32_t index) { payload->bruteforce.value = cfg->data.proximity_pair.model; payload->bruteforce.size = 2; cfg->data.proximity_pair.bruteforce_mode = ContinuityPpBruteforceModel; - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); break; default: if(payload->mode != PayloadModeBruteforce || cfg->data.proximity_pair.bruteforce_mode == ContinuityPpBruteforceModel) payload->mode = PayloadModeValue; cfg->data.proximity_pair.model = pp_models[index - 1].value; - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); break; } } @@ -881,8 +881,11 @@ void scene_continuity_pp_model_on_enter(void* _ctx) { view_dispatcher_switch_to_view(ctx->view_dispatcher, ViewSubmenu); } bool scene_continuity_pp_model_on_event(void* _ctx, SceneManagerEvent event) { - UNUSED(_ctx); - UNUSED(event); + Ctx* ctx = _ctx; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_previous_scene(ctx->scene_manager); + return true; + } return false; } void scene_continuity_pp_model_on_exit(void* _ctx) { @@ -898,8 +901,7 @@ static void pp_model_custom_callback(void* _ctx) { cfg->data.proximity_pair.bruteforce_mode == ContinuityPpBruteforceModel) payload->mode = PayloadModeValue; cfg->data.proximity_pair.model = (ctx->byte_store[0] << 0x08) + (ctx->byte_store[1] << 0x00); - scene_manager_previous_scene(ctx->scene_manager); - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); } void scene_continuity_pp_model_custom_on_enter(void* _ctx) { Ctx* ctx = _ctx; @@ -918,8 +920,12 @@ void scene_continuity_pp_model_custom_on_enter(void* _ctx) { view_dispatcher_switch_to_view(ctx->view_dispatcher, ViewByteInput); } bool scene_continuity_pp_model_custom_on_event(void* _ctx, SceneManagerEvent event) { - UNUSED(_ctx); - UNUSED(event); + Ctx* ctx = _ctx; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_previous_scene(ctx->scene_manager); + scene_manager_previous_scene(ctx->scene_manager); + return true; + } return false; } void scene_continuity_pp_model_custom_on_exit(void* _ctx) { @@ -950,13 +956,13 @@ static void pp_color_callback(void* _ctx, uint32_t index) { payload->bruteforce.value = cfg->data.proximity_pair.color; payload->bruteforce.size = 1; cfg->data.proximity_pair.bruteforce_mode = ContinuityPpBruteforceColor; - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); } else { if(payload->mode != PayloadModeBruteforce || cfg->data.proximity_pair.bruteforce_mode == ContinuityPpBruteforceColor) payload->mode = PayloadModeValue; cfg->data.proximity_pair.color = pp_models[model_index].colors[index].value; - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); } } void scene_continuity_pp_color_on_enter(void* _ctx) { @@ -1005,8 +1011,11 @@ void scene_continuity_pp_color_on_enter(void* _ctx) { view_dispatcher_switch_to_view(ctx->view_dispatcher, ViewSubmenu); } bool scene_continuity_pp_color_on_event(void* _ctx, SceneManagerEvent event) { - UNUSED(_ctx); - UNUSED(event); + Ctx* ctx = _ctx; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_previous_scene(ctx->scene_manager); + return true; + } return false; } void scene_continuity_pp_color_on_exit(void* _ctx) { @@ -1022,8 +1031,7 @@ static void pp_color_custom_callback(void* _ctx) { cfg->data.proximity_pair.bruteforce_mode == ContinuityPpBruteforceColor) payload->mode = PayloadModeValue; cfg->data.proximity_pair.color = (ctx->byte_store[0] << 0x00); - scene_manager_previous_scene(ctx->scene_manager); - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); } void scene_continuity_pp_color_custom_on_enter(void* _ctx) { Ctx* ctx = _ctx; @@ -1041,8 +1049,12 @@ void scene_continuity_pp_color_custom_on_enter(void* _ctx) { view_dispatcher_switch_to_view(ctx->view_dispatcher, ViewByteInput); } bool scene_continuity_pp_color_custom_on_event(void* _ctx, SceneManagerEvent event) { - UNUSED(_ctx); - UNUSED(event); + Ctx* ctx = _ctx; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_previous_scene(ctx->scene_manager); + scene_manager_previous_scene(ctx->scene_manager); + return true; + } return false; } void scene_continuity_pp_color_custom_on_exit(void* _ctx) { @@ -1056,14 +1068,14 @@ static void pp_prefix_callback(void* _ctx, uint32_t index) { switch(index) { case 0: cfg->data.proximity_pair.prefix = 0x00; - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); break; case pp_prefixes_count + 1: scene_manager_next_scene(ctx->scene_manager, SceneContinuityPpPrefixCustom); break; default: cfg->data.proximity_pair.prefix = pp_prefixes[index - 1].value; - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); break; } } @@ -1098,8 +1110,11 @@ void scene_continuity_pp_prefix_on_enter(void* _ctx) { view_dispatcher_switch_to_view(ctx->view_dispatcher, ViewSubmenu); } bool scene_continuity_pp_prefix_on_event(void* _ctx, SceneManagerEvent event) { - UNUSED(_ctx); - UNUSED(event); + Ctx* ctx = _ctx; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_previous_scene(ctx->scene_manager); + return true; + } return false; } void scene_continuity_pp_prefix_on_exit(void* _ctx) { @@ -1112,8 +1127,7 @@ static void pp_prefix_custom_callback(void* _ctx) { Payload* payload = &ctx->attack->payload; ContinuityCfg* cfg = &payload->cfg.continuity; cfg->data.proximity_pair.prefix = (ctx->byte_store[0] << 0x00); - scene_manager_previous_scene(ctx->scene_manager); - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); } void scene_continuity_pp_prefix_custom_on_enter(void* _ctx) { Ctx* ctx = _ctx; @@ -1131,8 +1145,12 @@ void scene_continuity_pp_prefix_custom_on_enter(void* _ctx) { view_dispatcher_switch_to_view(ctx->view_dispatcher, ViewByteInput); } bool scene_continuity_pp_prefix_custom_on_event(void* _ctx, SceneManagerEvent event) { - UNUSED(_ctx); - UNUSED(event); + Ctx* ctx = _ctx; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_previous_scene(ctx->scene_manager); + scene_manager_previous_scene(ctx->scene_manager); + return true; + } return false; } void scene_continuity_pp_prefix_custom_on_exit(void* _ctx) { @@ -1146,7 +1164,7 @@ static void na_action_callback(void* _ctx, uint32_t index) { switch(index) { case 0: payload->mode = PayloadModeRandom; - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); break; case na_actions_count + 1: scene_manager_next_scene(ctx->scene_manager, SceneContinuityNaActionCustom); @@ -1156,12 +1174,12 @@ static void na_action_callback(void* _ctx, uint32_t index) { payload->bruteforce.counter = 0; payload->bruteforce.value = cfg->data.nearby_action.action; payload->bruteforce.size = 1; - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); break; default: payload->mode = PayloadModeValue; cfg->data.nearby_action.action = na_actions[index - 1].value; - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); break; } } @@ -1201,8 +1219,11 @@ void scene_continuity_na_action_on_enter(void* _ctx) { view_dispatcher_switch_to_view(ctx->view_dispatcher, ViewSubmenu); } bool scene_continuity_na_action_on_event(void* _ctx, SceneManagerEvent event) { - UNUSED(_ctx); - UNUSED(event); + Ctx* ctx = _ctx; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_previous_scene(ctx->scene_manager); + return true; + } return false; } void scene_continuity_na_action_on_exit(void* _ctx) { @@ -1216,8 +1237,7 @@ static void na_action_custom_callback(void* _ctx) { ContinuityCfg* cfg = &payload->cfg.continuity; payload->mode = PayloadModeValue; cfg->data.nearby_action.action = (ctx->byte_store[0] << 0x00); - scene_manager_previous_scene(ctx->scene_manager); - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); } void scene_continuity_na_action_custom_on_enter(void* _ctx) { Ctx* ctx = _ctx; @@ -1235,8 +1255,12 @@ void scene_continuity_na_action_custom_on_enter(void* _ctx) { view_dispatcher_switch_to_view(ctx->view_dispatcher, ViewByteInput); } bool scene_continuity_na_action_custom_on_event(void* _ctx, SceneManagerEvent event) { - UNUSED(_ctx); - UNUSED(event); + Ctx* ctx = _ctx; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_previous_scene(ctx->scene_manager); + scene_manager_previous_scene(ctx->scene_manager); + return true; + } return false; } void scene_continuity_na_action_custom_on_exit(void* _ctx) { @@ -1245,7 +1269,7 @@ void scene_continuity_na_action_custom_on_exit(void* _ctx) { static void na_flags_callback(void* _ctx) { Ctx* ctx = _ctx; - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); } void scene_continuity_na_flags_on_enter(void* _ctx) { Ctx* ctx = _ctx; @@ -1264,6 +1288,10 @@ void scene_continuity_na_flags_on_enter(void* _ctx) { } bool scene_continuity_na_flags_on_event(void* _ctx, SceneManagerEvent event) { Ctx* ctx = _ctx; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_previous_scene(ctx->scene_manager); + return true; + } if(event.type == SceneManagerEventTypeBack) { ctx->byte_store[0] = 0x00; } diff --git a/ble_spam/protocols/easysetup.c b/ble_spam/protocols/easysetup.c index 94eb5019085..34195c53156 100644 --- a/ble_spam/protocols/easysetup.c +++ b/ble_spam/protocols/easysetup.c @@ -373,7 +373,7 @@ static void buds_model_callback(void* _ctx, uint32_t index) { switch(index) { case 0: payload->mode = PayloadModeRandom; - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); break; case buds_models_count + 1: scene_manager_next_scene(ctx->scene_manager, SceneEasysetupBudsModelCustom); @@ -383,12 +383,12 @@ static void buds_model_callback(void* _ctx, uint32_t index) { payload->bruteforce.counter = 0; payload->bruteforce.value = cfg->data.buds.model; payload->bruteforce.size = 3; - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); break; default: payload->mode = PayloadModeValue; cfg->data.buds.model = buds_models[index - 1].value; - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); break; } } @@ -428,8 +428,11 @@ void scene_easysetup_buds_model_on_enter(void* _ctx) { view_dispatcher_switch_to_view(ctx->view_dispatcher, ViewSubmenu); } bool scene_easysetup_buds_model_on_event(void* _ctx, SceneManagerEvent event) { - UNUSED(_ctx); - UNUSED(event); + Ctx* ctx = _ctx; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_previous_scene(ctx->scene_manager); + return true; + } return false; } void scene_easysetup_buds_model_on_exit(void* _ctx) { @@ -444,8 +447,7 @@ static void buds_model_custom_callback(void* _ctx) { payload->mode = PayloadModeValue; cfg->data.buds.model = (ctx->byte_store[0] << 0x10) + (ctx->byte_store[1] << 0x08) + (ctx->byte_store[2] << 0x00); - scene_manager_previous_scene(ctx->scene_manager); - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); } void scene_easysetup_buds_model_custom_on_enter(void* _ctx) { Ctx* ctx = _ctx; @@ -465,8 +467,12 @@ void scene_easysetup_buds_model_custom_on_enter(void* _ctx) { view_dispatcher_switch_to_view(ctx->view_dispatcher, ViewByteInput); } bool scene_easysetup_buds_model_custom_on_event(void* _ctx, SceneManagerEvent event) { - UNUSED(_ctx); - UNUSED(event); + Ctx* ctx = _ctx; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_previous_scene(ctx->scene_manager); + scene_manager_previous_scene(ctx->scene_manager); + return true; + } return false; } void scene_easysetup_buds_model_custom_on_exit(void* _ctx) { @@ -480,7 +486,7 @@ static void watch_model_callback(void* _ctx, uint32_t index) { switch(index) { case 0: payload->mode = PayloadModeRandom; - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); break; case watch_models_count + 1: scene_manager_next_scene(ctx->scene_manager, SceneEasysetupWatchModelCustom); @@ -490,12 +496,12 @@ static void watch_model_callback(void* _ctx, uint32_t index) { payload->bruteforce.counter = 0; payload->bruteforce.value = cfg->data.watch.model; payload->bruteforce.size = 1; - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); break; default: payload->mode = PayloadModeValue; cfg->data.watch.model = watch_models[index - 1].value; - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); break; } } @@ -535,8 +541,11 @@ void scene_easysetup_watch_model_on_enter(void* _ctx) { view_dispatcher_switch_to_view(ctx->view_dispatcher, ViewSubmenu); } bool scene_easysetup_watch_model_on_event(void* _ctx, SceneManagerEvent event) { - UNUSED(_ctx); - UNUSED(event); + Ctx* ctx = _ctx; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_previous_scene(ctx->scene_manager); + return true; + } return false; } void scene_easysetup_watch_model_on_exit(void* _ctx) { @@ -550,8 +559,7 @@ static void watch_model_custom_callback(void* _ctx) { EasysetupCfg* cfg = &payload->cfg.easysetup; payload->mode = PayloadModeValue; cfg->data.watch.model = (ctx->byte_store[0] << 0x00); - scene_manager_previous_scene(ctx->scene_manager); - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); } void scene_easysetup_watch_model_custom_on_enter(void* _ctx) { Ctx* ctx = _ctx; @@ -569,8 +577,12 @@ void scene_easysetup_watch_model_custom_on_enter(void* _ctx) { view_dispatcher_switch_to_view(ctx->view_dispatcher, ViewByteInput); } bool scene_easysetup_watch_model_custom_on_event(void* _ctx, SceneManagerEvent event) { - UNUSED(_ctx); - UNUSED(event); + Ctx* ctx = _ctx; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_previous_scene(ctx->scene_manager); + scene_manager_previous_scene(ctx->scene_manager); + return true; + } return false; } void scene_easysetup_watch_model_custom_on_exit(void* _ctx) { diff --git a/ble_spam/protocols/fastpair.c b/ble_spam/protocols/fastpair.c index 28a37487622..2244705ac9b 100644 --- a/ble_spam/protocols/fastpair.c +++ b/ble_spam/protocols/fastpair.c @@ -726,7 +726,7 @@ static void model_callback(void* _ctx, uint32_t index) { switch(index) { case 0: payload->mode = PayloadModeRandom; - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); break; case models_count + 1: scene_manager_next_scene(ctx->scene_manager, SceneFastpairModelCustom); @@ -736,12 +736,12 @@ static void model_callback(void* _ctx, uint32_t index) { payload->bruteforce.counter = 0; payload->bruteforce.value = cfg->model; payload->bruteforce.size = 3; - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); break; default: payload->mode = PayloadModeValue; cfg->model = models[index - 1].value; - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); break; } } @@ -780,8 +780,11 @@ void scene_fastpair_model_on_enter(void* _ctx) { view_dispatcher_switch_to_view(ctx->view_dispatcher, ViewSubmenu); } bool scene_fastpair_model_on_event(void* _ctx, SceneManagerEvent event) { - UNUSED(_ctx); - UNUSED(event); + Ctx* ctx = _ctx; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_previous_scene(ctx->scene_manager); + return true; + } return false; } void scene_fastpair_model_on_exit(void* _ctx) { @@ -796,8 +799,7 @@ static void model_custom_callback(void* _ctx) { payload->mode = PayloadModeValue; cfg->model = (ctx->byte_store[0] << 0x10) + (ctx->byte_store[1] << 0x08) + (ctx->byte_store[2] << 0x00); - scene_manager_previous_scene(ctx->scene_manager); - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); } void scene_fastpair_model_custom_on_enter(void* _ctx) { Ctx* ctx = _ctx; @@ -817,8 +819,12 @@ void scene_fastpair_model_custom_on_enter(void* _ctx) { view_dispatcher_switch_to_view(ctx->view_dispatcher, ViewByteInput); } bool scene_fastpair_model_custom_on_event(void* _ctx, SceneManagerEvent event) { - UNUSED(_ctx); - UNUSED(event); + Ctx* ctx = _ctx; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_previous_scene(ctx->scene_manager); + scene_manager_previous_scene(ctx->scene_manager); + return true; + } return false; } void scene_fastpair_model_custom_on_exit(void* _ctx) { diff --git a/ble_spam/protocols/lovespouse.c b/ble_spam/protocols/lovespouse.c index f7206534ed3..265741a53a4 100644 --- a/ble_spam/protocols/lovespouse.c +++ b/ble_spam/protocols/lovespouse.c @@ -195,7 +195,7 @@ static void mode_callback(void* _ctx, uint32_t index) { LovespouseCfg* cfg = &payload->cfg.lovespouse; if(index == 0) { payload->mode = PayloadModeRandom; - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); } else if(index == modes[cfg->state].count + 1U) { scene_manager_next_scene(ctx->scene_manager, SceneLovespouseModeCustom); } else if(modes[cfg->state].count + 2U) { @@ -203,11 +203,11 @@ static void mode_callback(void* _ctx, uint32_t index) { payload->bruteforce.counter = 0; payload->bruteforce.value = cfg->mode; payload->bruteforce.size = 3; - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); } else { payload->mode = PayloadModeValue; cfg->mode = modes[cfg->state].modes[index - 1].value; - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); } } void scene_lovespouse_mode_on_enter(void* _ctx) { @@ -246,8 +246,11 @@ void scene_lovespouse_mode_on_enter(void* _ctx) { view_dispatcher_switch_to_view(ctx->view_dispatcher, ViewSubmenu); } bool scene_lovespouse_mode_on_event(void* _ctx, SceneManagerEvent event) { - UNUSED(_ctx); - UNUSED(event); + Ctx* ctx = _ctx; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_previous_scene(ctx->scene_manager); + return true; + } return false; } void scene_lovespouse_mode_on_exit(void* _ctx) { @@ -262,8 +265,7 @@ static void mode_custom_callback(void* _ctx) { payload->mode = PayloadModeValue; cfg->mode = (ctx->byte_store[0] << 0x10) + (ctx->byte_store[1] << 0x08) + (ctx->byte_store[2] << 0x00); - scene_manager_previous_scene(ctx->scene_manager); - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); } void scene_lovespouse_mode_custom_on_enter(void* _ctx) { Ctx* ctx = _ctx; @@ -283,8 +285,12 @@ void scene_lovespouse_mode_custom_on_enter(void* _ctx) { view_dispatcher_switch_to_view(ctx->view_dispatcher, ViewByteInput); } bool scene_lovespouse_mode_custom_on_event(void* _ctx, SceneManagerEvent event) { - UNUSED(_ctx); - UNUSED(event); + Ctx* ctx = _ctx; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_previous_scene(ctx->scene_manager); + scene_manager_previous_scene(ctx->scene_manager); + return true; + } return false; } void scene_lovespouse_mode_custom_on_exit(void* _ctx) { diff --git a/ble_spam/protocols/nameflood.c b/ble_spam/protocols/nameflood.c index 129406a71be..a387ae7f51a 100644 --- a/ble_spam/protocols/nameflood.c +++ b/ble_spam/protocols/nameflood.c @@ -142,7 +142,7 @@ static void name_callback(void* _ctx) { Ctx* ctx = _ctx; Payload* payload = &ctx->attack->payload; payload->mode = PayloadModeValue; - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); } void scene_nameflood_name_on_enter(void* _ctx) { Ctx* ctx = _ctx; @@ -162,6 +162,10 @@ void scene_nameflood_name_on_enter(void* _ctx) { bool scene_nameflood_name_on_event(void* _ctx, SceneManagerEvent event) { Ctx* ctx = _ctx; Payload* payload = &ctx->attack->payload; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_previous_scene(ctx->scene_manager); + return true; + } if(event.type == SceneManagerEventTypeBack) { payload->mode = PayloadModeRandom; } diff --git a/ble_spam/protocols/swiftpair.c b/ble_spam/protocols/swiftpair.c index 9632f76458b..fbd563bf706 100644 --- a/ble_spam/protocols/swiftpair.c +++ b/ble_spam/protocols/swiftpair.c @@ -135,7 +135,7 @@ static void name_callback(void* _ctx) { Ctx* ctx = _ctx; Payload* payload = &ctx->attack->payload; payload->mode = PayloadModeValue; - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); } void scene_swiftpair_name_on_enter(void* _ctx) { Ctx* ctx = _ctx; @@ -155,6 +155,10 @@ void scene_swiftpair_name_on_enter(void* _ctx) { bool scene_swiftpair_name_on_event(void* _ctx, SceneManagerEvent event) { Ctx* ctx = _ctx; Payload* payload = &ctx->attack->payload; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_previous_scene(ctx->scene_manager); + return true; + } if(event.type == SceneManagerEventTypeBack) { payload->mode = PayloadModeRandom; } diff --git a/ble_spam/scenes/config.c b/ble_spam/scenes/config.c index 7a4f9e46ac4..0be62f1ddf2 100644 --- a/ble_spam/scenes/config.c +++ b/ble_spam/scenes/config.c @@ -30,7 +30,7 @@ static void config_callback(void* _ctx, uint32_t index) { break; case ConfigLockKeyboard: ctx->lock_keyboard = true; - scene_manager_previous_scene(ctx->scene_manager); + view_dispatcher_send_custom_event(ctx->view_dispatcher, 0); notification_message_block(ctx->notification, &sequence_display_backlight_off); break; default: @@ -64,8 +64,11 @@ void scene_config_on_enter(void* _ctx) { } bool scene_config_on_event(void* _ctx, SceneManagerEvent event) { - UNUSED(_ctx); - UNUSED(event); + Ctx* ctx = _ctx; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_previous_scene(ctx->scene_manager); + return true; + } return false; } diff --git a/bt_trigger/application.fam b/bt_trigger/application.fam index e54d5801934..d36b586108a 100644 --- a/bt_trigger/application.fam +++ b/bt_trigger/application.fam @@ -12,6 +12,6 @@ App( fap_libs=["ble_profile"], fap_author="@Nem0oo", fap_weburl="https://github.com/Nem0oo/flipper-zero-bluetooth-trigger", - fap_version="1.2", + fap_version="1.3", fap_description="Control your smartphone camera via your Flipper Zero", ) diff --git a/geiger/application.fam b/geiger/application.fam index e065a9f4537..4cdad82349c 100644 --- a/geiger/application.fam +++ b/geiger/application.fam @@ -12,6 +12,6 @@ App( fap_category="GPIO", fap_author="@nmrr", fap_weburl="https://github.com/nmrr/flipperzero-geigercounter", - fap_version="1.2", + fap_version="1.3", fap_description="Works with J305 Geiger tube on external board", ) diff --git a/geiger/geiger.png b/geiger/geiger.png index d41e1915b68..8d881c4bf5c 100644 Binary files a/geiger/geiger.png and b/geiger/geiger.png differ diff --git a/nfc_magic/.catalog/changelog.md b/nfc_magic/.catalog/changelog.md index 616a170c8f0..640b67cb010 100644 --- a/nfc_magic/.catalog/changelog.md +++ b/nfc_magic/.catalog/changelog.md @@ -1,3 +1,9 @@ +## 1.8 + - Ultralight Various fixes and improvements + +## 1.7 + - Gen2/CUID write support + ## 1.6 - Rework with new bit lib API diff --git a/nfc_magic/.catalog/screenshots/0.png b/nfc_magic/.catalog/screenshots/0.png new file mode 100644 index 00000000000..f1849165108 Binary files /dev/null and b/nfc_magic/.catalog/screenshots/0.png differ diff --git a/nfc_magic/.catalog/screenshots/1.png b/nfc_magic/.catalog/screenshots/1.png index 6b16e1b1298..238489ed23b 100644 Binary files a/nfc_magic/.catalog/screenshots/1.png and b/nfc_magic/.catalog/screenshots/1.png differ diff --git a/nfc_magic/.catalog/screenshots/2.png b/nfc_magic/.catalog/screenshots/2.png index 9bb578f3e10..16976fe0db9 100644 Binary files a/nfc_magic/.catalog/screenshots/2.png and b/nfc_magic/.catalog/screenshots/2.png differ diff --git a/nfc_magic/application.fam b/nfc_magic/application.fam index 23b929d4c6e..df793b44db3 100644 --- a/nfc_magic/application.fam +++ b/nfc_magic/application.fam @@ -10,13 +10,8 @@ App( ], stack_size=4 * 1024, fap_description="Application for writing to NFC tags with modifiable sector 0", - fap_version="1.6", + fap_version="1.8", fap_icon="assets/125_10px.png", fap_category="NFC", - fap_private_libs=[ - Lib( - name="magic", - ), - ], fap_icon_assets="assets", ) diff --git a/nfc_magic/lib/magic/nfc_magic_scanner.c b/nfc_magic/magic/nfc_magic_scanner.c similarity index 73% rename from nfc_magic/lib/magic/nfc_magic_scanner.c rename to nfc_magic/magic/nfc_magic_scanner.c index fbe1f1958d0..9c00f8edf2c 100644 --- a/nfc_magic/lib/magic/nfc_magic_scanner.c +++ b/nfc_magic/magic/nfc_magic_scanner.c @@ -1,8 +1,9 @@ #include "nfc_magic_scanner.h" #include "core/check.h" -#include "protocols/gen1a/gen1a_poller.h" #include "protocols/gen4/gen4.h" +#include "protocols/gen1a/gen1a_poller.h" +#include "protocols/gen2/gen2_poller.h" #include "protocols/gen4/gen4_poller.h" #include @@ -70,24 +71,37 @@ static int32_t nfc_magic_scanner_worker(void* context) { furi_assert(instance->session_state == NfcMagicScannerSessionStateActive); while(instance->session_state == NfcMagicScannerSessionStateActive) { - if(instance->current_protocol == NfcMagicProtocolGen1) { - instance->magic_protocol_detected = gen1a_poller_detect(instance->nfc); - } else if(instance->current_protocol == NfcMagicProtocolGen4) { - gen4_reset(instance->gen4_data); - Gen4 gen4_data; - Gen4PollerError error = - gen4_poller_detect(instance->nfc, instance->gen4_password, &gen4_data); - if(error == Gen4PollerErrorProtocol) { - NfcMagicScannerEvent event = { - .type = NfcMagicScannerEventTypeDetectedNotMagic, - }; - instance->callback(event, instance->context); - break; - } else { + do { + if(instance->current_protocol == NfcMagicProtocolGen1) { + instance->magic_protocol_detected = gen1a_poller_detect(instance->nfc); + if(instance->magic_protocol_detected) { + break; + } + } else if(instance->current_protocol == NfcMagicProtocolGen4) { + gen4_reset(instance->gen4_data); + Gen4 gen4_data; + Gen4PollerError error = + gen4_poller_detect(instance->nfc, instance->gen4_password, &gen4_data); instance->magic_protocol_detected = (error == Gen4PollerErrorNone); - gen4_copy(instance->gen4_data, &gen4_data); + if(instance->magic_protocol_detected) { + gen4_copy(instance->gen4_data, &gen4_data); + break; + } + } else if(instance->current_protocol == NfcMagicProtocolGen2) { + Gen2PollerError error = gen2_poller_detect(instance->nfc); + instance->magic_protocol_detected = (error == Gen2PollerErrorNone); + if(instance->magic_protocol_detected) { + break; + } + } else if(instance->current_protocol == NfcMagicProtocolClassic) { + NfcPoller* poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); + instance->magic_protocol_detected = nfc_poller_detect(poller); + nfc_poller_free(poller); + if(instance->magic_protocol_detected) { + break; + } } - } + } while(false); if(instance->magic_protocol_detected) { NfcMagicScannerEvent event = { @@ -158,7 +172,7 @@ void nfc_magic_scanner_stop(NfcMagicScanner* instance) { instance->context = NULL; } -Gen4* nfc_magic_scanner_get_gen4_data(NfcMagicScanner* instance) { +const Gen4* nfc_magic_scanner_get_gen4_data(NfcMagicScanner* instance) { furi_assert(instance); return instance->gen4_data; diff --git a/nfc_magic/lib/magic/nfc_magic_scanner.h b/nfc_magic/magic/nfc_magic_scanner.h similarity index 93% rename from nfc_magic/lib/magic/nfc_magic_scanner.h rename to nfc_magic/magic/nfc_magic_scanner.h index b6fa670982c..20355c85664 100644 --- a/nfc_magic/lib/magic/nfc_magic_scanner.h +++ b/nfc_magic/magic/nfc_magic_scanner.h @@ -40,7 +40,7 @@ void nfc_magic_scanner_start( void nfc_magic_scanner_stop(NfcMagicScanner* instance); -Gen4* nfc_magic_scanner_get_gen4_data(NfcMagicScanner* instance); +const Gen4* nfc_magic_scanner_get_gen4_data(NfcMagicScanner* instance); #ifdef __cplusplus } diff --git a/nfc_magic/lib/magic/protocols/gen1a/gen1a_poller.c b/nfc_magic/magic/protocols/gen1a/gen1a_poller.c similarity index 100% rename from nfc_magic/lib/magic/protocols/gen1a/gen1a_poller.c rename to nfc_magic/magic/protocols/gen1a/gen1a_poller.c diff --git a/nfc_magic/lib/magic/protocols/gen1a/gen1a_poller.h b/nfc_magic/magic/protocols/gen1a/gen1a_poller.h similarity index 100% rename from nfc_magic/lib/magic/protocols/gen1a/gen1a_poller.h rename to nfc_magic/magic/protocols/gen1a/gen1a_poller.h diff --git a/nfc_magic/lib/magic/protocols/gen1a/gen1a_poller_i.c b/nfc_magic/magic/protocols/gen1a/gen1a_poller_i.c similarity index 100% rename from nfc_magic/lib/magic/protocols/gen1a/gen1a_poller_i.c rename to nfc_magic/magic/protocols/gen1a/gen1a_poller_i.c diff --git a/nfc_magic/lib/magic/protocols/gen1a/gen1a_poller_i.h b/nfc_magic/magic/protocols/gen1a/gen1a_poller_i.h similarity index 100% rename from nfc_magic/lib/magic/protocols/gen1a/gen1a_poller_i.h rename to nfc_magic/magic/protocols/gen1a/gen1a_poller_i.h diff --git a/nfc_magic/magic/protocols/gen2/crypto1.c b/nfc_magic/magic/protocols/gen2/crypto1.c new file mode 100644 index 00000000000..2ac2d771cc3 --- /dev/null +++ b/nfc_magic/magic/protocols/gen2/crypto1.c @@ -0,0 +1,178 @@ +#include "crypto1.h" + +#include +#include +#include + +// Algorithm from https://github.com/RfidResearchGroup/proxmark3.git + +#define SWAPENDIAN(x) \ + ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) +#define LF_POLY_ODD (0x29CE5C) +#define LF_POLY_EVEN (0x870804) + +#define BEBIT(x, n) FURI_BIT(x, (n) ^ 24) + +Crypto1* crypto1_alloc() { + Crypto1* instance = malloc(sizeof(Crypto1)); + + return instance; +} + +void crypto1_free(Crypto1* instance) { + furi_assert(instance); + + free(instance); +} + +void crypto1_reset(Crypto1* crypto1) { + furi_assert(crypto1); + crypto1->even = 0; + crypto1->odd = 0; +} + +void crypto1_init(Crypto1* crypto1, uint64_t key) { + furi_assert(crypto1); + crypto1->even = 0; + crypto1->odd = 0; + for(int8_t i = 47; i > 0; i -= 2) { + crypto1->odd = crypto1->odd << 1 | FURI_BIT(key, (i - 1) ^ 7); + crypto1->even = crypto1->even << 1 | FURI_BIT(key, i ^ 7); + } +} + +static uint32_t crypto1_filter(uint32_t in) { + uint32_t out = 0; + out = 0xf22c0 >> (in & 0xf) & 16; + out |= 0x6c9c0 >> (in >> 4 & 0xf) & 8; + out |= 0x3c8b0 >> (in >> 8 & 0xf) & 4; + out |= 0x1e458 >> (in >> 12 & 0xf) & 2; + out |= 0x0d938 >> (in >> 16 & 0xf) & 1; + return FURI_BIT(0xEC57E80A, out); +} + +uint8_t crypto1_bit(Crypto1* crypto1, uint8_t in, int is_encrypted) { + furi_assert(crypto1); + uint8_t out = crypto1_filter(crypto1->odd); + uint32_t feed = out & (!!is_encrypted); + feed ^= !!in; + feed ^= LF_POLY_ODD & crypto1->odd; + feed ^= LF_POLY_EVEN & crypto1->even; + crypto1->even = crypto1->even << 1 | (nfc_util_even_parity32(feed)); + + FURI_SWAP(crypto1->odd, crypto1->even); + return out; +} + +uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted) { + furi_assert(crypto1); + uint8_t out = 0; + for(uint8_t i = 0; i < 8; i++) { + out |= crypto1_bit(crypto1, FURI_BIT(in, i), is_encrypted) << i; + } + return out; +} + +uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted) { + furi_assert(crypto1); + uint32_t out = 0; + for(uint8_t i = 0; i < 32; i++) { + out |= (uint32_t)crypto1_bit(crypto1, BEBIT(in, i), is_encrypted) << (24 ^ i); + } + return out; +} + +uint32_t prng_successor(uint32_t x, uint32_t n) { + SWAPENDIAN(x); + while(n--) x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31; + + return SWAPENDIAN(x); +} + +void crypto1_decrypt(Crypto1* crypto, const BitBuffer* buff, BitBuffer* out) { + furi_assert(crypto); + furi_assert(buff); + furi_assert(out); + + size_t bits = bit_buffer_get_size(buff); + bit_buffer_set_size(out, bits); + const uint8_t* encrypted_data = bit_buffer_get_data(buff); + if(bits < 8) { + uint8_t decrypted_byte = 0; + uint8_t encrypted_byte = encrypted_data[0]; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_byte, 0)) << 0; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_byte, 1)) << 1; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_byte, 2)) << 2; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_byte, 3)) << 3; + bit_buffer_set_byte(out, 0, decrypted_byte); + } else { + for(size_t i = 0; i < bits / 8; i++) { + uint8_t decrypted_byte = crypto1_byte(crypto, 0, 0) ^ encrypted_data[i]; + bit_buffer_set_byte(out, i, decrypted_byte); + } + } +} + +void crypto1_encrypt(Crypto1* crypto, uint8_t* keystream, const BitBuffer* buff, BitBuffer* out) { + furi_assert(crypto); + furi_assert(buff); + furi_assert(out); + + size_t bits = bit_buffer_get_size(buff); + bit_buffer_set_size(out, bits); + const uint8_t* plain_data = bit_buffer_get_data(buff); + if(bits < 8) { + uint8_t encrypted_byte = 0; + for(size_t i = 0; i < bits; i++) { + encrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(plain_data[0], i)) << i; + } + bit_buffer_set_byte(out, 0, encrypted_byte); + } else { + for(size_t i = 0; i < bits / 8; i++) { + uint8_t encrypted_byte = crypto1_byte(crypto, keystream ? keystream[i] : 0, 0) ^ + plain_data[i]; + bool parity_bit = + ((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(plain_data[i])) & 0x01); + bit_buffer_set_byte_with_parity(out, i, encrypted_byte, parity_bit); + } + } +} + +void crypto1_encrypt_reader_nonce( + Crypto1* crypto, + uint64_t key, + uint32_t cuid, + uint8_t* nt, + uint8_t* nr, + BitBuffer* out, + bool is_nested) { + furi_assert(crypto); + furi_assert(nt); + furi_assert(nr); + furi_assert(out); + + bit_buffer_set_size_bytes(out, 8); + uint32_t nt_num = bit_lib_bytes_to_num_be(nt, sizeof(uint32_t)); + + crypto1_init(crypto, key); + if(is_nested) { + nt_num = crypto1_word(crypto, nt_num ^ cuid, 1) ^ nt_num; + } else { + crypto1_word(crypto, nt_num ^ cuid, 0); + } + + for(size_t i = 0; i < 4; i++) { + uint8_t byte = crypto1_byte(crypto, nr[i], 0) ^ nr[i]; + bool parity_bit = ((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nr[i])) & 0x01); + bit_buffer_set_byte_with_parity(out, i, byte, parity_bit); + nr[i] = byte; + } + + nt_num = prng_successor(nt_num, 32); + for(size_t i = 4; i < 8; i++) { + nt_num = prng_successor(nt_num, 8); + uint8_t byte = crypto1_byte(crypto, 0, 0) ^ (uint8_t)(nt_num); + bool parity_bit = ((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nt_num)) & 0x01); + bit_buffer_set_byte_with_parity(out, i, byte, parity_bit); + } +} diff --git a/nfc_magic/magic/protocols/gen2/crypto1.h b/nfc_magic/magic/protocols/gen2/crypto1.h new file mode 100644 index 00000000000..7cc16fcffde --- /dev/null +++ b/nfc_magic/magic/protocols/gen2/crypto1.h @@ -0,0 +1,45 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + uint32_t odd; + uint32_t even; +} Crypto1; + +Crypto1* crypto1_alloc(); + +void crypto1_free(Crypto1* instance); + +void crypto1_reset(Crypto1* crypto1); + +void crypto1_init(Crypto1* crypto1, uint64_t key); + +uint8_t crypto1_bit(Crypto1* crypto1, uint8_t in, int is_encrypted); + +uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted); + +uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted); + +void crypto1_decrypt(Crypto1* crypto, const BitBuffer* buff, BitBuffer* out); + +void crypto1_encrypt(Crypto1* crypto, uint8_t* keystream, const BitBuffer* buff, BitBuffer* out); + +void crypto1_encrypt_reader_nonce( + Crypto1* crypto, + uint64_t key, + uint32_t cuid, + uint8_t* nt, + uint8_t* nr, + BitBuffer* out, + bool is_nested); + +uint32_t prng_successor(uint32_t x, uint32_t n); + +#ifdef __cplusplus +} +#endif diff --git a/nfc_magic/magic/protocols/gen2/gen2_poller.c b/nfc_magic/magic/protocols/gen2/gen2_poller.c new file mode 100644 index 00000000000..18c043f138e --- /dev/null +++ b/nfc_magic/magic/protocols/gen2/gen2_poller.c @@ -0,0 +1,594 @@ +#include "gen2_poller_i.h" +#include + +#include + +#define GEN2_POLLER_THREAD_FLAG_DETECTED (1U << 0) + +#define TAG "GEN2" + +typedef NfcCommand (*Gen2PollerStateHandler)(Gen2Poller* instance); + +typedef struct { + NfcPoller* poller; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + FuriThreadId thread_id; + bool detected; + Gen2PollerError error; +} Gen2PollerDetectContext; + +// Array of known Gen2 ATS responses +// 0978009102DABC1910F005 - flavour 2 +// 0978009102DABC1910F005 - flavour 4 +// 0D780071028849A13020150608563D - flavour 6 +// Other flavours can't be detected other than by just trying to write to block 0 +const uint8_t GEN2_ATS[3][16] = { + {0x09, 0x78, 0x00, 0x91, 0x02, 0xDA, 0xBC, 0x19, 0x10, 0xF0, 0x05}, + {0x09, 0x78, 0x00, 0x91, 0x02, 0xDA, 0xBC, 0x19, 0x10, 0xF0, 0x05}, + {0x0D, 0x78, 0x00, 0x71, 0x02, 0x88, 0x49, 0xA1, 0x30, 0x20, 0x15, 0x06, 0x08, 0x56, 0x3D}}; + +static const MfClassicBlock gen2_poller_default_block_0 = { + .data = + {0x00, + 0x01, + 0x02, + 0x03, + 0x00, // BCC - IMPORTANT + 0x08, // SAK + 0x04, // ATQA0 + 0x00, // ATQA1 + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00}, +}; + +static const MfClassicBlock gen2_poller_default_empty_block = { + .data = + {0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00}, +}; + +static MfClassicBlock gen2_poller_default_sector_trailer_block = { + .data = + {0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0x07, + 0x80, + 0x69, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF}, +}; + +const char* const gen2_problem_strings[] = { + "UID may be non-\nrewritable. Check data after writing", + "No data in selected file", + "Some sectors are locked", + "Can't find keys to some sectors", + "The selected file is incomplete", +}; + +Gen2Poller* gen2_poller_alloc(Nfc* nfc) { + Gen2Poller* instance = malloc(sizeof(Gen2Poller)); + instance->poller = nfc_poller_alloc(nfc, NfcProtocolIso14443_3a); + instance->data = mf_classic_alloc(); + instance->crypto = crypto1_alloc(); + instance->tx_plain_buffer = bit_buffer_alloc(GEN2_POLLER_MAX_BUFFER_SIZE); + instance->tx_encrypted_buffer = bit_buffer_alloc(GEN2_POLLER_MAX_BUFFER_SIZE); + instance->rx_plain_buffer = bit_buffer_alloc(GEN2_POLLER_MAX_BUFFER_SIZE); + instance->rx_encrypted_buffer = bit_buffer_alloc(GEN2_POLLER_MAX_BUFFER_SIZE); + instance->card_state = Gen2CardStateLost; + + instance->gen2_event.data = &instance->gen2_event_data; + + instance->mode_ctx.write_ctx.mfc_data_source = malloc(sizeof(MfClassicData)); + instance->mode_ctx.write_ctx.mfc_data_target = malloc(sizeof(MfClassicData)); + + instance->mode_ctx.write_ctx.need_halt_before_write = true; + + return instance; +} + +void gen2_poller_free(Gen2Poller* instance) { + furi_assert(instance); + furi_assert(instance->data); + furi_assert(instance->crypto); + furi_assert(instance->tx_plain_buffer); + furi_assert(instance->rx_plain_buffer); + furi_assert(instance->tx_encrypted_buffer); + furi_assert(instance->rx_encrypted_buffer); + + nfc_poller_free(instance->poller); + mf_classic_free(instance->data); + crypto1_free(instance->crypto); + bit_buffer_free(instance->tx_plain_buffer); + bit_buffer_free(instance->rx_plain_buffer); + bit_buffer_free(instance->tx_encrypted_buffer); + bit_buffer_free(instance->rx_encrypted_buffer); + + free(instance->mode_ctx.write_ctx.mfc_data_source); + free(instance->mode_ctx.write_ctx.mfc_data_target); + + free(instance); +} + +NfcCommand gen2_poller_detect_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolIso14443_3a); + furi_assert(event.event_data); + furi_assert(event.instance); + + NfcCommand command = NfcCommandStop; + Gen2PollerDetectContext* detect_ctx = context; + Iso14443_3aPoller* iso3_poller = event.instance; + Iso14443_3aPollerEvent* iso3_event = event.event_data; + detect_ctx->error = Gen2PollerErrorTimeout; + + bit_buffer_reset(detect_ctx->tx_buffer); + bit_buffer_append_byte(detect_ctx->tx_buffer, GEN2_CMD_READ_ATS); + bit_buffer_append_byte(detect_ctx->tx_buffer, GEN2_FSDI_256 << 4); + + if(iso3_event->type == Iso14443_3aPollerEventTypeReady) { + do { + const Iso14443_3aError iso14443_3a_error = iso14443_3a_poller_send_standard_frame( + iso3_poller, detect_ctx->tx_buffer, detect_ctx->rx_buffer, GEN2_POLLER_MAX_FWT); + + if(iso14443_3a_error != Iso14443_3aErrorNone && + iso14443_3a_error != Iso14443_3aErrorWrongCrc) { + FURI_LOG_E(TAG, "ATS request failed"); + detect_ctx->error = Gen2PollerErrorProtocol; + break; + + } else { + FURI_LOG_D(TAG, "ATS request succeeded:"); + // Check against known ATS responses + for(size_t i = 0; i < COUNT_OF(GEN2_ATS); i++) { + if(memcmp( + bit_buffer_get_data(detect_ctx->rx_buffer), + GEN2_ATS[i], + sizeof(GEN2_ATS[i])) == 0) { + detect_ctx->error = Gen2PollerErrorNone; + break; + } + } + } + } while(false); + } else if(iso3_event->type == Iso14443_3aPollerEventTypeError) { + detect_ctx->error = Gen2PollerErrorTimeout; + } + furi_thread_flags_set(detect_ctx->thread_id, GEN2_POLLER_THREAD_FLAG_DETECTED); + + return command; +} + +Gen2PollerError gen2_poller_detect(Nfc* nfc) { + furi_assert(nfc); + + Gen2PollerDetectContext detect_ctx = { + .poller = nfc_poller_alloc(nfc, NfcProtocolIso14443_3a), + .tx_buffer = bit_buffer_alloc(GEN2_POLLER_MAX_BUFFER_SIZE), + .rx_buffer = bit_buffer_alloc(GEN2_POLLER_MAX_BUFFER_SIZE), + .thread_id = furi_thread_get_current_id(), + .detected = false, + .error = Gen2PollerErrorNone, + }; + + nfc_poller_start(detect_ctx.poller, gen2_poller_detect_callback, &detect_ctx); + uint32_t flags = + furi_thread_flags_wait(GEN2_POLLER_THREAD_FLAG_DETECTED, FuriFlagWaitAny, FuriWaitForever); + if(flags & GEN2_POLLER_THREAD_FLAG_DETECTED) { + furi_thread_flags_clear(GEN2_POLLER_THREAD_FLAG_DETECTED); + } + nfc_poller_stop(detect_ctx.poller); + + bit_buffer_free(detect_ctx.tx_buffer); + bit_buffer_free(detect_ctx.rx_buffer); + nfc_poller_free(detect_ctx.poller); + + return detect_ctx.error; +} + +NfcCommand gen2_poller_idle_handler(Gen2Poller* instance) { + furi_assert(instance); + + NfcCommand command = NfcCommandContinue; + + instance->mode_ctx.write_ctx.current_block = 0; + instance->gen2_event.type = Gen2PollerEventTypeDetected; + command = instance->callback(instance->gen2_event, instance->context); + instance->state = Gen2PollerStateRequestMode; + + return command; +} + +NfcCommand gen2_poller_request_mode_handler(Gen2Poller* instance) { + furi_assert(instance); + + NfcCommand command = NfcCommandContinue; + + instance->gen2_event.type = Gen2PollerEventTypeRequestMode; + command = instance->callback(instance->gen2_event, instance->context); + instance->mode = instance->gen2_event_data.poller_mode.mode; + if(instance->gen2_event_data.poller_mode.mode == Gen2PollerModeWipe) { + instance->state = Gen2PollerStateWriteTargetDataRequest; + } else { + instance->state = Gen2PollerStateWriteSourceDataRequest; + } + + return command; +} + +NfcCommand gen2_poller_write_source_data_request_handler(Gen2Poller* instance) { + NfcCommand command = NfcCommandContinue; + + instance->gen2_event.type = Gen2PollerEventTypeRequestDataToWrite; + command = instance->callback(instance->gen2_event, instance->context); + memcpy( + instance->mode_ctx.write_ctx.mfc_data_source, + instance->gen2_event_data.data_to_write.mfc_data, + sizeof(MfClassicData)); + instance->state = Gen2PollerStateWriteTargetDataRequest; + + return command; +} + +NfcCommand gen2_poller_write_target_data_request_handler(Gen2Poller* instance) { + NfcCommand command = NfcCommandContinue; + + instance->gen2_event.type = Gen2PollerEventTypeRequestTargetData; + command = instance->callback(instance->gen2_event, instance->context); + memcpy( + instance->mode_ctx.write_ctx.mfc_data_target, + instance->gen2_event_data.target_data.mfc_data, + sizeof(MfClassicData)); + if(instance->mode == Gen2PollerModeWipe) { + instance->state = Gen2PollerStateWipe; + } else { + instance->state = Gen2PollerStateWrite; + } + + return command; +} + +Gen2PollerError gen2_poller_write_block_handler( + Gen2Poller* instance, + uint8_t block_num, + const MfClassicBlock* block) { + furi_assert(instance); + + Gen2PollerError error = Gen2PollerErrorNone; + Gen2PollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx; + MfClassicKey auth_key = write_ctx->auth_key; + + do { + // Compare the target and source data + if(memcmp(block->data, write_ctx->mfc_data_target->block[block_num].data, 16) == 0) { + FURI_LOG_D(TAG, "Block %d is the same, skipping", block_num); + break; + } + + // Reauth if necessary + if(write_ctx->need_halt_before_write) { + FURI_LOG_D(TAG, "Auth before writing block %d", write_ctx->current_block); + error = gen2_poller_auth( + instance, write_ctx->current_block, &auth_key, write_ctx->write_key, NULL); + if(error != Gen2PollerErrorNone) { + FURI_LOG_D( + TAG, "Failed to auth to block %d for writing", write_ctx->current_block); + break; + } + } + + // Write the block + error = gen2_poller_write_block(instance, write_ctx->current_block, block); + if(error != Gen2PollerErrorNone) { + FURI_LOG_D(TAG, "Failed to write block %d", write_ctx->current_block); + break; + } + } while(false); + FURI_LOG_D(TAG, "Block %d finished, halting", write_ctx->current_block); + gen2_poller_halt(instance); + return error; +} + +NfcCommand gen2_poller_wipe_handler(Gen2Poller* instance) { + NfcCommand command = NfcCommandContinue; + Gen2PollerError error = Gen2PollerErrorNone; + Gen2PollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx; + uint8_t block_num = write_ctx->current_block; + + do { + // Check whether the ACs for that block are known in target data + if(!mf_classic_is_block_read( + write_ctx->mfc_data_target, + mf_classic_get_sector_trailer_num_by_block(block_num))) { + FURI_LOG_E(TAG, "Sector trailer for block %d not present in target data", block_num); + break; + } + + // Check whether ACs need to be reset and whether they can be reset + if(!gen2_poller_can_write_block(write_ctx->mfc_data_target, block_num)) { + if(!gen2_can_reset_access_conditions(write_ctx->mfc_data_target, block_num)) { + FURI_LOG_E(TAG, "Block %d cannot be written", block_num); + break; + } else { + FURI_LOG_D(TAG, "Resetting ACs for block %d", block_num); + // Generate a block with old keys and default ACs (0xFF, 0x07, 0x80) + MfClassicBlock block; + memset(&block, 0, sizeof(block)); + memcpy(block.data, write_ctx->mfc_data_target->block[block_num].data, 16); + memcpy(block.data + 6, "\xFF\x07\x80", 3); + + error = gen2_poller_write_block_handler(instance, block_num, &block); + if(error != Gen2PollerErrorNone) { + FURI_LOG_E(TAG, "Failed to reset ACs for block %d", block_num); + break; + } else { + FURI_LOG_D(TAG, "ACs for block %d reset", block_num); + memcpy(write_ctx->mfc_data_target->block[block_num].data, block.data, 16); + } + } + } + + // Figure out which key to use for writing + write_ctx->write_key = + gen2_poller_get_key_type_to_write(write_ctx->mfc_data_target, block_num); + + // Get the key to use for writing from the target data + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector( + write_ctx->mfc_data_target, mf_classic_get_sector_by_block(block_num)); + if(write_ctx->write_key == MfClassicKeyTypeA) { + write_ctx->auth_key = sec_tr->key_a; + } else { + write_ctx->auth_key = sec_tr->key_b; + } + + // Write the default block depending on the block type + if(block_num == 0) { + error = + gen2_poller_write_block_handler(instance, block_num, &gen2_poller_default_block_0); + } else if(mf_classic_is_sector_trailer(block_num)) { + error = gen2_poller_write_block_handler( + instance, block_num, &gen2_poller_default_sector_trailer_block); + } else { + error = gen2_poller_write_block_handler( + instance, block_num, &gen2_poller_default_empty_block); + } + if(error != Gen2PollerErrorNone) { + FURI_LOG_E(TAG, "Couldn't write block %d", block_num); + } + } while(false); + + write_ctx->current_block++; + + if(error != Gen2PollerErrorNone) { + FURI_LOG_D(TAG, "Error occurred: %d", error); + } + + if(write_ctx->current_block == + mf_classic_get_total_block_num(write_ctx->mfc_data_target->type)) { + instance->state = Gen2PollerStateSuccess; + } + + return command; +} + +NfcCommand gen2_poller_write_handler(Gen2Poller* instance) { + NfcCommand command = NfcCommandContinue; + Gen2PollerError error = Gen2PollerErrorNone; + Gen2PollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx; + uint8_t block_num = write_ctx->current_block; + + do { + // Check whether the block is present in the source data + if(!mf_classic_is_block_read(write_ctx->mfc_data_source, block_num)) { + // FURI_LOG_E(TAG, "Block %d not present in source data", block_num); + break; + } + + // Check whether the ACs for that block are known in target data + if(!mf_classic_is_block_read( + write_ctx->mfc_data_target, + mf_classic_get_sector_trailer_num_by_block(block_num))) { + FURI_LOG_E(TAG, "Sector trailer for block %d not present in target data", block_num); + break; + } + + // Check whether ACs need to be reset and whether they can be reset + if(!gen2_poller_can_write_block(write_ctx->mfc_data_target, block_num)) { + if(!gen2_can_reset_access_conditions(write_ctx->mfc_data_target, block_num)) { + FURI_LOG_E(TAG, "Block %d cannot be written", block_num); + break; + } else { + FURI_LOG_D(TAG, "Resetting ACs for block %d", block_num); + // Generate a block with old keys and default ACs (0xFF, 0x07, 0x80) + MfClassicBlock block; + memset(&block, 0, sizeof(block)); + memcpy(block.data, write_ctx->mfc_data_target->block[block_num].data, 16); + memcpy(block.data + 6, "\xFF\x07\x80", 3); + + error = gen2_poller_write_block_handler(instance, block_num, &block); + if(error != Gen2PollerErrorNone) { + FURI_LOG_E(TAG, "Failed to reset ACs for block %d", block_num); + break; + } else { + FURI_LOG_D(TAG, "ACs for block %d reset", block_num); + memcpy(write_ctx->mfc_data_target->block[block_num].data, block.data, 16); + } + } + } + + // Figure out which key to use for writing + write_ctx->write_key = + gen2_poller_get_key_type_to_write(write_ctx->mfc_data_target, block_num); + + // Get the key to use for writing from the target data + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector( + write_ctx->mfc_data_target, mf_classic_get_sector_by_block(block_num)); + if(write_ctx->write_key == MfClassicKeyTypeA) { + write_ctx->auth_key = sec_tr->key_a; + } else { + write_ctx->auth_key = sec_tr->key_b; + } + + // Write the block + error = gen2_poller_write_block_handler( + instance, block_num, &write_ctx->mfc_data_source->block[block_num]); + if(error != Gen2PollerErrorNone) { + FURI_LOG_E(TAG, "Couldn't write block %d", block_num); + } + } while(false); + write_ctx->current_block++; + + if(error != Gen2PollerErrorNone) { + FURI_LOG_D(TAG, "Error occurred: %d", error); + } else if( + write_ctx->current_block == + mf_classic_get_total_block_num(write_ctx->mfc_data_source->type)) { + instance->state = Gen2PollerStateSuccess; + } + + return command; +} + +NfcCommand gen2_poller_success_handler(Gen2Poller* instance) { + furi_assert(instance); + + NfcCommand command = NfcCommandContinue; + + instance->gen2_event.type = Gen2PollerEventTypeSuccess; + command = instance->callback(instance->gen2_event, instance->context); + instance->state = Gen2PollerStateIdle; + + return command; +} + +NfcCommand gen2_poller_fail_handler(Gen2Poller* instance) { + furi_assert(instance); + + NfcCommand command = NfcCommandContinue; + + instance->gen2_event.type = Gen2PollerEventTypeFail; + command = instance->callback(instance->gen2_event, instance->context); + instance->state = Gen2PollerStateIdle; + + return command; +} + +static const Gen2PollerStateHandler gen2_poller_state_handlers[Gen2PollerStateNum] = { + [Gen2PollerStateIdle] = gen2_poller_idle_handler, + [Gen2PollerStateRequestMode] = gen2_poller_request_mode_handler, + [Gen2PollerStateWipe] = gen2_poller_wipe_handler, + [Gen2PollerStateWriteSourceDataRequest] = gen2_poller_write_source_data_request_handler, + [Gen2PollerStateWriteTargetDataRequest] = gen2_poller_write_target_data_request_handler, + [Gen2PollerStateWrite] = gen2_poller_write_handler, + [Gen2PollerStateSuccess] = gen2_poller_success_handler, + [Gen2PollerStateFail] = gen2_poller_fail_handler, +}; + +NfcCommand gen2_poller_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolIso14443_3a); + furi_assert(event.event_data); + furi_assert(event.instance); + + NfcCommand command = NfcCommandContinue; + Gen2Poller* instance = context; + instance->iso3_poller = event.instance; + Iso14443_3aPollerEvent* iso3_event = event.event_data; + + if(iso3_event->type == Iso14443_3aPollerEventTypeReady) { + command = gen2_poller_state_handlers[instance->state](instance); + } + + return command; +} + +void gen2_poller_start(Gen2Poller* instance, Gen2PollerCallback callback, void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; + + nfc_poller_start(instance->poller, gen2_poller_callback, instance); + return; +} + +void gen2_poller_stop(Gen2Poller* instance) { + furi_assert(instance); + + FURI_LOG_D(TAG, "Stopping Gen2 poller"); + nfc_poller_stop(instance->poller); + return; +} + +Gen2PollerWriteProblems gen2_poller_check_target_problems(NfcDevice* target_dev) { + furi_assert(target_dev); + + Gen2PollerWriteProblems problems = {0}; + const MfClassicData* mfc_data = nfc_device_get_data(target_dev, NfcProtocolMfClassic); + + if(mfc_data) { + uint16_t total_block_num = mf_classic_get_total_block_num(mfc_data->type); + for(uint16_t i = 0; i < total_block_num; i++) { + if(mf_classic_is_sector_trailer(i)) { + problems.all_problems |= + gen2_poller_can_write_sector_trailer(mfc_data, i).all_problems; + } else { + problems.all_problems |= + gen2_poller_can_write_data_block(mfc_data, i).all_problems; + } + } + } else { + problems.no_data = true; + } + + return problems; +} + +Gen2PollerWriteProblems gen2_poller_check_source_problems(NfcDevice* source_dev) { + furi_assert(source_dev); + + Gen2PollerWriteProblems problems = {0}; + const MfClassicData* mfc_data = nfc_device_get_data(source_dev, NfcProtocolMfClassic); + + if(mfc_data) { + uint16_t total_block_num = mf_classic_get_total_block_num(mfc_data->type); + for(uint16_t i = 0; i < total_block_num; i++) { + if(!mf_classic_is_block_read(mfc_data, i)) { + problems.missing_source_data = true; + } + } + } + + return problems; +} diff --git a/nfc_magic/magic/protocols/gen2/gen2_poller.h b/nfc_magic/magic/protocols/gen2/gen2_poller.h new file mode 100644 index 00000000000..20243d1c82e --- /dev/null +++ b/nfc_magic/magic/protocols/gen2/gen2_poller.h @@ -0,0 +1,97 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + Gen2PollerErrorNone, + Gen2PollerErrorNotPresent, + Gen2PollerErrorProtocol, + Gen2PollerErrorAuth, + Gen2PollerErrorTimeout, + Gen2PollerErrorAccess, +} Gen2PollerError; + +// Possible write problems, sorted by priority top to bottom +typedef union { + uint8_t all_problems; + struct { + bool uid_locked : 1; // UID may be non-rewritable. Check data after writing + bool no_data : 1; // Shouldn't happen, mfc_data missing in nfc device + bool locked_access_bits : 1; // Access bits on the target card don't allow writing in some cases + bool missing_target_keys : 1; // Keys to write some sectors are not available + bool missing_source_data : 1; // The source dump is incomplete + }; +} Gen2PollerWriteProblems; + +#define GEN2_POLLER_WRITE_PROBLEMS_LEN (5) + +extern const char* const gen2_problem_strings[]; + +typedef enum { + Gen2PollerEventTypeDetected, + Gen2PollerEventTypeRequestMode, + Gen2PollerEventTypeRequestDataToWrite, + Gen2PollerEventTypeRequestTargetData, + + Gen2PollerEventTypeSuccess, + Gen2PollerEventTypeFail, +} Gen2PollerEventType; + +typedef enum { + Gen2PollerModeWipe, + Gen2PollerModeWrite, +} Gen2PollerMode; + +typedef struct { + Gen2PollerMode mode; +} Gen2PollerEventDataRequestMode; + +typedef struct { + const MfClassicData* mfc_data; +} Gen2PollerEventDataRequestDataToWrite; + +typedef struct { + const MfClassicData* mfc_data; +} Gen2PollerEventDataRequestTargetData; + +typedef union { + Gen2PollerEventDataRequestMode poller_mode; + Gen2PollerEventDataRequestDataToWrite data_to_write; + Gen2PollerEventDataRequestTargetData target_data; +} Gen2PollerEventData; + +typedef struct { + Gen2PollerEventType type; + Gen2PollerEventData* data; +} Gen2PollerEvent; + +typedef NfcCommand (*Gen2PollerCallback)(Gen2PollerEvent event, void* context); + +typedef struct Gen2Poller Gen2Poller; + +Gen2PollerError gen2_poller_detect(Nfc* nfc); + +Gen2Poller* gen2_poller_alloc(Nfc* nfc); + +void gen2_poller_free(Gen2Poller* instance); + +void gen2_poller_start(Gen2Poller* instance, Gen2PollerCallback callback, void* context); + +void gen2_poller_stop(Gen2Poller* instance); + +Gen2PollerWriteProblems gen2_poller_check_target_problems(NfcDevice* target_dev); + +Gen2PollerWriteProblems gen2_poller_check_source_problems(NfcDevice* source_dev); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/nfc_magic/magic/protocols/gen2/gen2_poller_i.c b/nfc_magic/magic/protocols/gen2/gen2_poller_i.c new file mode 100644 index 00000000000..7e403f57f98 --- /dev/null +++ b/nfc_magic/magic/protocols/gen2/gen2_poller_i.c @@ -0,0 +1,629 @@ +#include "gen2_poller_i.h" +#include + +#include +#include "furi_hal_random.h" + +#include + +#define TAG "GEN2_I" + +MfClassicError mf_classic_process_error(Iso14443_3aError error) { + MfClassicError ret = MfClassicErrorNone; + + switch(error) { + case Iso14443_3aErrorNone: + ret = MfClassicErrorNone; + break; + case Iso14443_3aErrorNotPresent: + ret = MfClassicErrorNotPresent; + break; + case Iso14443_3aErrorColResFailed: + case Iso14443_3aErrorCommunication: + case Iso14443_3aErrorWrongCrc: + ret = MfClassicErrorProtocol; + break; + case Iso14443_3aErrorTimeout: + ret = MfClassicErrorTimeout; + break; + default: + ret = MfClassicErrorProtocol; + break; + } + return ret; +} + +Gen2PollerError gen2_poller_process_iso3_error(Iso14443_3aError error) { + Gen2PollerError ret = Gen2PollerErrorNone; + + switch(error) { + case Iso14443_3aErrorNone: + ret = Gen2PollerErrorNone; + break; + case Iso14443_3aErrorNotPresent: + ret = Gen2PollerErrorNotPresent; + break; + case Iso14443_3aErrorWrongCrc: + ret = Gen2PollerErrorProtocol; + break; + case Iso14443_3aErrorTimeout: + ret = Gen2PollerErrorTimeout; + break; + default: + ret = Gen2PollerErrorProtocol; + break; + } + return ret; +} + +Gen2PollerError gen2_poller_process_mifare_classic_error(MfClassicError error) { + Gen2PollerError ret = Gen2PollerErrorNone; + + switch(error) { + case MfClassicErrorNone: + ret = Gen2PollerErrorNone; + break; + case MfClassicErrorNotPresent: + ret = Gen2PollerErrorNotPresent; + break; + case MfClassicErrorProtocol: + ret = Gen2PollerErrorProtocol; + break; + case MfClassicErrorAuth: + ret = Gen2PollerErrorAuth; + break; + case MfClassicErrorTimeout: + ret = Gen2PollerErrorTimeout; + break; + default: + ret = Gen2PollerErrorProtocol; + break; + } + + return ret; +} + +static Gen2PollerError gen2_poller_get_nt_common( + Gen2Poller* instance, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicNt* nt, + bool is_nested) { + MfClassicError ret = MfClassicErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + uint8_t auth_type = (key_type == MfClassicKeyTypeB) ? MF_CLASSIC_CMD_AUTH_KEY_B : + MF_CLASSIC_CMD_AUTH_KEY_A; + uint8_t auth_cmd[2] = {auth_type, block_num}; + bit_buffer_copy_bytes(instance->tx_plain_buffer, auth_cmd, sizeof(auth_cmd)); + + if(is_nested) { + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso3_poller, + instance->tx_encrypted_buffer, + instance->rx_plain_buffer, // NT gets decrypted by mf_classic_async_auth + GEN2_POLLER_MAX_FWT); + if(error != Iso14443_3aErrorNone) { + ret = mf_classic_process_error(error); + break; + } + } else { + FURI_LOG_D(TAG, "Plain auth cmd"); + error = iso14443_3a_poller_send_standard_frame( + instance->iso3_poller, + instance->tx_plain_buffer, + instance->rx_plain_buffer, + GEN2_POLLER_MAX_FWT); + if(error != Iso14443_3aErrorWrongCrc) { + ret = mf_classic_process_error(error); + break; + } + } + if(bit_buffer_get_size_bytes(instance->rx_plain_buffer) != sizeof(MfClassicNt)) { + ret = MfClassicErrorProtocol; + break; + } + + if(nt) { + bit_buffer_write_bytes(instance->rx_plain_buffer, nt->data, sizeof(MfClassicNt)); + } + } while(false); + + return gen2_poller_process_mifare_classic_error(ret); +} + +Gen2PollerError gen2_poller_get_nt( + Gen2Poller* instance, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicNt* nt) { + return gen2_poller_get_nt_common(instance, block_num, key_type, nt, false); +} + +Gen2PollerError gen2_poller_get_nt_nested( + Gen2Poller* instance, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicNt* nt) { + return gen2_poller_get_nt_common(instance, block_num, key_type, nt, true); +} + +static Gen2PollerError gen2_poller_auth_common( + Gen2Poller* instance, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicAuthContext* data, + bool is_nested) { + Gen2PollerError ret = Gen2PollerErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + iso14443_3a_copy(instance->data->iso14443_3a_data, nfc_poller_get_data(instance->poller)); + + MfClassicNt nt = {}; + if(is_nested) { + ret = gen2_poller_get_nt_nested(instance, block_num, key_type, &nt); + } else { + ret = gen2_poller_get_nt(instance, block_num, key_type, &nt); + } + if(ret != Gen2PollerErrorNone) break; + if(data) { + data->nt = nt; + } + + uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); + uint64_t key_num = bit_lib_bytes_to_num_be(key->data, sizeof(MfClassicKey)); + MfClassicNr nr = {}; + furi_hal_random_fill_buf(nr.data, sizeof(MfClassicNr)); + + crypto1_encrypt_reader_nonce( + instance->crypto, + key_num, + cuid, + nt.data, + nr.data, + instance->tx_encrypted_buffer, + is_nested); + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso3_poller, + instance->tx_encrypted_buffer, + instance->rx_encrypted_buffer, + GEN2_POLLER_MAX_FWT); + + if(error != Iso14443_3aErrorNone) { + ret = gen2_poller_process_iso3_error(error); + break; + } + if(bit_buffer_get_size_bytes(instance->rx_encrypted_buffer) != 4) { + ret = Gen2PollerErrorAuth; + } + + crypto1_word(instance->crypto, 0, 0); + instance->auth_state = Gen2AuthStatePassed; + + if(data) { + data->nr = nr; + const uint8_t* nr_ar = bit_buffer_get_data(instance->tx_encrypted_buffer); + memcpy(data->ar.data, &nr_ar[4], sizeof(MfClassicAr)); + bit_buffer_write_bytes( + instance->rx_encrypted_buffer, data->at.data, sizeof(MfClassicAt)); + } + } while(false); + + if(ret != Gen2PollerErrorNone) { + iso14443_3a_poller_halt(instance->iso3_poller); + } + + return ret; +} + +Gen2PollerError gen2_poller_auth( + Gen2Poller* instance, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicAuthContext* data) { + return gen2_poller_auth_common(instance, block_num, key, key_type, data, false); +} + +Gen2PollerError gen2_poller_halt(Gen2Poller* instance) { + Gen2PollerError ret = Gen2PollerErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + uint8_t halt_cmd[2] = {MF_CLASSIC_CMD_HALT_MSB, MF_CLASSIC_CMD_HALT_LSB}; + bit_buffer_copy_bytes(instance->tx_plain_buffer, halt_cmd, sizeof(halt_cmd)); + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + + if(instance->auth_state == Gen2AuthStatePassed) { + // Send an encrypted halt command + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + FURI_LOG_D(TAG, "Send enc halt"); + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso3_poller, + instance->tx_encrypted_buffer, + instance->rx_encrypted_buffer, + GEN2_POLLER_MAX_FWT); + } + + if(error != Iso14443_3aErrorNone) { + FURI_LOG_D(TAG, "Enc halt error"); + // Do not break because we still need to halt the iso3 poller + } + + // Send a regular halt command to halt the iso3 poller + FURI_LOG_D(TAG, "Send reg halt"); + error = iso14443_3a_poller_halt(instance->iso3_poller); + + if(error != Iso14443_3aErrorTimeout) { + FURI_LOG_D(TAG, "Reg halt error"); + // Do not break as well becaue the first halt command might have worked + // and the card didn't respond because it was already halted + } + + crypto1_reset(instance->crypto); + instance->auth_state = Gen2AuthStateIdle; + } while(false); + + return ret; +} + +Gen2PollerError + gen2_poller_write_block(Gen2Poller* instance, uint8_t block_num, const MfClassicBlock* data) { + Gen2PollerError ret = Gen2PollerErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + uint8_t write_block_cmd[2] = {MF_CLASSIC_CMD_WRITE_BLOCK, block_num}; + bit_buffer_copy_bytes(instance->tx_plain_buffer, write_block_cmd, sizeof(write_block_cmd)); + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso3_poller, + instance->tx_encrypted_buffer, + instance->rx_encrypted_buffer, + GEN2_POLLER_MAX_FWT); + if(error != Iso14443_3aErrorNone) { + ret = gen2_poller_process_iso3_error(error); + break; + } + if(bit_buffer_get_size(instance->rx_encrypted_buffer) != 4) { + ret = Gen2PollerErrorProtocol; + break; + } + + crypto1_decrypt( + instance->crypto, instance->rx_encrypted_buffer, instance->rx_plain_buffer); + + if(bit_buffer_get_byte(instance->rx_plain_buffer, 0) != MF_CLASSIC_CMD_ACK) { + FURI_LOG_D(TAG, "NACK received"); + ret = Gen2PollerErrorProtocol; + break; + } + + bit_buffer_copy_bytes(instance->tx_plain_buffer, data->data, sizeof(MfClassicBlock)); + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso3_poller, + instance->tx_encrypted_buffer, + instance->rx_encrypted_buffer, + GEN2_POLLER_MAX_FWT); + if(error != Iso14443_3aErrorNone) { + ret = gen2_poller_process_iso3_error(error); + break; + } + if(bit_buffer_get_size(instance->rx_encrypted_buffer) != 4) { + ret = Gen2PollerErrorProtocol; + break; + } + + crypto1_decrypt( + instance->crypto, instance->rx_encrypted_buffer, instance->rx_plain_buffer); + + if(bit_buffer_get_byte(instance->rx_plain_buffer, 0) != MF_CLASSIC_CMD_ACK) { + FURI_LOG_D(TAG, "NACK received"); + ret = Gen2PollerErrorProtocol; + break; + } + } while(false); + + return ret; +} + +bool gen2_poller_can_write_block(const MfClassicData* target_data, uint8_t block_num) { + furi_assert(target_data); + + bool can_write = true; + + if(block_num == 0 && target_data->iso14443_3a_data->uid_len == 7) { + // 7-byte UID gen2 cards are not supported yet, need further testing + can_write = false; + } + + if(mf_classic_is_sector_trailer(block_num)) { + can_write = gen2_poller_can_write_sector_trailer(target_data, block_num).all_problems == 0; + } else { + can_write = gen2_poller_can_write_data_block(target_data, block_num).all_problems == 0; + } + + return can_write; +} + +Gen2PollerWriteProblems + gen2_poller_can_write_data_block(const MfClassicData* target_data, uint8_t block_num) { + // Check whether it's possible to write the block + furi_assert(target_data); + + // Check rules: + // 1. Check if block is read + // 2. Check if we have any of the keys + // 3. For each key, check if we can write the block + // 3.1. If none of the keys can write the block, check whether access conditions can be reset to allow writing + // 3.2 If the above conditions are not met, return an error code + + Gen2PollerWriteProblems can_write = {0}; + + bool has_key_a = mf_classic_is_key_found( + target_data, mf_classic_get_sector_by_block(block_num), MfClassicKeyTypeA); + bool has_key_b = mf_classic_is_key_found( + target_data, mf_classic_get_sector_by_block(block_num), MfClassicKeyTypeB); + + if(!has_key_a && !has_key_b) { + can_write.missing_target_keys = true; + } + if(!gen2_is_allowed_access( + target_data, block_num, MfClassicKeyTypeA, MfClassicActionDataWrite) && + !gen2_is_allowed_access( + target_data, block_num, MfClassicKeyTypeB, MfClassicActionDataWrite)) { + if(!gen2_can_reset_access_conditions(target_data, block_num)) { + can_write.locked_access_bits = true; + } + } + + return can_write; +} + +Gen2PollerWriteProblems + gen2_poller_can_write_sector_trailer(const MfClassicData* target_data, uint8_t block_num) { + // Check whether it's possible to write the sector trailer + furi_assert(target_data); + + // Check rules: + // 1. Check if block is read + // 2. Check if we have any of the keys + // 3. For each key, check if we can write the block + // 3.1 Check that at least one of the keys can write Key A + // 3.1.1 If none of the keys can write Key A, check whether access conditions can be reset to allow writing + // 3.2 Check that at least one of the keys can write the Access Conditions + // 3.3 Check that at least one of the keys can write Key B + // 3.3.1 If none of the keys can write Key B, check whether access conditions can be reset to allow writing + // 3.4 If any of the above conditions are not met, return an error code + + Gen2PollerWriteProblems can_write = {0}; + + bool has_key_a = mf_classic_is_key_found( + target_data, mf_classic_get_sector_by_block(block_num), MfClassicKeyTypeA); + bool has_key_b = mf_classic_is_key_found( + target_data, mf_classic_get_sector_by_block(block_num), MfClassicKeyTypeB); + + if(!has_key_a && !has_key_b) { + can_write.missing_target_keys = true; + } + if(!gen2_is_allowed_access( + target_data, block_num, MfClassicKeyTypeA, MfClassicActionKeyAWrite) && + !gen2_is_allowed_access( + target_data, block_num, MfClassicKeyTypeB, MfClassicActionKeyAWrite)) { + if(!gen2_can_reset_access_conditions(target_data, block_num)) { + can_write.locked_access_bits = true; + } + } + if(!gen2_is_allowed_access(target_data, block_num, MfClassicKeyTypeA, MfClassicActionACWrite) && + !gen2_is_allowed_access(target_data, block_num, MfClassicKeyTypeB, MfClassicActionACWrite)) { + can_write.locked_access_bits = true; + } + if(!gen2_is_allowed_access( + target_data, block_num, MfClassicKeyTypeA, MfClassicActionKeyBWrite) && + !gen2_is_allowed_access( + target_data, block_num, MfClassicKeyTypeB, MfClassicActionKeyBWrite)) { + if(!gen2_can_reset_access_conditions(target_data, block_num)) { + can_write.locked_access_bits = true; + } + } + + return can_write; +} + +bool gen2_can_reset_access_conditions(const MfClassicData* target_data, uint8_t block_num) { + // Check whether it's possible to reset the access conditions + furi_assert(target_data); + + // Check rules: + // 1. Check if the sector trailer for this block is read + // 2. Check if we have any of the keys + // 3. For each key, check if we can write the access conditions + // 3.1. If none of the keys can write the access conditions, return false + + bool can_reset = false; + + bool has_key_a = mf_classic_is_key_found( + target_data, mf_classic_get_sector_by_block(block_num), MfClassicKeyTypeA); + bool has_key_b = mf_classic_is_key_found( + target_data, mf_classic_get_sector_by_block(block_num), MfClassicKeyTypeB); + uint8_t sector_tr_num = mf_classic_get_sector_trailer_num_by_block(block_num); + + if(!mf_classic_is_block_read(target_data, sector_tr_num)) { + can_reset = false; + return can_reset; + } + + if(!has_key_a && !has_key_b) { + can_reset = false; + return can_reset; + } + if(gen2_is_allowed_access(target_data, block_num, MfClassicKeyTypeA, MfClassicActionACWrite) || + gen2_is_allowed_access(target_data, block_num, MfClassicKeyTypeB, MfClassicActionACWrite)) { + can_reset = true; + } + + return can_reset; +} + +MfClassicKeyType + gen2_poller_get_key_type_to_write(const MfClassicData* target_data, uint8_t block_num) { + // Get the key type to use for writing + // We assume that at least one of the keys can write the block + furi_assert(target_data); + + MfClassicKeyType key_type = MfClassicKeyTypeA; + + if(gen2_is_allowed_access( + target_data, block_num, MfClassicKeyTypeA, MfClassicActionDataWrite)) { + key_type = MfClassicKeyTypeA; + } else if(gen2_is_allowed_access( + target_data, block_num, MfClassicKeyTypeB, MfClassicActionDataWrite)) { + key_type = MfClassicKeyTypeB; + } + + return key_type; +} + +static bool gen2_is_allowed_access_sector_trailer( + const MfClassicData* data, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicAction action) { + uint8_t sector_num = mf_classic_get_sector_by_block(block_num); + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sector_num); + uint8_t* access_bits_arr = sec_tr->access_bits.data; + uint8_t AC = ((access_bits_arr[1] >> 5) & 0x04) | ((access_bits_arr[2] >> 2) & 0x02) | + ((access_bits_arr[2] >> 7) & 0x01); + FURI_LOG_T("NFC", "AC: %02X", AC); + + switch(action) { + case MfClassicActionKeyARead: { + return false; + } + case MfClassicActionKeyAWrite: + case MfClassicActionKeyBWrite: { + return ( + (key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x01)) || + (key_type == MfClassicKeyTypeB && + (AC == 0x00 || AC == 0x04 || AC == 0x03 || AC == 0x01))); + } + case MfClassicActionKeyBRead: { + return (key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x02 || AC == 0x01)) || + (key_type == MfClassicKeyTypeB && (AC == 0x00 || AC == 0x02 || AC == 0x01)); + } + case MfClassicActionACRead: { + return ((key_type == MfClassicKeyTypeA) || (key_type == MfClassicKeyTypeB)); + } + case MfClassicActionACWrite: { + return ( + (key_type == MfClassicKeyTypeA && (AC == 0x01)) || + (key_type == MfClassicKeyTypeB && (AC == 0x01 || AC == 0x03 || AC == 0x05))); + } + default: + return false; + } + return true; +} + +bool gen2_is_allowed_access_data_block( + MfClassicSectorTrailer* sec_tr, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicAction action) { + // Same as mf_classic_is_allowed_access_data_block but with sector 0 allowed + furi_assert(sec_tr); + + uint8_t* access_bits_arr = sec_tr->access_bits.data; + + uint8_t sector_block = 0; + if(block_num <= 128) { + sector_block = block_num & 0x03; + } else { + sector_block = (block_num & 0x0f) / 5; + } + + uint8_t AC; + switch(sector_block) { + case 0x00: { + AC = ((access_bits_arr[1] >> 2) & 0x04) | ((access_bits_arr[2] << 1) & 0x02) | + ((access_bits_arr[2] >> 4) & 0x01); + break; + } + case 0x01: { + AC = ((access_bits_arr[1] >> 3) & 0x04) | ((access_bits_arr[2] >> 0) & 0x02) | + ((access_bits_arr[2] >> 5) & 0x01); + break; + } + case 0x02: { + AC = ((access_bits_arr[1] >> 4) & 0x04) | ((access_bits_arr[2] >> 1) & 0x02) | + ((access_bits_arr[2] >> 6) & 0x01); + break; + } + default: + return false; + } + + switch(action) { + case MfClassicActionDataRead: { + return ( + (key_type == MfClassicKeyTypeA && !(AC == 0x03 || AC == 0x05 || AC == 0x07)) || + (key_type == MfClassicKeyTypeB && !(AC == 0x07))); + } + case MfClassicActionDataWrite: { + return ( + (key_type == MfClassicKeyTypeA && (AC == 0x00)) || + (key_type == MfClassicKeyTypeB && + (AC == 0x00 || AC == 0x04 || AC == 0x06 || AC == 0x03))); + } + case MfClassicActionDataInc: { + return ( + (key_type == MfClassicKeyTypeA && (AC == 0x00)) || + (key_type == MfClassicKeyTypeB && (AC == 0x00 || AC == 0x06))); + } + case MfClassicActionDataDec: { + return ( + (key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x06 || AC == 0x01)) || + (key_type == MfClassicKeyTypeB && (AC == 0x00 || AC == 0x06 || AC == 0x01))); + } + default: + return false; + } + + return false; +} + +bool gen2_is_allowed_access( + const MfClassicData* data, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicAction action) { + // Same as mf_classic_is_allowed_access but with sector 0 allowed + furi_assert(data); + + bool access_allowed = false; + if(mf_classic_is_sector_trailer(block_num)) { + access_allowed = gen2_is_allowed_access_sector_trailer(data, block_num, key_type, action); + } else { + uint8_t sector_num = mf_classic_get_sector_by_block(block_num); + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sector_num); + access_allowed = gen2_is_allowed_access_data_block(sec_tr, block_num, key_type, action); + } + + return access_allowed; +} \ No newline at end of file diff --git a/nfc_magic/magic/protocols/gen2/gen2_poller_i.h b/nfc_magic/magic/protocols/gen2/gen2_poller_i.h new file mode 100644 index 00000000000..f30f665fa6f --- /dev/null +++ b/nfc_magic/magic/protocols/gen2/gen2_poller_i.h @@ -0,0 +1,139 @@ +#pragma once + +#include "gen2_poller.h" +#include +#include "crypto1.h" // TODO: Move to a better home +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define GEN2_CMD_READ_ATS (0xE0) +#define GEN2_FSDI_256 (0x8U) + +#define GEN2_POLLER_BLOCK_SIZE (16) + +#define GEN2_POLLER_MAX_BUFFER_SIZE (64U) +#define GEN2_POLLER_MAX_FWT (150000U) + +typedef enum { + Gen2PollerStateIdle, + Gen2PollerStateRequestMode, + Gen2PollerStateWipe, + Gen2PollerStateWriteSourceDataRequest, + Gen2PollerStateWriteTargetDataRequest, + Gen2PollerStateWrite, + Gen2PollerStateSuccess, + Gen2PollerStateFail, + + Gen2PollerStateNum, +} Gen2PollerState; + +typedef enum { + Gen2AuthStateIdle, + Gen2AuthStatePassed, +} Gen2AuthState; + +typedef enum { + Gen2CardStateDetected, + Gen2CardStateLost, +} Gen2CardState; + +typedef struct { + MfClassicData* mfc_data_source; + MfClassicData* mfc_data_target; + MfClassicKey auth_key; + MfClassicKeyType read_key; + MfClassicKeyType write_key; + uint16_t current_block; + bool need_halt_before_write; +} Gen2PollerWriteContext; + +typedef union { + Gen2PollerWriteContext write_ctx; +} Gen2PollerModeContext; + +struct Gen2Poller { + Nfc* nfc; + Gen2PollerState state; + + NfcPoller* poller; + Iso14443_3aPoller* iso3_poller; + + Gen2AuthState auth_state; + Gen2CardState card_state; + + Gen2PollerModeContext mode_ctx; + Gen2PollerMode mode; + + Crypto1* crypto; + BitBuffer* tx_plain_buffer; + BitBuffer* tx_encrypted_buffer; + BitBuffer* rx_plain_buffer; + BitBuffer* rx_encrypted_buffer; + MfClassicData* data; + + Gen2PollerEvent gen2_event; + Gen2PollerEventData gen2_event_data; + + Gen2PollerCallback callback; + void* context; +}; + +typedef struct { + uint8_t block; + MfClassicKeyType key_type; + MfClassicNt nt; +} Gen2CollectNtContext; + +typedef struct { + uint8_t block_num; + MfClassicKey key; + MfClassicKeyType key_type; + MfClassicBlock block; +} Gen2ReadBlockContext; + +typedef struct { + uint8_t block_num; + MfClassicKey key; + MfClassicKeyType key_type; + MfClassicBlock block; +} Gen2WriteBlockContext; + +Gen2PollerError gen2_poller_write(Gen2Poller* instance); + +Gen2PollerError gen2_poller_auth( + Gen2Poller* instance, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicAuthContext* data); + +Gen2PollerError gen2_poller_halt(Gen2Poller* instance); + +Gen2PollerError + gen2_poller_write_block(Gen2Poller* instance, uint8_t block_num, const MfClassicBlock* data); + +MfClassicKeyType + gen2_poller_get_key_type_to_write(const MfClassicData* mfc_data, uint8_t block_num); + +bool gen2_poller_can_write_block(const MfClassicData* mfc_data, uint8_t block_num); + +bool gen2_can_reset_access_conditions(const MfClassicData* mfc_data, uint8_t block_num); + +Gen2PollerWriteProblems + gen2_poller_can_write_data_block(const MfClassicData* mfc_data, uint8_t block_num); + +Gen2PollerWriteProblems + gen2_poller_can_write_sector_trailer(const MfClassicData* mfc_data, uint8_t block_num); + +bool gen2_is_allowed_access( + const MfClassicData* data, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicAction action); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/nfc_magic/lib/magic/protocols/gen4/gen4.c b/nfc_magic/magic/protocols/gen4/gen4.c similarity index 72% rename from nfc_magic/lib/magic/protocols/gen4/gen4.c rename to nfc_magic/magic/protocols/gen4/gen4.c index 5af365aed56..acf75cd6e71 100644 --- a/nfc_magic/lib/magic/protocols/gen4/gen4.c +++ b/nfc_magic/magic/protocols/gen4/gen4.c @@ -7,23 +7,35 @@ Gen4* gen4_alloc() { } void gen4_free(Gen4* instance) { - furi_check(instance != NULL); + furi_check(instance); free(instance); } void gen4_reset(Gen4* instance) { - furi_check(instance != NULL); + furi_check(instance); memset(&instance->config, 0, sizeof(Gen4Config)); memset(&instance->revision, 0, sizeof(Gen4Revision)); } void gen4_copy(Gen4* dest, const Gen4* source) { - furi_check(dest != NULL); - furi_check(source != NULL); + furi_check(dest); + furi_check(source); memcpy(dest, source, sizeof(Gen4)); } -char* gen4_get_shadow_mode_name(Gen4ShadowMode mode) { +bool gen4_password_is_set(const Gen4Password* instance) { + return (instance->bytes[0] || instance->bytes[1] || instance->bytes[2] || instance->bytes[3]); +} + +void gen4_password_reset(Gen4Password* instance) { + memset(instance->bytes, 0, GEN4_PASSWORD_LEN); +} + +void gen4_password_copy(Gen4Password* dest, const Gen4Password* source) { + memcpy(dest->bytes, source->bytes, GEN4_PASSWORD_LEN); +} + +const char* gen4_get_shadow_mode_name(Gen4ShadowMode mode) { switch(mode) { case Gen4ShadowModePreWrite: return "Pre-Write"; @@ -40,7 +52,7 @@ char* gen4_get_shadow_mode_name(Gen4ShadowMode mode) { } } -char* gen4_get_direct_write_mode_name(Gen4DirectWriteBlock0Mode mode) { +const char* gen4_get_direct_write_mode_name(Gen4DirectWriteBlock0Mode mode) { switch(mode) { case Gen4DirectWriteBlock0ModeEnabled: return "Enabled"; @@ -53,7 +65,7 @@ char* gen4_get_direct_write_mode_name(Gen4DirectWriteBlock0Mode mode) { } } -char* gen4_get_uid_len_num(Gen4UIDLength code) { +const char* gen4_get_uid_len_num(Gen4UIDLength code) { switch(code) { case Gen4UIDLengthSingle: return "4"; @@ -66,7 +78,7 @@ char* gen4_get_uid_len_num(Gen4UIDLength code) { } } -char* gen4_get_configuration_name(const Gen4Config* config) { +const char* gen4_get_configuration_name(const Gen4Config* config) { switch(config->data_parsed.protocol) { case Gen4ProtocolMfClassic: { switch(config->data_parsed.total_blocks) { diff --git a/nfc_magic/lib/magic/protocols/gen4/gen4.h b/nfc_magic/magic/protocols/gen4/gen4.h similarity index 80% rename from nfc_magic/lib/magic/protocols/gen4/gen4.h rename to nfc_magic/magic/protocols/gen4/gen4.h index 959e407bd5f..054a95a39fb 100644 --- a/nfc_magic/lib/magic/protocols/gen4/gen4.h +++ b/nfc_magic/magic/protocols/gen4/gen4.h @@ -1,5 +1,6 @@ #pragma once +#include "core/common_defines.h" #include #define GEN4_CONFIG_SIZE (32) @@ -15,8 +16,7 @@ typedef enum { Gen4ProtocolMfUltralight = 0x01, } Gen4Protocol; -typedef union { - uint32_t value; +typedef struct { uint8_t bytes[GEN4_PASSWORD_LEN]; } Gen4Password; @@ -57,7 +57,6 @@ typedef enum { typedef union { uint8_t data_raw[GEN4_CONFIG_SIZE]; -#pragma pack(push, 1) struct { Gen4Protocol protocol; Gen4UIDLength uid_len_code; @@ -71,8 +70,7 @@ typedef union { uint8_t total_blocks; Gen4DirectWriteBlock0Mode direct_write_mode; uint8_t crc[GEN4_CRC_LEN]; - } data_parsed; -#pragma pack(pop) + } FURI_PACKED data_parsed; } Gen4Config; typedef struct { @@ -92,10 +90,16 @@ void gen4_reset(Gen4* instance); void gen4_copy(Gen4* dest, const Gen4* source); -char* gen4_get_shadow_mode_name(Gen4ShadowMode mode); +bool gen4_password_is_set(const Gen4Password* instance); + +void gen4_password_reset(Gen4Password* instance); + +void gen4_password_copy(Gen4Password* dest, const Gen4Password* source); + +const char* gen4_get_shadow_mode_name(Gen4ShadowMode mode); -char* gen4_get_direct_write_mode_name(Gen4DirectWriteBlock0Mode mode); +const char* gen4_get_direct_write_mode_name(Gen4DirectWriteBlock0Mode mode); -char* gen4_get_uid_len_num(Gen4UIDLength code); +const char* gen4_get_uid_len_num(Gen4UIDLength code); -char* gen4_get_configuration_name(const Gen4Config* config); \ No newline at end of file +const char* gen4_get_configuration_name(const Gen4Config* config); \ No newline at end of file diff --git a/nfc_magic/lib/magic/protocols/gen4/gen4_poller.c b/nfc_magic/magic/protocols/gen4/gen4_poller.c similarity index 81% rename from nfc_magic/lib/magic/protocols/gen4/gen4_poller.c rename to nfc_magic/magic/protocols/gen4/gen4_poller.c index 1ead4178fd9..165996546b5 100644 --- a/nfc_magic/lib/magic/protocols/gen4/gen4_poller.c +++ b/nfc_magic/magic/protocols/gen4/gen4_poller.c @@ -1,7 +1,8 @@ #include "bit_buffer.h" +#include "core/check.h" #include "gen4_poller_i.h" -#include "protocols/gen4/gen4.h" -#include "protocols/gen4/gen4_poller.h" +#include "magic/protocols/gen4/gen4.h" +#include "magic/protocols/gen4/gen4_poller.h" #include #include #include @@ -202,8 +203,7 @@ NfcCommand gen4_poller_idle_handler(Gen4Poller* instance) { NfcCommand command = NfcCommandContinue; instance->current_block = 0; - //TODO: FOR WHAT? - //memset(instance->config, 0, sizeof(instance->config)); + instance->gen4_event.type = Gen4PollerEventTypeCardDetected; command = instance->callback(instance->gen4_event, instance->context); instance->state = Gen4PollerStateRequestMode; @@ -254,7 +254,7 @@ NfcCommand gen4_poller_wipe_handler(Gen4Poller* instance) { instance->state = Gen4PollerStateFail; break; } - instance->password.value = 0; + gen4_password_reset(&instance->password); error = gen4_poller_write_block( instance, instance->password, instance->current_block, gen4_poller_default_block_0); if(error != Gen4PollerErrorNone) { @@ -378,15 +378,25 @@ static NfcCommand gen4_poller_write_mf_ultralight(Gen4Poller* instance) { case MfUltralightTypeNTAGI2C2K: case MfUltralightTypeNTAGI2CPlus1K: case MfUltralightTypeNTAGI2CPlus2K: + FURI_LOG_D(TAG, "NTAG type"); instance->config.data_parsed.mfu_mode = Gen4UltralightModeNTAG; instance->total_blocks = 64 * 2; break; + case MfUltralightTypeUnknown: + FURI_LOG_D(TAG, "Ultralight type"); + instance->config.data_parsed.mfu_mode = Gen4UltralightModeUL; + break; + + case MfUltralightTypeMfulC: + FURI_LOG_D(TAG, "MfulC type"); + instance->config.data_parsed.mfu_mode = Gen4UltralightModeUL_C; + break; + case MfUltralightTypeUL11: case MfUltralightTypeUL21: - // UL-C? - // UL? default: + FURI_LOG_D(TAG, "EV1 type"); instance->config.data_parsed.mfu_mode = Gen4UltralightModeUL_EV1; break; } @@ -405,7 +415,6 @@ static NfcCommand gen4_poller_write_mf_ultralight(Gen4Poller* instance) { instance->config.data_parsed.atqa[0] = iso3_data->atqa[0]; instance->config.data_parsed.atqa[1] = iso3_data->atqa[1]; instance->config.data_parsed.sak = iso3_data->sak; - //instance->config.data_parsed.mfu_mode = Gen4UltralightModeUL_EV1; instance->config.data_parsed.total_blocks = instance->total_blocks - 1; instance->config.data_parsed.direct_write_mode = Gen4DirectWriteBlock0ModeDisabled; @@ -435,81 +444,104 @@ static NfcCommand gen4_poller_write_mf_ultralight(Gen4Poller* instance) { } else { uint8_t block[GEN4_POLLER_BLOCK_SIZE] = {}; bool write_success = true; - for(size_t i = 0; i < 8; i++) { - memcpy(block, &mfu_data->signature.data[i * 4], 4); //-V1086 - Gen4PollerError error = - gen4_poller_write_block(instance, instance->password, 0xF2 + i, block); - if(error != Gen4PollerErrorNone) { - write_success = false; + + if(mf_ultralight_support_feature( + mf_ultralight_get_feature_support_set(mfu_data->type), + MfUltralightFeatureSupportReadSignature)) { + FURI_LOG_D(TAG, "Writing Signature"); + for(size_t i = 0; i < 8; i++) { + memcpy(block, &mfu_data->signature.data[i * 4], 4); //-V1086 + Gen4PollerError error = + gen4_poller_write_block(instance, instance->password, 0xF2 + i, block); + if(error != Gen4PollerErrorNone) { + write_success = false; + break; + } + } + if(!write_success) { + FURI_LOG_E(TAG, "Failed to write Signature"); + instance->state = Gen4PollerStateFail; break; } - } - if(!write_success) { - FURI_LOG_E(TAG, "Failed to write Signature"); - instance->state = Gen4PollerStateFail; - break; - } - - block[0] = mfu_data->version.header; - block[1] = mfu_data->version.vendor_id; - block[2] = mfu_data->version.prod_type; - block[3] = mfu_data->version.prod_subtype; - Gen4PollerError error = - gen4_poller_write_block(instance, instance->password, 0xFA, block); - if(error != Gen4PollerErrorNone) { - FURI_LOG_E(TAG, "Failed to write 1st part Version"); - instance->state = Gen4PollerStateFail; - break; - } - - block[0] = mfu_data->version.prod_ver_major; - block[1] = mfu_data->version.prod_ver_minor; - block[2] = mfu_data->version.storage_size; - block[3] = mfu_data->version.protocol_type; - error = gen4_poller_write_block(instance, instance->password, 0xFB, block); - if(error != Gen4PollerErrorNone) { - FURI_LOG_E(TAG, "Failed to write 2nd part Version"); - instance->state = Gen4PollerStateFail; - break; + } else { + FURI_LOG_D(TAG, "Signature is not supported, skipping"); } - // Password - MfUltralightConfigPages* config_pages = NULL; - mf_ultralight_get_config_page(mfu_data, &config_pages); + if(mf_ultralight_support_feature( + mf_ultralight_get_feature_support_set(mfu_data->type), + MfUltralightFeatureSupportReadVersion)) { + FURI_LOG_D(TAG, "Writing Version part 1"); + block[0] = mfu_data->version.header; + block[1] = mfu_data->version.vendor_id; + block[2] = mfu_data->version.prod_type; + block[3] = mfu_data->version.prod_subtype; + Gen4PollerError error = + gen4_poller_write_block(instance, instance->password, 0xFA, block); + if(error != Gen4PollerErrorNone) { + FURI_LOG_E(TAG, "Failed to write 1st part Version"); + instance->state = Gen4PollerStateFail; + break; + } - block[0] = config_pages->password.data[0]; - block[1] = config_pages->password.data[1]; - block[2] = config_pages->password.data[2]; - block[3] = config_pages->password.data[3]; - error = gen4_poller_write_block(instance, instance->password, 0xE5, block); - if(error != Gen4PollerErrorNone) { - FURI_LOG_E(TAG, "Failed to write Password to sector E5"); - instance->state = Gen4PollerStateFail; - break; - } - error = gen4_poller_write_block(instance, instance->password, 0xF0, block); - if(error != Gen4PollerErrorNone) { - FURI_LOG_E(TAG, "Failed to write Password to sector F0"); - instance->state = Gen4PollerStateFail; - break; + FURI_LOG_D(TAG, "Writing Version part 2"); + block[0] = mfu_data->version.prod_ver_major; + block[1] = mfu_data->version.prod_ver_minor; + block[2] = mfu_data->version.storage_size; + block[3] = mfu_data->version.protocol_type; + error = gen4_poller_write_block(instance, instance->password, 0xFB, block); + if(error != Gen4PollerErrorNone) { + FURI_LOG_E(TAG, "Failed to write 2nd part Version"); + instance->state = Gen4PollerStateFail; + break; + } + } else { + FURI_LOG_D(TAG, "Version is not supported, skipping"); } - // PACK - block[0] = config_pages->pack.data[0]; - block[1] = config_pages->pack.data[1]; - block[2] = 0x00; - block[3] = 0x00; - error = gen4_poller_write_block(instance, instance->password, 0xE6, block); - if(error != Gen4PollerErrorNone) { - FURI_LOG_E(TAG, "Failed to write PACK to sector E6"); - instance->state = Gen4PollerStateFail; - break; - } - error = gen4_poller_write_block(instance, instance->password, 0xF1, block); - if(error != Gen4PollerErrorNone) { - FURI_LOG_E(TAG, "Failed to write PACK to sector F1"); - instance->state = Gen4PollerStateFail; - break; + if(mf_ultralight_support_feature( + mf_ultralight_get_feature_support_set(mfu_data->type), + MfUltralightFeatureSupportPasswordAuth)) { + FURI_LOG_D(TAG, "Writing Password"); + MfUltralightConfigPages* config_pages = NULL; + if(mf_ultralight_get_config_page(mfu_data, &config_pages)) { + block[0] = config_pages->password.data[0]; + block[1] = config_pages->password.data[1]; + block[2] = config_pages->password.data[2]; + block[3] = config_pages->password.data[3]; + Gen4PollerError error = + gen4_poller_write_block(instance, instance->password, 0xE5, block); + if(error != Gen4PollerErrorNone) { + FURI_LOG_E(TAG, "Failed to write Password to sector E5"); + instance->state = Gen4PollerStateFail; + break; + } + error = gen4_poller_write_block(instance, instance->password, 0xF0, block); + if(error != Gen4PollerErrorNone) { + FURI_LOG_E(TAG, "Failed to write Password to sector F0"); + instance->state = Gen4PollerStateFail; + break; + } + + FURI_LOG_D(TAG, "Writing PACK"); + block[0] = config_pages->pack.data[0]; + block[1] = config_pages->pack.data[1]; + block[2] = 0x00; + block[3] = 0x00; + error = gen4_poller_write_block(instance, instance->password, 0xE6, block); + if(error != Gen4PollerErrorNone) { + FURI_LOG_E(TAG, "Failed to write PACK to sector E6"); + instance->state = Gen4PollerStateFail; + break; + } + error = gen4_poller_write_block(instance, instance->password, 0xF1, block); + if(error != Gen4PollerErrorNone) { + FURI_LOG_E(TAG, "Failed to write PACK to sector F1"); + instance->state = Gen4PollerStateFail; + break; + } + } + } else { + FURI_LOG_D(TAG, "Password is not supported, skipping"); } instance->state = Gen4PollerStateSuccess; @@ -769,3 +801,20 @@ void gen4_poller_stop(Gen4Poller* instance) { nfc_poller_stop(instance->poller); } + +const Gen4* gen4_poller_get_gen4_data(const Gen4Poller* instance) { + furi_assert(instance); + return instance->gen4_data; +} + +void gen4_poller_struct_set_direct_write_block_0_mode( + Gen4Poller* instance, + Gen4DirectWriteBlock0Mode mode) { + furi_assert(instance); + instance->direct_write_block_0_mode = mode; +} + +void gen4_poller_struct_set_shadow_mode(Gen4Poller* instance, Gen4ShadowMode mode) { + furi_assert(instance); + instance->shadow_mode = mode; +} \ No newline at end of file diff --git a/nfc_magic/lib/magic/protocols/gen4/gen4_poller.h b/nfc_magic/magic/protocols/gen4/gen4_poller.h similarity index 88% rename from nfc_magic/lib/magic/protocols/gen4/gen4_poller.h rename to nfc_magic/magic/protocols/gen4/gen4_poller.h index 490602e60d7..cbad69867e1 100644 --- a/nfc_magic/lib/magic/protocols/gen4/gen4_poller.h +++ b/nfc_magic/magic/protocols/gen4/gen4_poller.h @@ -1,6 +1,6 @@ #pragma once -#include "protocols/gen4/gen4.h" +#include "gen4.h" #include #include #include @@ -88,6 +88,14 @@ void gen4_poller_start(Gen4Poller* instance, Gen4PollerCallback callback, void* void gen4_poller_stop(Gen4Poller* instance); +const Gen4* gen4_poller_get_gen4_data(const Gen4Poller* instance); + +void gen4_poller_struct_set_direct_write_block_0_mode( + Gen4Poller* instance, + Gen4DirectWriteBlock0Mode mode); + +void gen4_poller_struct_set_shadow_mode(Gen4Poller* instance, Gen4ShadowMode mode); + #ifdef __cplusplus } #endif diff --git a/nfc_magic/lib/magic/protocols/gen4/gen4_poller_i.c b/nfc_magic/magic/protocols/gen4/gen4_poller_i.c similarity index 95% rename from nfc_magic/lib/magic/protocols/gen4/gen4_poller_i.c rename to nfc_magic/magic/protocols/gen4/gen4_poller_i.c index 56709b13a97..4111f429bc6 100644 --- a/nfc_magic/lib/magic/protocols/gen4/gen4_poller_i.c +++ b/nfc_magic/magic/protocols/gen4/gen4_poller_i.c @@ -1,8 +1,7 @@ #include "gen4_poller_i.h" #include "bit_buffer.h" -#include "protocols/gen4/gen4.h" -#include "protocols/gen4/gen4_poller.h" +#include "magic/protocols/gen4/gen4_poller.h" #include #define GEN4_CMD_PREFIX (0xCF) @@ -256,9 +255,16 @@ Gen4PollerError gen4_poller_change_password( FURI_LOG_D( TAG, - "Trying to change password from 0x%08lX to 0x%08lX. Card response: 0x%02X", - pwd_current.value, - pwd_new.value, + "Trying to change password from 0x%02X %02X %02X %02X to " + "0x%02X %02X %02X %02X. Card response: 0x%02X", + pwd_current.bytes[0], + pwd_current.bytes[1], + pwd_current.bytes[2], + pwd_current.bytes[3], + pwd_new.bytes[0], + pwd_new.bytes[1], + pwd_new.bytes[2], + pwd_new.bytes[3], response); if(response != GEN4_RESPONSE_SUCCESS) { diff --git a/nfc_magic/lib/magic/protocols/gen4/gen4_poller_i.h b/nfc_magic/magic/protocols/gen4/gen4_poller_i.h similarity index 99% rename from nfc_magic/lib/magic/protocols/gen4/gen4_poller_i.h rename to nfc_magic/magic/protocols/gen4/gen4_poller_i.h index ad3049a4313..d7e993bab3c 100644 --- a/nfc_magic/lib/magic/protocols/gen4/gen4_poller_i.h +++ b/nfc_magic/magic/protocols/gen4/gen4_poller_i.h @@ -1,6 +1,5 @@ #pragma once -#include "gen4.h" #include "gen4_poller.h" #include #include diff --git a/nfc_magic/lib/magic/protocols/nfc_magic_protocols.c b/nfc_magic/magic/protocols/nfc_magic_protocols.c similarity index 64% rename from nfc_magic/lib/magic/protocols/nfc_magic_protocols.c rename to nfc_magic/magic/protocols/nfc_magic_protocols.c index 46c3d0b62a1..41c27269980 100644 --- a/nfc_magic/lib/magic/protocols/nfc_magic_protocols.c +++ b/nfc_magic/magic/protocols/nfc_magic_protocols.c @@ -3,8 +3,10 @@ #include static const char* nfc_magic_protocol_names[NfcMagicProtocolNum] = { - [NfcMagicProtocolGen1] = "Classic Gen 1A/B", - [NfcMagicProtocolGen4] = "Gen 4 GTU", + [NfcMagicProtocolGen1] = "Gen1A/B", + [NfcMagicProtocolGen2] = "Gen2", + [NfcMagicProtocolClassic] = "MIFARE Classic", + [NfcMagicProtocolGen4] = "Gen4 GTU", }; const char* nfc_magic_protocols_get_name(NfcMagicProtocol protocol) { diff --git a/nfc_magic/lib/magic/protocols/nfc_magic_protocols.h b/nfc_magic/magic/protocols/nfc_magic_protocols.h similarity index 75% rename from nfc_magic/lib/magic/protocols/nfc_magic_protocols.h rename to nfc_magic/magic/protocols/nfc_magic_protocols.h index 6bf87ec4009..083b6fbb208 100644 --- a/nfc_magic/lib/magic/protocols/nfc_magic_protocols.h +++ b/nfc_magic/magic/protocols/nfc_magic_protocols.h @@ -6,7 +6,9 @@ extern "C" { typedef enum { NfcMagicProtocolGen1, + NfcMagicProtocolGen2, NfcMagicProtocolGen4, + NfcMagicProtocolClassic, // Last to give priority to the others NfcMagicProtocolNum, NfcMagicProtocolInvalid, diff --git a/nfc_magic/nfc_magic_app.c b/nfc_magic/nfc_magic_app.c index 410c4ed800f..34fca2b3e2d 100644 --- a/nfc_magic/nfc_magic_app.c +++ b/nfc_magic/nfc_magic_app.c @@ -1,5 +1,5 @@ #include "nfc_magic_app_i.h" -#include "protocols/gen4/gen4.h" +#include "magic/protocols/gen4/gen4.h" bool nfc_magic_app_custom_event_callback(void* context, uint32_t event) { furi_assert(context); @@ -49,13 +49,16 @@ NfcMagicApp* nfc_magic_app_alloc() { view_dispatcher_set_tick_event_callback( instance->view_dispatcher, nfc_magic_app_tick_event_callback, 100); - // Nfc device + // NFC source device (file) instance->source_dev = nfc_device_alloc(); nfc_device_set_loading_callback( instance->source_dev, nfc_magic_app_show_loading_popup, instance); instance->file_path = furi_string_alloc_set(NFC_APP_FOLDER); instance->file_name = furi_string_alloc(); + // NFC target device (tag) + instance->target_dev = nfc_device_alloc(); + // Open GUI record instance->gui = furi_record_open(RECORD_GUI); view_dispatcher_attach_to_gui( @@ -106,6 +109,20 @@ NfcMagicApp* nfc_magic_app_alloc() { instance->gen4_data = gen4_alloc(); + // Dict attack + instance->dict_attack = dict_attack_alloc(); + view_dispatcher_add_view( + instance->view_dispatcher, + NfcMagicAppViewDictAttack, + dict_attack_get_view(instance->dict_attack)); + + // Write problems + instance->write_problems = write_problems_alloc(); + view_dispatcher_add_view( + instance->view_dispatcher, + NfcMagicAppViewWriteProblems, + write_problems_get_view(instance->write_problems)); + instance->nfc = nfc_alloc(); instance->scanner = nfc_magic_scanner_alloc(instance->nfc); @@ -115,11 +132,14 @@ NfcMagicApp* nfc_magic_app_alloc() { void nfc_magic_app_free(NfcMagicApp* instance) { furi_assert(instance); - // Nfc device + // Nfc source device nfc_device_free(instance->source_dev); furi_string_free(instance->file_name); furi_string_free(instance->file_path); + // Nfc target device + nfc_device_free(instance->target_dev); + // Submenu view_dispatcher_remove_view(instance->view_dispatcher, NfcMagicAppViewMenu); submenu_free(instance->submenu); @@ -144,6 +164,14 @@ void nfc_magic_app_free(NfcMagicApp* instance) { view_dispatcher_remove_view(instance->view_dispatcher, NfcMagicAppViewWidget); widget_free(instance->widget); + // Dict attack + view_dispatcher_remove_view(instance->view_dispatcher, NfcMagicAppViewDictAttack); + dict_attack_free(instance->dict_attack); + + // Write problems + view_dispatcher_remove_view(instance->view_dispatcher, NfcMagicAppViewWriteProblems); + write_problems_free(instance->write_problems); + // View Dispatcher view_dispatcher_free(instance->view_dispatcher); diff --git a/nfc_magic/nfc_magic_app_i.h b/nfc_magic/nfc_magic_app_i.h index 9a8eb82ee3c..39fe2dfc39b 100644 --- a/nfc_magic/nfc_magic_app_i.h +++ b/nfc_magic/nfc_magic_app_i.h @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include @@ -28,16 +30,24 @@ #include #include +#include +#include -#include "lib/magic/nfc_magic_scanner.h" -#include "lib/magic/protocols/nfc_magic_protocols.h" -#include "lib/magic/protocols/gen1a/gen1a_poller.h" -#include "lib/magic/protocols/gen4/gen4_poller.h" +#include "magic/nfc_magic_scanner.h" +#include "magic/protocols/nfc_magic_protocols.h" +#include "magic/protocols/gen1a/gen1a_poller.h" +#include "magic/protocols/gen2/gen2_poller.h" +#include "magic/protocols/gen4/gen4_poller.h" + +#include "lib/nfc/protocols/mf_classic/mf_classic_poller.h" #define NFC_APP_FOLDER ANY_PATH("nfc") #define NFC_APP_EXTENSION ".nfc" #define NFC_APP_SHADOW_EXTENSION ".shd" +#define NFC_APP_MF_CLASSIC_DICT_USER_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict_user.nfc") +#define NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict.nfc") + #define NFC_MAGIC_APP_BYTE_INPUT_STORE_SIZE (4) enum NfcMagicAppCustomEvent { @@ -48,8 +58,33 @@ enum NfcMagicAppCustomEvent { NfcMagicAppCustomEventWorkerExit, NfcMagicAppCustomEventByteInputDone, NfcMagicAppCustomEventTextInputDone, + NfcMagicAppCustomEventCardDetected, + NfcMagicAppCustomEventCardLost, + NfcMagicAppCustomEventDictAttackDataUpdate, + NfcMagicAppCustomEventDictAttackComplete, + NfcMagicAppCustomEventDictAttackSkip, }; +typedef struct { + KeysDict* dict; + uint8_t sectors_total; + uint8_t sectors_read; + uint8_t current_sector; + uint8_t keys_found; + size_t dict_keys_total; + size_t dict_keys_current; + bool is_key_attack; + uint8_t key_attack_current_sector; + bool is_card_present; +} NfcMagicAppMfClassicDictAttackContext; + +typedef struct { + uint8_t problem_index; + uint8_t problem_index_abs; + uint8_t problems_total; + Gen2PollerWriteProblems problems; +} NfcMagicAppWriteProblemsContext; + struct NfcMagicApp { ViewDispatcher* view_dispatcher; Gui* gui; @@ -59,13 +94,19 @@ struct NfcMagicApp { SceneManager* scene_manager; NfcDevice* source_dev; + NfcDevice* target_dev; FuriString* file_name; FuriString* file_path; Nfc* nfc; NfcMagicProtocol protocol; NfcMagicScanner* scanner; + NfcPoller* poller; Gen1aPoller* gen1a_poller; + + Gen2Poller* gen2_poller; + bool gen2_poller_is_wipe_mode; + Gen4Poller* gen4_poller; Gen4* gen4_data; @@ -73,6 +114,11 @@ struct NfcMagicApp { Gen4Password gen4_password; Gen4Password gen4_password_new; + NfcMagicAppMfClassicDictAttackContext nfc_dict_context; + DictAttack* dict_attack; + NfcMagicAppWriteProblemsContext write_problems_context; + WriteProblems* write_problems; + FuriString* text_box_store; uint8_t byte_input_store[NFC_MAGIC_APP_BYTE_INPUT_STORE_SIZE]; @@ -92,6 +138,8 @@ typedef enum { NfcMagicAppViewTextInput, NfcMagicAppViewByteInput, NfcMagicAppViewWidget, + NfcMagicAppViewDictAttack, + NfcMagicAppViewWriteProblems, } NfcMagicAppView; void nfc_magic_app_blink_start(NfcMagicApp* nfc_magic); diff --git a/nfc_magic/scenes/nfc_magic_scene_config.h b/nfc_magic/scenes/nfc_magic_scene_config.h index 5b7fc624f1b..5f890586c6a 100644 --- a/nfc_magic/scenes/nfc_magic_scene_config.h +++ b/nfc_magic/scenes/nfc_magic_scene_config.h @@ -24,3 +24,8 @@ ADD_SCENE(nfc_magic, change_key, ChangeKey) ADD_SCENE(nfc_magic, change_key_fail, ChangeKeyFail) ADD_SCENE(nfc_magic, wrong_card, WrongCard) ADD_SCENE(nfc_magic, not_magic, NotMagic) +ADD_SCENE(nfc_magic, gen2_menu, Gen2Menu) +ADD_SCENE(nfc_magic, mf_classic_menu, MfClassicMenu) +ADD_SCENE(nfc_magic, mf_classic_dict_attack, MfClassicDictAttack) +ADD_SCENE(nfc_magic, gen2_write_check, Gen2WriteCheck) +ADD_SCENE(nfc_magic, mf_classic_write_check, MfClassicWriteCheck) \ No newline at end of file diff --git a/nfc_magic/scenes/nfc_magic_scene_file_select.c b/nfc_magic/scenes/nfc_magic_scene_file_select.c index a13b3e89311..20e40a420ca 100644 --- a/nfc_magic/scenes/nfc_magic_scene_file_select.c +++ b/nfc_magic/scenes/nfc_magic_scene_file_select.c @@ -30,6 +30,14 @@ static bool nfc_magic_scene_file_select_is_file_suitable(NfcMagicApp* instance) (mfu_type != MfUltralightTypeNTAGI2CPlus2K); } } + } else if(instance->protocol == NfcMagicProtocolGen2) { + if(protocol == NfcProtocolMfClassic) { + suitable = true; + } + } else if(instance->protocol == NfcMagicProtocolClassic) { + if(protocol == NfcProtocolMfClassic) { + suitable = true; + } } return suitable; @@ -40,7 +48,13 @@ void nfc_magic_scene_file_select_on_enter(void* context) { if(nfc_magic_load_from_file_select(instance)) { if(nfc_magic_scene_file_select_is_file_suitable(instance)) { - scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWriteConfirm); + if(instance->protocol == NfcMagicProtocolClassic || + instance->protocol == NfcMagicProtocolGen2) { + scene_manager_next_scene( + instance->scene_manager, NfcMagicSceneMfClassicDictAttack); + } else { + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWriteConfirm); + } } else { scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWrongCard); } diff --git a/nfc_magic/scenes/nfc_magic_scene_gen2_menu.c b/nfc_magic/scenes/nfc_magic_scene_gen2_menu.c new file mode 100644 index 00000000000..c28c79c3a8e --- /dev/null +++ b/nfc_magic/scenes/nfc_magic_scene_gen2_menu.c @@ -0,0 +1,55 @@ +#include "../nfc_magic_app_i.h" + +enum SubmenuIndex { + SubmenuIndexWrite, + SubmenuIndexWipe, +}; + +void nfc_magic_scene_gen2_menu_submenu_callback(void* context, uint32_t index) { + NfcMagicApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, index); +} + +void nfc_magic_scene_gen2_menu_on_enter(void* context) { + NfcMagicApp* instance = context; + + Submenu* submenu = instance->submenu; + submenu_add_item( + submenu, "Write", SubmenuIndexWrite, nfc_magic_scene_gen2_menu_submenu_callback, instance); + submenu_add_item( + submenu, "Wipe", SubmenuIndexWipe, nfc_magic_scene_gen2_menu_submenu_callback, instance); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneGen2Menu)); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewMenu); +} + +bool nfc_magic_scene_gen2_menu_on_event(void* context, SceneManagerEvent event) { + NfcMagicApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexWrite) { + instance->gen2_poller_is_wipe_mode = false; + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneFileSelect); + consumed = true; + } else if(event.event == SubmenuIndexWipe) { + instance->gen2_poller_is_wipe_mode = true; + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneMfClassicDictAttack); + consumed = true; + } + scene_manager_set_scene_state(instance->scene_manager, NfcMagicSceneGen2Menu, event.event); + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcMagicSceneStart); + } + + return consumed; +} + +void nfc_magic_scene_gen2_menu_on_exit(void* context) { + NfcMagicApp* instance = context; + + submenu_reset(instance->submenu); +} diff --git a/nfc_magic/scenes/nfc_magic_scene_gen2_write_check.c b/nfc_magic/scenes/nfc_magic_scene_gen2_write_check.c new file mode 100644 index 00000000000..11a38ec54ea --- /dev/null +++ b/nfc_magic/scenes/nfc_magic_scene_gen2_write_check.c @@ -0,0 +1,130 @@ +#include "../nfc_magic_app_i.h" + +void nfc_magic_scene_gen2_write_check_view_callback(WriteProblemsEvent event, void* context) { + NfcMagicApp* instance = context; + NfcMagicAppWriteProblemsContext* problems_context = &instance->write_problems_context; + + if(event == WriteProblemsEventCenterPressed) { + if(problems_context->problem_index == problems_context->problems_total - 1) { + // Continue to the next scene + if(instance->gen2_poller_is_wipe_mode) { + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWipe); + } else { + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWrite); + } + } else { + // Move to the next problem + problems_context->problem_index++; + problems_context->problem_index_abs++; + write_problems_set_problem_index( + instance->write_problems, problems_context->problem_index); + + for(uint8_t i = problems_context->problem_index_abs; + i < GEN2_POLLER_WRITE_PROBLEMS_LEN; + i++) { + if(problems_context->problems.all_problems & (1 << i)) { + write_problems_set_content(instance->write_problems, gen2_problem_strings[i]); + problems_context->problem_index_abs = i; + break; + } + } + } + } else if(event == WriteProblemsEventLeftPressed) { + if(problems_context->problem_index == 0) { + // Exit to the previous scene + scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcMagicSceneMfClassicMenu); + } else { + // Move to the previous problem + problems_context->problem_index--; + problems_context->problem_index_abs--; + write_problems_set_problem_index( + instance->write_problems, problems_context->problem_index); + + for(uint8_t i = problems_context->problem_index_abs; + i < GEN2_POLLER_WRITE_PROBLEMS_LEN; + i--) { + if(problems_context->problems.all_problems & (1 << i)) { + write_problems_set_content(instance->write_problems, gen2_problem_strings[i]); + problems_context->problem_index_abs = i; + break; + } + } + } + } +} + +void nfc_magic_scene_gen2_write_check_on_enter(void* context) { + NfcMagicApp* instance = context; + + Gen2PollerWriteProblems problems = gen2_poller_check_target_problems(instance->target_dev); + if(!instance->gen2_poller_is_wipe_mode) { + problems.all_problems |= + gen2_poller_check_source_problems(instance->source_dev).all_problems; + } + + WriteProblems* write_problems = instance->write_problems; + uint8_t problems_count = 0; + uint8_t current_problem = 0; + furi_assert(!problems.no_data, "No MFC data in nfc device"); + + if(problems.all_problems == 0) { + if(instance->gen2_poller_is_wipe_mode) { + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWipe); + return; + } else { + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWrite); + return; + } + } + + // Count the total number of problems + for(uint8_t i = 0; i < GEN2_POLLER_WRITE_PROBLEMS_LEN; i++) { + if(problems.all_problems & (1 << i)) { + problems_count++; + } + } + + // Init the view + write_problems_set_callback( + write_problems, nfc_magic_scene_gen2_write_check_view_callback, instance); + write_problems_set_problems_total(write_problems, problems_count); + write_problems_set_problem_index(write_problems, current_problem); + + // Set the initial content to the first problem + for(uint8_t i = 0; i < GEN2_POLLER_WRITE_PROBLEMS_LEN; i++) { + if(problems.all_problems & (1 << i)) { + write_problems_set_content(write_problems, gen2_problem_strings[i]); + current_problem = i; + break; + } + } + + // Save the context + instance->write_problems_context.problem_index = current_problem; + instance->write_problems_context.problems_total = problems_count; + instance->write_problems_context.problems = problems; + + // Setup and start worker + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWriteProblems); +} + +bool nfc_magic_scene_gen2_write_check_on_event(void* context, SceneManagerEvent event) { + NfcMagicApp* instance = context; + UNUSED(event); + UNUSED(context); + UNUSED(instance); + bool consumed = false; + + return consumed; +} + +void nfc_magic_scene_gen2_write_check_on_exit(void* context) { + NfcMagicApp* instance = context; + + instance->write_problems_context.problem_index = 0; + instance->write_problems_context.problems_total = 0; + instance->write_problems_context.problems.all_problems = 0; + + write_problems_reset(instance->write_problems); +} diff --git a/nfc_magic/scenes/nfc_magic_scene_gen4_get_info.c b/nfc_magic/scenes/nfc_magic_scene_gen4_get_info.c index 91ced30792d..8cda9a0e493 100644 --- a/nfc_magic/scenes/nfc_magic_scene_gen4_get_info.c +++ b/nfc_magic/scenes/nfc_magic_scene_gen4_get_info.c @@ -1,7 +1,4 @@ #include "../nfc_magic_app_i.h" -//TODO: INCAPSULATE? -#include "gui/scene_manager.h" -#include "protocols/gen4/gen4_poller_i.h" enum { NfcMagicSceneGen4GetInfoStateCardSearch, @@ -21,8 +18,7 @@ NfcCommand nfc_mafic_scene_gen4_get_info_poller_callback(Gen4PollerEvent event, event.data->request_mode.mode = Gen4PollerModeGetInfo; } else if(event.type == Gen4PollerEventTypeSuccess) { // Copy data from event to main instance - // TODO: From event data? or from poller? - gen4_copy(instance->gen4_data, instance->gen4_poller->gen4_data); + gen4_copy(instance->gen4_data, gen4_poller_get_gen4_data(instance->gen4_poller)); view_dispatcher_send_custom_event( instance->view_dispatcher, NfcMagicCustomEventWorkerSuccess); diff --git a/nfc_magic/scenes/nfc_magic_scene_gen4_menu.c b/nfc_magic/scenes/nfc_magic_scene_gen4_menu.c index 72a84c421a5..899921cca51 100644 --- a/nfc_magic/scenes/nfc_magic_scene_gen4_menu.c +++ b/nfc_magic/scenes/nfc_magic_scene_gen4_menu.c @@ -1,6 +1,4 @@ #include "../nfc_magic_app_i.h" -#include "gui/scene_manager.h" -#include "protocols/gen4/gen4.h" enum SubmenuIndex { SubmenuIndexWrite, @@ -98,7 +96,7 @@ bool nfc_magic_scene_gen4_menu_on_event(void* context, SceneManagerEvent event) scene_manager_set_scene_state(instance->scene_manager, NfcMagicSceneGen4Menu, event.event); } else if(event.type == SceneManagerEventTypeBack) { - if(instance->gen4_password.value != 0) { + if(gen4_password_is_set(&instance->gen4_password)) { consumed = scene_manager_search_and_switch_to_previous_scene( instance->scene_manager, NfcMagicSceneGen4ActionsMenu); } else { diff --git a/nfc_magic/scenes/nfc_magic_scene_gen4_select_shd_mode.c b/nfc_magic/scenes/nfc_magic_scene_gen4_select_shd_mode.c index 7a786d6173a..47372c10418 100644 --- a/nfc_magic/scenes/nfc_magic_scene_gen4_select_shd_mode.c +++ b/nfc_magic/scenes/nfc_magic_scene_gen4_select_shd_mode.c @@ -1,5 +1,4 @@ #include "../nfc_magic_app_i.h" -#include "protocols/gen4/gen4_poller_i.h" enum SubmenuIndex { SubmenuIndexPreWriteMode, diff --git a/nfc_magic/scenes/nfc_magic_scene_gen4_set_direct_write_block_0_mode.c b/nfc_magic/scenes/nfc_magic_scene_gen4_set_direct_write_block_0_mode.c index aa7ee620719..8e2b1abe205 100644 --- a/nfc_magic/scenes/nfc_magic_scene_gen4_set_direct_write_block_0_mode.c +++ b/nfc_magic/scenes/nfc_magic_scene_gen4_set_direct_write_block_0_mode.c @@ -1,6 +1,5 @@ #include "../nfc_magic_app_i.h" -#include "../lib/magic/protocols/gen4/gen4_poller_i.h" -#include "gui/scene_manager.h" +#include "magic/protocols/gen4/gen4_poller.h" enum { NfcMagicSceneGen4SetDirectWriteBlock0ModeStateCardSearch, @@ -66,7 +65,9 @@ void nfc_magic_scene_gen4_set_direct_write_block_0_mode_on_enter(void* context) instance->gen4_poller = gen4_poller_alloc(instance->nfc); gen4_poller_set_password(instance->gen4_poller, instance->gen4_password); - instance->gen4_poller->direct_write_block_0_mode = direct_write_block_0_mode; + + gen4_poller_struct_set_direct_write_block_0_mode( + instance->gen4_poller, direct_write_block_0_mode); gen4_poller_start( instance->gen4_poller, diff --git a/nfc_magic/scenes/nfc_magic_scene_gen4_set_shd_mode.c b/nfc_magic/scenes/nfc_magic_scene_gen4_set_shd_mode.c index 342e755afcf..0181cdb44c4 100644 --- a/nfc_magic/scenes/nfc_magic_scene_gen4_set_shd_mode.c +++ b/nfc_magic/scenes/nfc_magic_scene_gen4_set_shd_mode.c @@ -1,5 +1,5 @@ #include "../nfc_magic_app_i.h" -#include "../lib/magic/protocols/gen4/gen4_poller_i.h" +#include "magic/protocols/gen4/gen4_poller.h" enum { NfcMagicSceneGen4SetShadowModeStateCardSearch, @@ -64,7 +64,7 @@ void nfc_magic_scene_gen4_set_shd_mode_on_enter(void* context) { instance->gen4_poller = gen4_poller_alloc(instance->nfc); gen4_poller_set_password(instance->gen4_poller, instance->gen4_password); - instance->gen4_poller->shadow_mode = shadow_mode; + gen4_poller_struct_set_shadow_mode(instance->gen4_poller, shadow_mode); gen4_poller_start( instance->gen4_poller, nfc_magic_scene_gen4_set_shd_mode_poller_callback, instance); diff --git a/nfc_magic/scenes/nfc_magic_scene_gen4_show_cfg.c b/nfc_magic/scenes/nfc_magic_scene_gen4_show_cfg.c index d1f93f30ddd..7ab6fc324e4 100644 --- a/nfc_magic/scenes/nfc_magic_scene_gen4_show_cfg.c +++ b/nfc_magic/scenes/nfc_magic_scene_gen4_show_cfg.c @@ -1,6 +1,4 @@ #include "../nfc_magic_app_i.h" -#include "protocols/gen4/gen4.h" -#include "protocols/gen4/gen4_poller_i.h" void nfc_magic_scene_gen4_show_cfg_widget_callback( GuiButtonType result, diff --git a/nfc_magic/scenes/nfc_magic_scene_gen4_show_info.c b/nfc_magic/scenes/nfc_magic_scene_gen4_show_info.c index 4fdf1a09fbf..d8de8424017 100644 --- a/nfc_magic/scenes/nfc_magic_scene_gen4_show_info.c +++ b/nfc_magic/scenes/nfc_magic_scene_gen4_show_info.c @@ -1,10 +1,4 @@ #include "../nfc_magic_app_i.h" -#include "core/string.h" -#include "gui/modules/widget_elements/widget_element.h" -#include "gui/scene_manager.h" - -#include -#include void nfc_magic_scene_gen4_show_info_widget_callback( GuiButtonType result, diff --git a/nfc_magic/scenes/nfc_magic_scene_key_input.c b/nfc_magic/scenes/nfc_magic_scene_key_input.c index d0020d98c6c..dfc56227739 100644 --- a/nfc_magic/scenes/nfc_magic_scene_key_input.c +++ b/nfc_magic/scenes/nfc_magic_scene_key_input.c @@ -1,9 +1,5 @@ #include "../nfc_magic_app_i.h" -#include "protocols/gen4/gen4.h" -#include -#include - void nfc_magic_scene_key_input_byte_input_callback(void* context) { NfcMagicApp* instance = context; diff --git a/nfc_magic/scenes/nfc_magic_scene_magic_info.c b/nfc_magic/scenes/nfc_magic_scene_magic_info.c index 83942df1e10..5a954d5f631 100644 --- a/nfc_magic/scenes/nfc_magic_scene_magic_info.c +++ b/nfc_magic/scenes/nfc_magic_scene_magic_info.c @@ -1,9 +1,5 @@ #include "../nfc_magic_app_i.h" -#include "core/string.h" -#include "gui/canvas.h" -#include "gui/modules/widget.h" -#include "lib/magic/nfc_magic_scanner.h" -#include "protocols/gen4/gen4.h" +#include "magic/nfc_magic_scanner.h" void nfc_magic_scene_magic_info_widget_callback( GuiButtonType result, @@ -22,22 +18,24 @@ void nfc_magic_scene_magic_info_on_enter(void* context) { notification_message(instance->notifications, &sequence_success); - //widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); - widget_add_string_element( - widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Magic card detected"); - widget_add_string_element( - widget, - 3, - 17, - AlignLeft, - AlignTop, - FontSecondary, - nfc_magic_protocols_get_name(instance->protocol)); + FuriString* message = furi_string_alloc(); + + if(instance->protocol == NfcMagicProtocolClassic) { + widget_add_string_element( + widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "It Might Be a Magic Card"); + furi_string_printf(message, "You can make sure the card is\nmagic by writing to it\n"); + } else { + widget_add_string_element( + widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "Magic card detected!"); + } + furi_string_cat_printf( + message, "Magic Type: %s", nfc_magic_protocols_get_name(instance->protocol)); + widget_add_text_box_element( + widget, 0, 10, 128, 54, AlignLeft, AlignTop, furi_string_get_cstr(message), false); + if(instance->protocol == NfcMagicProtocolGen4) { gen4_copy(instance->gen4_data, nfc_magic_scanner_get_gen4_data(instance->scanner)); - FuriString* message = furi_string_alloc(); - furi_string_printf( message, "Revision: %02X %02X\n", @@ -45,7 +43,7 @@ void nfc_magic_scene_magic_info_on_enter(void* context) { instance->gen4_data->revision.data[4]); widget_add_string_element( - widget, 55, 17, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(message)); + widget, 0, 20, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(message)); furi_string_printf( message, @@ -53,14 +51,16 @@ void nfc_magic_scene_magic_info_on_enter(void* context) { gen4_get_configuration_name(&instance->gen4_data->config)); widget_add_string_multiline_element( - widget, 3, 27, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(message)); - furi_string_free(message); + widget, 0, 30, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(message)); } + widget_add_button_element( widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_magic_info_widget_callback, instance); widget_add_button_element( widget, GuiButtonTypeRight, "More", nfc_magic_scene_magic_info_widget_callback, instance); + furi_string_free(message); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWidget); } @@ -75,9 +75,15 @@ bool nfc_magic_scene_magic_info_on_event(void* context, SceneManagerEvent event) if(instance->protocol == NfcMagicProtocolGen1) { scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen1Menu); consumed = true; - } else { + } else if(instance->protocol == NfcMagicProtocolGen4) { scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4Menu); consumed = true; + } else if(instance->protocol == NfcMagicProtocolGen2) { + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen2Menu); + consumed = true; + } else if(instance->protocol == NfcMagicProtocolClassic) { + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneMfClassicMenu); + consumed = true; } } } else if(event.type == SceneManagerEventTypeBack) { diff --git a/nfc_magic/scenes/nfc_magic_scene_mf_classic_dict_attack.c b/nfc_magic/scenes/nfc_magic_scene_mf_classic_dict_attack.c new file mode 100644 index 00000000000..c53841984ed --- /dev/null +++ b/nfc_magic/scenes/nfc_magic_scene_mf_classic_dict_attack.c @@ -0,0 +1,311 @@ +#include "../nfc_magic_app_i.h" + +#include +#include + +#include "views/dict_attack.h" + +#define TAG "NfcMagicMfClassicDictAttack" + +typedef enum { + DictAttackStateUserDictInProgress, + DictAttackStateSystemDictInProgress, +} DictAttackState; + +NfcCommand nfc_dict_attack_worker_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.instance); + furi_assert(event.protocol == NfcProtocolMfClassic); + + NfcCommand command = NfcCommandContinue; + MfClassicPollerEvent* mfc_event = event.event_data; + + NfcMagicApp* instance = context; + if(mfc_event->type == MfClassicPollerEventTypeCardDetected) { + instance->nfc_dict_context.is_card_present = true; + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicAppCustomEventCardDetected); + } else if(mfc_event->type == MfClassicPollerEventTypeCardLost) { + instance->nfc_dict_context.is_card_present = false; + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicAppCustomEventCardLost); + } else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) { + const MfClassicData* mfc_data = nfc_poller_get_data(instance->poller); + nfc_device_set_data(instance->target_dev, NfcProtocolMfClassic, mfc_data); + FURI_LOG_D(TAG, "MFC type: %d", mfc_data->type); + mfc_event->data->poller_mode.mode = MfClassicPollerModeDictAttack; + mfc_event->data->poller_mode.data = mfc_data; + instance->nfc_dict_context.sectors_total = + mf_classic_get_total_sectors_num(mfc_data->type); + FURI_LOG_D(TAG, "Total sectors: %d", mf_classic_get_total_sectors_num(mfc_data->type)); + mf_classic_get_read_sectors_and_keys( + mfc_data, + &instance->nfc_dict_context.sectors_read, + &instance->nfc_dict_context.keys_found); + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicAppCustomEventDictAttackDataUpdate); + } else if(mfc_event->type == MfClassicPollerEventTypeRequestKey) { + MfClassicKey key = {}; + if(keys_dict_get_next_key( + instance->nfc_dict_context.dict, key.data, sizeof(MfClassicKey))) { + mfc_event->data->key_request_data.key = key; + mfc_event->data->key_request_data.key_provided = true; + instance->nfc_dict_context.dict_keys_current++; + if(instance->nfc_dict_context.dict_keys_current % 10 == 0) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicAppCustomEventDictAttackDataUpdate); + } + } else { + mfc_event->data->key_request_data.key_provided = false; + } + } else if(mfc_event->type == MfClassicPollerEventTypeDataUpdate) { + MfClassicPollerEventDataUpdate* data_update = &mfc_event->data->data_update; + instance->nfc_dict_context.sectors_read = data_update->sectors_read; + instance->nfc_dict_context.keys_found = data_update->keys_found; + instance->nfc_dict_context.current_sector = data_update->current_sector; + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicAppCustomEventDictAttackDataUpdate); + } else if(mfc_event->type == MfClassicPollerEventTypeNextSector) { + keys_dict_rewind(instance->nfc_dict_context.dict); + instance->nfc_dict_context.dict_keys_current = 0; + instance->nfc_dict_context.current_sector = + mfc_event->data->next_sector_data.current_sector; + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicAppCustomEventDictAttackDataUpdate); + } else if(mfc_event->type == MfClassicPollerEventTypeFoundKeyA) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicAppCustomEventDictAttackDataUpdate); + } else if(mfc_event->type == MfClassicPollerEventTypeFoundKeyB) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicAppCustomEventDictAttackDataUpdate); + } else if(mfc_event->type == MfClassicPollerEventTypeKeyAttackStart) { + instance->nfc_dict_context.key_attack_current_sector = + mfc_event->data->key_attack_data.current_sector; + instance->nfc_dict_context.is_key_attack = true; + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicAppCustomEventDictAttackDataUpdate); + } else if(mfc_event->type == MfClassicPollerEventTypeKeyAttackStop) { + keys_dict_rewind(instance->nfc_dict_context.dict); + instance->nfc_dict_context.is_key_attack = false; + instance->nfc_dict_context.dict_keys_current = 0; + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicAppCustomEventDictAttackDataUpdate); + } else if(mfc_event->type == MfClassicPollerEventTypeSuccess) { + const MfClassicData* mfc_data = nfc_poller_get_data(instance->poller); + nfc_device_set_data(instance->target_dev, NfcProtocolMfClassic, mfc_data); + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicAppCustomEventDictAttackComplete); + command = NfcCommandStop; + } + + return command; +} + +void nfc_dict_attack_dict_attack_result_callback(DictAttackEvent event, void* context) { + furi_assert(context); + NfcMagicApp* instance = context; + + if(event == DictAttackEventSkipPressed) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicAppCustomEventDictAttackSkip); + } +} + +static void nfc_magic_scene_mf_classic_dict_attack_update_view(NfcMagicApp* instance) { + NfcMagicAppMfClassicDictAttackContext* mfc_dict = &instance->nfc_dict_context; + + if(mfc_dict->is_key_attack) { + dict_attack_set_key_attack(instance->dict_attack, mfc_dict->key_attack_current_sector); + } else { + dict_attack_reset_key_attack(instance->dict_attack); + dict_attack_set_sectors_total(instance->dict_attack, mfc_dict->sectors_total); + dict_attack_set_sectors_read(instance->dict_attack, mfc_dict->sectors_read); + dict_attack_set_keys_found(instance->dict_attack, mfc_dict->keys_found); + dict_attack_set_current_dict_key(instance->dict_attack, mfc_dict->dict_keys_current); + dict_attack_set_current_sector(instance->dict_attack, mfc_dict->current_sector); + } +} + +static void nfc_magic_scene_mf_classic_dict_attack_prepare_view(NfcMagicApp* instance) { + uint32_t state = + scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneMfClassicDictAttack); + if(state == DictAttackStateUserDictInProgress) { + do { + if(!keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_PATH)) { + state = DictAttackStateSystemDictInProgress; + break; + } + + instance->nfc_dict_context.dict = keys_dict_alloc( + NFC_APP_MF_CLASSIC_DICT_USER_PATH, KeysDictModeOpenAlways, sizeof(MfClassicKey)); + if(keys_dict_get_total_keys(instance->nfc_dict_context.dict) == 0) { + keys_dict_free(instance->nfc_dict_context.dict); + state = DictAttackStateSystemDictInProgress; + break; + } + + dict_attack_set_header(instance->dict_attack, "MF Classic User Dictionary"); + } while(false); + } + if(state == DictAttackStateSystemDictInProgress) { + instance->nfc_dict_context.dict = keys_dict_alloc( + NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH, KeysDictModeOpenExisting, sizeof(MfClassicKey)); + dict_attack_set_header(instance->dict_attack, "MF Classic System Dictionary"); + } + + instance->nfc_dict_context.dict_keys_total = + keys_dict_get_total_keys(instance->nfc_dict_context.dict); + dict_attack_set_total_dict_keys( + instance->dict_attack, instance->nfc_dict_context.dict_keys_total); + instance->nfc_dict_context.dict_keys_current = 0; + + dict_attack_set_callback( + instance->dict_attack, nfc_dict_attack_dict_attack_result_callback, instance); + nfc_magic_scene_mf_classic_dict_attack_update_view(instance); + + scene_manager_set_scene_state( + instance->scene_manager, NfcMagicSceneMfClassicDictAttack, state); +} + +void nfc_magic_scene_mf_classic_dict_attack_on_enter(void* context) { + NfcMagicApp* instance = context; + + scene_manager_set_scene_state( + instance->scene_manager, + NfcMagicSceneMfClassicDictAttack, + DictAttackStateUserDictInProgress); + nfc_magic_scene_mf_classic_dict_attack_prepare_view(instance); + dict_attack_set_card_state(instance->dict_attack, true); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewDictAttack); + nfc_magic_app_blink_start(instance); + notification_message(instance->notifications, &sequence_display_backlight_enforce_on); + + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); + nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); +} + +static void nfc_magic_scene_mf_classic_dict_attack_notify_read(NfcMagicApp* instance) { + const MfClassicData* mfc_data = nfc_poller_get_data(instance->poller); + bool is_card_fully_read = mf_classic_is_card_read(mfc_data); + if(is_card_fully_read) { + notification_message(instance->notifications, &sequence_success); + } else { + notification_message(instance->notifications, &sequence_semi_success); + } +} + +bool nfc_magic_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent event) { + NfcMagicApp* instance = context; + bool consumed = false; + + uint32_t state = + scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneMfClassicDictAttack); + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcMagicAppCustomEventDictAttackComplete) { + if(state == DictAttackStateUserDictInProgress) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + keys_dict_free(instance->nfc_dict_context.dict); + scene_manager_set_scene_state( + instance->scene_manager, + NfcMagicSceneMfClassicDictAttack, + DictAttackStateSystemDictInProgress); + nfc_magic_scene_mf_classic_dict_attack_prepare_view(instance); + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); + nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); + consumed = true; + } else { + nfc_magic_scene_mf_classic_dict_attack_notify_read(instance); + if(instance->protocol == NfcMagicProtocolGen2) { + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen2WriteCheck); + } else { + scene_manager_next_scene( + instance->scene_manager, NfcMagicSceneMfClassicWriteCheck); + } + dolphin_deed(DolphinDeedNfcReadSuccess); + consumed = true; + } + } else if(event.event == NfcMagicAppCustomEventCardDetected) { + dict_attack_set_card_state(instance->dict_attack, true); + consumed = true; + } else if(event.event == NfcMagicAppCustomEventCardLost) { + dict_attack_set_card_state(instance->dict_attack, false); + consumed = true; + } else if(event.event == NfcMagicAppCustomEventDictAttackDataUpdate) { + nfc_magic_scene_mf_classic_dict_attack_update_view(instance); + } else if(event.event == NfcMagicAppCustomEventDictAttackSkip) { + const MfClassicData* mfc_data = nfc_poller_get_data(instance->poller); + nfc_device_set_data(instance->target_dev, NfcProtocolMfClassic, mfc_data); + if(state == DictAttackStateUserDictInProgress) { + if(instance->nfc_dict_context.is_card_present) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + keys_dict_free(instance->nfc_dict_context.dict); + scene_manager_set_scene_state( + instance->scene_manager, + NfcMagicSceneMfClassicDictAttack, + DictAttackStateSystemDictInProgress); + nfc_magic_scene_mf_classic_dict_attack_prepare_view(instance); + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); + nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); + } else { + nfc_magic_scene_mf_classic_dict_attack_notify_read(instance); + if(instance->protocol == NfcMagicProtocolGen2) { + scene_manager_next_scene( + instance->scene_manager, NfcMagicSceneGen2WriteCheck); + } else { + scene_manager_next_scene( + instance->scene_manager, NfcMagicSceneMfClassicWriteCheck); + } + dolphin_deed(DolphinDeedNfcReadSuccess); + } + consumed = true; + } else if(state == DictAttackStateSystemDictInProgress) { + nfc_magic_scene_mf_classic_dict_attack_notify_read(instance); + if(instance->protocol == NfcMagicProtocolGen2) { + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen2WriteCheck); + } else { + scene_manager_next_scene( + instance->scene_manager, NfcMagicSceneMfClassicWriteCheck); + } + dolphin_deed(DolphinDeedNfcReadSuccess); + consumed = true; + } + } + } else if(event.type == SceneManagerEventTypeBack) { + scene_manager_previous_scene(instance->scene_manager); + consumed = true; + } + return consumed; +} + +void nfc_magic_scene_mf_classic_dict_attack_on_exit(void* context) { + NfcMagicApp* instance = context; + const MfClassicData* mfc_data = nfc_poller_get_data(instance->poller); + nfc_device_set_data(instance->target_dev, NfcProtocolMfClassic, mfc_data); + + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + + dict_attack_reset(instance->dict_attack); + scene_manager_set_scene_state( + instance->scene_manager, + NfcMagicSceneMfClassicDictAttack, + DictAttackStateUserDictInProgress); + + keys_dict_free(instance->nfc_dict_context.dict); + + instance->nfc_dict_context.current_sector = 0; + instance->nfc_dict_context.sectors_total = 0; + instance->nfc_dict_context.sectors_read = 0; + instance->nfc_dict_context.keys_found = 0; + instance->nfc_dict_context.dict_keys_total = 0; + instance->nfc_dict_context.dict_keys_current = 0; + instance->nfc_dict_context.is_key_attack = false; + instance->nfc_dict_context.key_attack_current_sector = 0; + instance->nfc_dict_context.is_card_present = false; + + nfc_magic_app_blink_stop(instance); + notification_message(instance->notifications, &sequence_display_backlight_enforce_auto); +} diff --git a/nfc_magic/scenes/nfc_magic_scene_mf_classic_menu.c b/nfc_magic/scenes/nfc_magic_scene_mf_classic_menu.c new file mode 100644 index 00000000000..a1237022613 --- /dev/null +++ b/nfc_magic/scenes/nfc_magic_scene_mf_classic_menu.c @@ -0,0 +1,65 @@ +#include "../nfc_magic_app_i.h" + +enum SubmenuIndex { + SubmenuIndexWrite, + SubmenuIndexWipe, +}; + +void nfc_magic_scene_mf_classic_menu_submenu_callback(void* context, uint32_t index) { + NfcMagicApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, index); +} + +void nfc_magic_scene_mf_classic_menu_on_enter(void* context) { + NfcMagicApp* instance = context; + + Submenu* submenu = instance->submenu; + submenu_add_item( + submenu, + "Write", + SubmenuIndexWrite, + nfc_magic_scene_mf_classic_menu_submenu_callback, + instance); + submenu_add_item( + submenu, + "Wipe", + SubmenuIndexWipe, + nfc_magic_scene_mf_classic_menu_submenu_callback, + instance); + + submenu_set_selected_item( + submenu, + scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneMfClassicMenu)); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewMenu); +} + +bool nfc_magic_scene_mf_classic_menu_on_event(void* context, SceneManagerEvent event) { + NfcMagicApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexWrite) { + instance->gen2_poller_is_wipe_mode = false; + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneFileSelect); + consumed = true; + } else if(event.event == SubmenuIndexWipe) { + instance->gen2_poller_is_wipe_mode = true; + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneMfClassicDictAttack); + consumed = true; + } + scene_manager_set_scene_state( + instance->scene_manager, NfcMagicSceneMfClassicMenu, event.event); + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcMagicSceneStart); + } + + return consumed; +} + +void nfc_magic_scene_mf_classic_menu_on_exit(void* context) { + NfcMagicApp* instance = context; + + submenu_reset(instance->submenu); +} diff --git a/nfc_magic/scenes/nfc_magic_scene_mf_classic_write_check.c b/nfc_magic/scenes/nfc_magic_scene_mf_classic_write_check.c new file mode 100644 index 00000000000..4694733eac8 --- /dev/null +++ b/nfc_magic/scenes/nfc_magic_scene_mf_classic_write_check.c @@ -0,0 +1,124 @@ +#include "../nfc_magic_app_i.h" + +void nfc_magic_scene_mf_classic_write_check_view_callback(WriteProblemsEvent event, void* context) { + NfcMagicApp* instance = context; + NfcMagicAppWriteProblemsContext* problems_context = &instance->write_problems_context; + + if(event == WriteProblemsEventCenterPressed) { + if(problems_context->problem_index == problems_context->problems_total - 1) { + // Continue to the next scene + if(instance->gen2_poller_is_wipe_mode) { + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWipe); + } else { + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWrite); + } + } else { + // Move to the next problem + problems_context->problem_index++; + problems_context->problem_index_abs++; + write_problems_set_problem_index( + instance->write_problems, problems_context->problem_index); + + for(uint8_t i = problems_context->problem_index_abs; + i < GEN2_POLLER_WRITE_PROBLEMS_LEN; + i++) { + if(problems_context->problems.all_problems & (1 << i)) { + write_problems_set_content(instance->write_problems, gen2_problem_strings[i]); + problems_context->problem_index_abs = i; + break; + } + } + } + } else if(event == WriteProblemsEventLeftPressed) { + if(problems_context->problem_index == 0) { + // Exit to the previous scene + scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcMagicSceneMfClassicMenu); + } else { + // Move to the previous problem + problems_context->problem_index--; + problems_context->problem_index_abs--; + write_problems_set_problem_index( + instance->write_problems, problems_context->problem_index); + + for(uint8_t i = problems_context->problem_index_abs; + i < GEN2_POLLER_WRITE_PROBLEMS_LEN; + i--) { + if(problems_context->problems.all_problems & (1 << i)) { + write_problems_set_content(instance->write_problems, gen2_problem_strings[i]); + problems_context->problem_index_abs = i; + break; + } + } + } + } +} + +void nfc_magic_scene_mf_classic_write_check_on_enter(void* context) { + NfcMagicApp* instance = context; + + Gen2PollerWriteProblems problems = gen2_poller_check_target_problems(instance->target_dev); + if(!instance->gen2_poller_is_wipe_mode) { + problems.all_problems |= + gen2_poller_check_source_problems(instance->source_dev).all_problems; + } + FURI_LOG_D("GEN2", "Problems: %d", problems.all_problems); + + WriteProblems* write_problems = instance->write_problems; + uint8_t problems_count = 0; + uint8_t current_problem = 0; + furi_assert(!problems.no_data, "No MFC data in nfc device"); + + // Set the uid_locked problem to true as we have a Mifare Classic card + problems.uid_locked = true; + + // Count the number of problems + for(uint8_t i = 0; i < GEN2_POLLER_WRITE_PROBLEMS_LEN; i++) { + if(problems.all_problems & (1 << i)) { + problems_count++; + } + } + + // Init the view + write_problems_set_callback( + write_problems, nfc_magic_scene_mf_classic_write_check_view_callback, instance); + write_problems_set_problems_total(write_problems, problems_count); + write_problems_set_problem_index(write_problems, current_problem); + + // Set the first problem + for(uint8_t i = current_problem; i < GEN2_POLLER_WRITE_PROBLEMS_LEN; i++) { + if(problems.all_problems & (1 << i)) { + write_problems_set_content(instance->write_problems, gen2_problem_strings[i]); + instance->write_problems_context.problem_index_abs = i; + break; + } + } + + // Save the problems context + instance->write_problems_context.problem_index = current_problem; + instance->write_problems_context.problems_total = problems_count; + instance->write_problems_context.problems = problems; + + // Setup and start worker + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWriteProblems); +} + +bool nfc_magic_scene_mf_classic_write_check_on_event(void* context, SceneManagerEvent event) { + NfcMagicApp* instance = context; + UNUSED(event); + UNUSED(context); + UNUSED(instance); + bool consumed = false; + + return consumed; +} + +void nfc_magic_scene_mf_classic_write_check_on_exit(void* context) { + NfcMagicApp* instance = context; + + instance->write_problems_context.problem_index = 0; + instance->write_problems_context.problems_total = 0; + instance->write_problems_context.problems.all_problems = 0; + + write_problems_reset(instance->write_problems); +} diff --git a/nfc_magic/scenes/nfc_magic_scene_start.c b/nfc_magic/scenes/nfc_magic_scene_start.c index b124d09145c..e37a62be311 100644 --- a/nfc_magic/scenes/nfc_magic_scene_start.c +++ b/nfc_magic/scenes/nfc_magic_scene_start.c @@ -27,7 +27,7 @@ void nfc_magic_scene_start_on_enter(void* context) { nfc_magic_scene_start_submenu_callback, instance); - instance->gen4_password.value = 0; + gen4_password_reset(&instance->gen4_password); submenu_set_selected_item( submenu, scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneStart)); diff --git a/nfc_magic/scenes/nfc_magic_scene_wipe.c b/nfc_magic/scenes/nfc_magic_scene_wipe.c index 3cf1f149b30..f04550b7621 100644 --- a/nfc_magic/scenes/nfc_magic_scene_wipe.c +++ b/nfc_magic/scenes/nfc_magic_scene_wipe.c @@ -5,7 +5,7 @@ enum { NfcMagicSceneWipeStateCardFound, }; -NfcCommand nfc_mafic_scene_wipe_gen1_poller_callback(Gen1aPollerEvent event, void* context) { +NfcCommand nfc_magic_scene_wipe_gen1_poller_callback(Gen1aPollerEvent event, void* context) { NfcMagicApp* instance = context; furi_assert(event.data); @@ -29,7 +29,35 @@ NfcCommand nfc_mafic_scene_wipe_gen1_poller_callback(Gen1aPollerEvent event, voi return command; } -NfcCommand nfc_mafic_scene_wipe_gen4_poller_callback(Gen4PollerEvent event, void* context) { +NfcCommand nfc_magic_scene_wipe_gen2_poller_callback(Gen2PollerEvent event, void* context) { + NfcMagicApp* instance = context; + + NfcCommand command = NfcCommandContinue; + + if(event.type == Gen2PollerEventTypeDetected) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicCustomEventCardDetected); + } else if(event.type == Gen2PollerEventTypeRequestMode) { + event.data->poller_mode.mode = Gen2PollerModeWipe; + } else if(event.type == Gen2PollerEventTypeRequestTargetData) { + const MfClassicData* mfc_data = + nfc_device_get_data(instance->target_dev, NfcProtocolMfClassic); + event.data->target_data.mfc_data = mfc_data; + + } else if(event.type == Gen2PollerEventTypeSuccess) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicCustomEventWorkerSuccess); + command = NfcCommandStop; + } else if(event.type == Gen2PollerEventTypeFail) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicCustomEventWorkerFail); + command = NfcCommandStop; + } + + return command; +} + +NfcCommand nfc_magic_scene_wipe_gen4_poller_callback(Gen4PollerEvent event, void* context) { NfcMagicApp* instance = context; NfcCommand command = NfcCommandContinue; @@ -81,12 +109,20 @@ void nfc_magic_scene_wipe_on_enter(void* context) { if(instance->protocol == NfcMagicProtocolGen1) { instance->gen1a_poller = gen1a_poller_alloc(instance->nfc); gen1a_poller_start( - instance->gen1a_poller, nfc_mafic_scene_wipe_gen1_poller_callback, instance); - } else { + instance->gen1a_poller, nfc_magic_scene_wipe_gen1_poller_callback, instance); + } else if(instance->protocol == NfcMagicProtocolGen2) { + instance->gen2_poller = gen2_poller_alloc(instance->nfc); + gen2_poller_start( + instance->gen2_poller, nfc_magic_scene_wipe_gen2_poller_callback, instance); + } else if(instance->protocol == NfcMagicProtocolClassic) { + instance->gen2_poller = gen2_poller_alloc(instance->nfc); + gen2_poller_start( + instance->gen2_poller, nfc_magic_scene_wipe_gen2_poller_callback, instance); + } else if(instance->protocol == NfcMagicProtocolGen4) { instance->gen4_poller = gen4_poller_alloc(instance->nfc); gen4_poller_set_password(instance->gen4_poller, instance->gen4_password); gen4_poller_start( - instance->gen4_poller, nfc_mafic_scene_wipe_gen4_poller_callback, instance); + instance->gen4_poller, nfc_magic_scene_wipe_gen4_poller_callback, instance); } } @@ -123,7 +159,13 @@ void nfc_magic_scene_wipe_on_exit(void* context) { if(instance->protocol == NfcMagicProtocolGen1) { gen1a_poller_stop(instance->gen1a_poller); gen1a_poller_free(instance->gen1a_poller); - } else { + } else if(instance->protocol == NfcMagicProtocolGen2) { + gen2_poller_stop(instance->gen2_poller); + gen2_poller_free(instance->gen2_poller); + } else if(instance->protocol == NfcMagicProtocolClassic) { + gen2_poller_stop(instance->gen2_poller); + gen2_poller_free(instance->gen2_poller); + } else if(instance->protocol == NfcMagicProtocolGen4) { gen4_poller_stop(instance->gen4_poller); gen4_poller_free(instance->gen4_poller); } diff --git a/nfc_magic/scenes/nfc_magic_scene_wipe_fail.c b/nfc_magic/scenes/nfc_magic_scene_wipe_fail.c index de443a19fcb..895b162d4c4 100644 --- a/nfc_magic/scenes/nfc_magic_scene_wipe_fail.c +++ b/nfc_magic/scenes/nfc_magic_scene_wipe_fail.c @@ -15,7 +15,10 @@ void nfc_magic_scene_wipe_fail_on_enter(void* context) { notification_message(instance->notifications, &sequence_error); widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); - widget_add_string_element(widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Wipe failed"); + widget_add_string_element(widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Failed to Wipe"); + widget_add_string_multiline_element( + widget, 0, 13, AlignLeft, AlignTop, FontSecondary, "Something went\nwrong while wiping"); + widget_add_button_element( widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_wipe_fail_widget_callback, instance); diff --git a/nfc_magic/scenes/nfc_magic_scene_write.c b/nfc_magic/scenes/nfc_magic_scene_write.c index e5705748c9c..af8c3905cc7 100644 --- a/nfc_magic/scenes/nfc_magic_scene_write.c +++ b/nfc_magic/scenes/nfc_magic_scene_write.c @@ -5,7 +5,7 @@ enum { NfcMagicSceneWriteStateCardFound, }; -NfcCommand nfc_mafic_scene_write_gen1_poller_callback(Gen1aPollerEvent event, void* context) { +NfcCommand nfc_magic_scene_write_gen1_poller_callback(Gen1aPollerEvent event, void* context) { NfcMagicApp* instance = context; furi_assert(event.data); @@ -33,7 +33,39 @@ NfcCommand nfc_mafic_scene_write_gen1_poller_callback(Gen1aPollerEvent event, vo return command; } -NfcCommand nfc_mafic_scene_write_gen4_poller_callback(Gen4PollerEvent event, void* context) { +NfcCommand nfc_magic_scene_write_gen2_poller_callback(Gen2PollerEvent event, void* context) { + NfcMagicApp* instance = context; + furi_assert(event.data); + + NfcCommand command = NfcCommandContinue; + + if(event.type == Gen2PollerEventTypeDetected) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicCustomEventCardDetected); + } else if(event.type == Gen2PollerEventTypeRequestMode) { + event.data->poller_mode.mode = Gen2PollerModeWrite; + } else if(event.type == Gen2PollerEventTypeRequestDataToWrite) { + const MfClassicData* mfc_data = + nfc_device_get_data(instance->source_dev, NfcProtocolMfClassic); + event.data->data_to_write.mfc_data = mfc_data; + } else if(event.type == Gen2PollerEventTypeRequestTargetData) { + const MfClassicData* mfc_data = + nfc_device_get_data(instance->target_dev, NfcProtocolMfClassic); + event.data->target_data.mfc_data = mfc_data; + } else if(event.type == Gen2PollerEventTypeSuccess) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicCustomEventWorkerSuccess); + command = NfcCommandStop; + } else if(event.type == Gen2PollerEventTypeFail) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicCustomEventWorkerFail); + command = NfcCommandStop; + } + + return command; +} + +NfcCommand nfc_magic_scene_write_gen4_poller_callback(Gen4PollerEvent event, void* context) { NfcMagicApp* instance = context; furi_assert(event.data); @@ -90,12 +122,20 @@ void nfc_magic_scene_write_on_enter(void* context) { if(instance->protocol == NfcMagicProtocolGen1) { instance->gen1a_poller = gen1a_poller_alloc(instance->nfc); gen1a_poller_start( - instance->gen1a_poller, nfc_mafic_scene_write_gen1_poller_callback, instance); + instance->gen1a_poller, nfc_magic_scene_write_gen1_poller_callback, instance); + } else if(instance->protocol == NfcMagicProtocolGen2) { + instance->gen2_poller = gen2_poller_alloc(instance->nfc); + gen2_poller_start( + instance->gen2_poller, nfc_magic_scene_write_gen2_poller_callback, instance); + } else if(instance->protocol == NfcMagicProtocolClassic) { + instance->gen2_poller = gen2_poller_alloc(instance->nfc); + gen2_poller_start( + instance->gen2_poller, nfc_magic_scene_write_gen2_poller_callback, instance); } else { instance->gen4_poller = gen4_poller_alloc(instance->nfc); gen4_poller_set_password(instance->gen4_poller, instance->gen4_password); gen4_poller_start( - instance->gen4_poller, nfc_mafic_scene_write_gen4_poller_callback, instance); + instance->gen4_poller, nfc_magic_scene_write_gen4_poller_callback, instance); } } @@ -132,7 +172,12 @@ void nfc_magic_scene_write_on_exit(void* context) { if(instance->protocol == NfcMagicProtocolGen1) { gen1a_poller_stop(instance->gen1a_poller); gen1a_poller_free(instance->gen1a_poller); - } else { + } else if( + instance->protocol == NfcMagicProtocolGen2 || + instance->protocol == NfcMagicProtocolClassic) { + gen2_poller_stop(instance->gen2_poller); + gen2_poller_free(instance->gen2_poller); + } else if(instance->protocol == NfcMagicProtocolGen4) { gen4_poller_stop(instance->gen4_poller); gen4_poller_free(instance->gen4_poller); } diff --git a/nfc_magic/scenes/nfc_magic_scene_write_fail.c b/nfc_magic/scenes/nfc_magic_scene_write_fail.c index 1e8ec2d3c7d..042b8098784 100644 --- a/nfc_magic/scenes/nfc_magic_scene_write_fail.c +++ b/nfc_magic/scenes/nfc_magic_scene_write_fail.c @@ -18,18 +18,12 @@ void nfc_magic_scene_write_fail_on_enter(void* context) { widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); widget_add_string_element( - widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!"); + widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Failed to Write"); widget_add_string_multiline_element( - widget, - 7, - 17, - AlignLeft, - AlignTop, - FontSecondary, - "Not all sectors\nwere written\ncorrectly."); + widget, 0, 13, AlignLeft, AlignTop, FontSecondary, "Something went\nwrong while\nwriting"); widget_add_button_element( - widget, GuiButtonTypeLeft, "Finish", nfc_magic_scene_write_fail_widget_callback, instance); + widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_write_fail_widget_callback, instance); // Setup and start worker view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWidget); @@ -41,12 +35,10 @@ bool nfc_magic_scene_write_fail_on_event(void* context, SceneManagerEvent event) if(event.type == SceneManagerEventTypeCustom) { if(event.event == GuiButtonTypeLeft) { - consumed = scene_manager_search_and_switch_to_previous_scene( - instance->scene_manager, NfcMagicSceneStart); + consumed = scene_manager_previous_scene(instance->scene_manager); } } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_search_and_switch_to_previous_scene( - instance->scene_manager, NfcMagicSceneStart); + consumed = scene_manager_previous_scene(instance->scene_manager); } return consumed; } diff --git a/nfc_magic/views/dict_attack.c b/nfc_magic/views/dict_attack.c new file mode 100644 index 00000000000..f09ca0c79b7 --- /dev/null +++ b/nfc_magic/views/dict_attack.c @@ -0,0 +1,245 @@ +#include "dict_attack.h" + +#include + +#define NFC_CLASSIC_KEYS_PER_SECTOR 2 + +struct DictAttack { + View* view; + DictAttackCallback callback; + void* context; +}; + +typedef struct { + FuriString* header; + bool card_detected; + uint8_t sectors_total; + uint8_t sectors_read; + uint8_t current_sector; + uint8_t keys_found; + size_t dict_keys_total; + size_t dict_keys_current; + bool is_key_attack; + uint8_t key_attack_current_sector; +} DictAttackViewModel; + +static void dict_attack_draw_callback(Canvas* canvas, void* model) { + DictAttackViewModel* m = model; + if(!m->card_detected) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned( + canvas, 64, 4, AlignCenter, AlignTop, "Hold the tag to the Flipper!"); + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned( + canvas, 64, 23, AlignCenter, AlignTop, "Make sure the tag is\npositioned correctly."); + } else { + char draw_str[32] = {}; + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned( + canvas, 64, 0, AlignCenter, AlignTop, furi_string_get_cstr(m->header)); + if(m->is_key_attack) { + snprintf( + draw_str, + sizeof(draw_str), + "Reuse key check for sector: %d", + m->key_attack_current_sector); + } else { + snprintf(draw_str, sizeof(draw_str), "Unlocking sector: %d", m->current_sector); + } + canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); + float dict_progress = m->dict_keys_total == 0 ? + 0 : + (float)(m->dict_keys_current) / (float)(m->dict_keys_total); + float progress = m->sectors_total == 0 ? 0 : + ((float)(m->current_sector) + dict_progress) / + (float)(m->sectors_total); + if(progress > 1.0f) { + progress = 1.0f; + } + if(m->dict_keys_current == 0) { + // Cause when people see 0 they think it's broken + snprintf(draw_str, sizeof(draw_str), "%d/%zu", 1, m->dict_keys_total); + } else { + snprintf( + draw_str, sizeof(draw_str), "%zu/%zu", m->dict_keys_current, m->dict_keys_total); + } + elements_progress_bar_with_text(canvas, 0, 20, 128, dict_progress, draw_str); + canvas_set_font(canvas, FontSecondary); + snprintf( + draw_str, + sizeof(draw_str), + "Keys found: %d/%d", + m->keys_found, + m->sectors_total * NFC_CLASSIC_KEYS_PER_SECTOR); + canvas_draw_str_aligned(canvas, 0, 33, AlignLeft, AlignTop, draw_str); + snprintf( + draw_str, sizeof(draw_str), "Sectors Read: %d/%d", m->sectors_read, m->sectors_total); + canvas_draw_str_aligned(canvas, 0, 43, AlignLeft, AlignTop, draw_str); + } + elements_button_center(canvas, "Skip"); +} + +static bool dict_attack_input_callback(InputEvent* event, void* context) { + DictAttack* instance = context; + bool consumed = false; + + if(event->type == InputTypeShort && event->key == InputKeyOk) { + if(instance->callback) { + instance->callback(DictAttackEventSkipPressed, instance->context); + } + consumed = true; + } + + return consumed; +} + +DictAttack* dict_attack_alloc() { + DictAttack* instance = malloc(sizeof(DictAttack)); + instance->view = view_alloc(); + view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(DictAttackViewModel)); + view_set_draw_callback(instance->view, dict_attack_draw_callback); + view_set_input_callback(instance->view, dict_attack_input_callback); + view_set_context(instance->view, instance); + with_view_model( + instance->view, + DictAttackViewModel * model, + { model->header = furi_string_alloc(); }, + false); + + return instance; +} + +void dict_attack_free(DictAttack* instance) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { furi_string_free(model->header); }, false); + + view_free(instance->view); + free(instance); +} + +void dict_attack_reset(DictAttack* instance) { + furi_assert(instance); + + with_view_model( + instance->view, + DictAttackViewModel * model, + { + model->sectors_total = 0; + model->sectors_read = 0; + model->current_sector = 0; + model->keys_found = 0; + model->dict_keys_total = 0; + model->dict_keys_current = 0; + model->is_key_attack = false; + furi_string_reset(model->header); + }, + false); +} + +View* dict_attack_get_view(DictAttack* instance) { + furi_assert(instance); + + return instance->view; +} + +void dict_attack_set_callback(DictAttack* instance, DictAttackCallback callback, void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +void dict_attack_set_header(DictAttack* instance, const char* header) { + furi_assert(instance); + furi_assert(header); + + with_view_model( + instance->view, + DictAttackViewModel * model, + { furi_string_set(model->header, header); }, + true); +} + +void dict_attack_set_card_state(DictAttack* instance, bool detected) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->card_detected = detected; }, true); +} + +void dict_attack_set_sectors_total(DictAttack* instance, uint8_t sectors_total) { + furi_assert(instance); + + with_view_model( + instance->view, + DictAttackViewModel * model, + { model->sectors_total = sectors_total; }, + true); +} + +void dict_attack_set_sectors_read(DictAttack* instance, uint8_t sectors_read) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->sectors_read = sectors_read; }, true); +} + +void dict_attack_set_keys_found(DictAttack* instance, uint8_t keys_found) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->keys_found = keys_found; }, true); +} + +void dict_attack_set_current_sector(DictAttack* instance, uint8_t current_sector) { + furi_assert(instance); + + with_view_model( + instance->view, + DictAttackViewModel * model, + { model->current_sector = current_sector; }, + true); +} + +void dict_attack_set_total_dict_keys(DictAttack* instance, size_t dict_keys_total) { + furi_assert(instance); + + with_view_model( + instance->view, + DictAttackViewModel * model, + { model->dict_keys_total = dict_keys_total; }, + true); +} + +void dict_attack_set_current_dict_key(DictAttack* instance, size_t cur_key_num) { + furi_assert(instance); + + with_view_model( + instance->view, + DictAttackViewModel * model, + { model->dict_keys_current = cur_key_num; }, + true); +} + +void dict_attack_set_key_attack(DictAttack* instance, uint8_t sector) { + furi_assert(instance); + + with_view_model( + instance->view, + DictAttackViewModel * model, + { + model->is_key_attack = true; + model->key_attack_current_sector = sector; + }, + true); +} + +void dict_attack_reset_key_attack(DictAttack* instance) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->is_key_attack = false; }, true); +} diff --git a/nfc_magic/views/dict_attack.h b/nfc_magic/views/dict_attack.h new file mode 100644 index 00000000000..54a0220fe59 --- /dev/null +++ b/nfc_magic/views/dict_attack.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct DictAttack DictAttack; + +typedef enum { + DictAttackEventSkipPressed, +} DictAttackEvent; + +typedef void (*DictAttackCallback)(DictAttackEvent event, void* context); + +DictAttack* dict_attack_alloc(); + +void dict_attack_free(DictAttack* instance); + +void dict_attack_reset(DictAttack* instance); + +View* dict_attack_get_view(DictAttack* instance); + +void dict_attack_set_callback(DictAttack* instance, DictAttackCallback callback, void* context); + +void dict_attack_set_header(DictAttack* instance, const char* header); + +void dict_attack_set_card_state(DictAttack* instance, bool detected); + +void dict_attack_set_sectors_total(DictAttack* instance, uint8_t sectors_total); + +void dict_attack_set_sectors_read(DictAttack* instance, uint8_t sectors_read); + +void dict_attack_set_keys_found(DictAttack* instance, uint8_t keys_found); + +void dict_attack_set_current_sector(DictAttack* instance, uint8_t curr_sec); + +void dict_attack_set_total_dict_keys(DictAttack* instance, size_t dict_keys_total); + +void dict_attack_set_current_dict_key(DictAttack* instance, size_t cur_key_num); + +void dict_attack_set_key_attack(DictAttack* instance, uint8_t sector); + +void dict_attack_reset_key_attack(DictAttack* instance); + +#ifdef __cplusplus +} +#endif diff --git a/nfc_magic/views/write_problems.c b/nfc_magic/views/write_problems.c new file mode 100644 index 00000000000..6b29c3845ac --- /dev/null +++ b/nfc_magic/views/write_problems.c @@ -0,0 +1,160 @@ +#include "write_problems.h" + +#include +#include "nfc_magic_icons.h" + +struct WriteProblems { + View* view; + WriteProblemsCallback callback; + void* context; +}; + +typedef struct { + uint8_t problem_index; + uint8_t problems_total; + FuriString* content; +} WriteProblemsViewModel; + +static void write_problems_view_draw_callback(Canvas* canvas, void* _model) { + WriteProblemsViewModel* model = _model; + FuriString* header = furi_string_alloc(); + canvas_clear(canvas); + + // Header + if(model->problems_total > 1) { + furi_string_printf( + header, "Warnings: %d of %d\n", model->problem_index + 1, model->problems_total); + } else { + furi_string_printf(header, "Warning!"); + } + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 64, 0, AlignCenter, AlignTop, furi_string_get_cstr(header)); + + // Warning message + canvas_set_font(canvas, FontSecondary); + elements_text_box( + canvas, 1, 13, 76, 42, AlignLeft, AlignTop, furi_string_get_cstr(model->content), false); + + // Butttons + if(model->problem_index == model->problems_total - 1) { + elements_button_center(canvas, "Skip"); + elements_button_left(canvas, "Retry"); + } else { + elements_button_center(canvas, "Next"); + elements_button_left(canvas, "Back"); + } + + // Dolphin + canvas_draw_icon(canvas, 83, 22, &I_WarningDolphinFlip_45x42); + + furi_string_free(header); +} + +static bool write_problems_input_callback(InputEvent* event, void* context) { + WriteProblems* instance = context; + + if(event->type == InputTypeShort) { + if(event->key == InputKeyLeft) { + instance->callback(WriteProblemsEventLeftPressed, instance->context); + return true; + } else if(event->key == InputKeyOk) { + instance->callback(WriteProblemsEventCenterPressed, instance->context); + return true; + } + } + return false; +} + +WriteProblems* write_problems_alloc() { + WriteProblems* instance = malloc(sizeof(WriteProblems)); + instance->view = view_alloc(); + view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(WriteProblemsViewModel)); + view_set_draw_callback(instance->view, write_problems_view_draw_callback); + view_set_input_callback(instance->view, write_problems_input_callback); + view_set_context(instance->view, instance); + with_view_model( + instance->view, + WriteProblemsViewModel * model, + { + model->content = furi_string_alloc(); + model->problem_index = 0; + model->problems_total = 0; + }, + false); + + return instance; +} + +void write_problems_free(WriteProblems* instance) { + furi_assert(instance); + + with_view_model( + instance->view, + WriteProblemsViewModel * model, + { furi_string_free(model->content); }, + false); + + view_free(instance->view); + free(instance); +} + +void write_problems_reset(WriteProblems* instance) { + furi_assert(instance); + + with_view_model( + instance->view, + WriteProblemsViewModel * model, + { + model->problem_index = 0; + model->problems_total = 0; + furi_string_reset(model->content); + }, + true); +} + +View* write_problems_get_view(WriteProblems* instance) { + furi_assert(instance); + + return instance->view; +} + +void write_problems_set_callback( + WriteProblems* instance, + WriteProblemsCallback callback, + void* context) { + furi_assert(instance); + instance->callback = callback; + instance->context = context; +} + +void write_problems_set_content(WriteProblems* instance, const char* content) { + furi_assert(instance); + furi_assert(content); + + with_view_model( + instance->view, + WriteProblemsViewModel * model, + { furi_string_set(model->content, content); }, + true); +} + +void write_problems_set_problems_total(WriteProblems* instance, uint8_t problems_total) { + furi_assert(instance); + + with_view_model( + instance->view, + WriteProblemsViewModel * model, + { model->problems_total = problems_total; }, + true); +} + +void write_problems_set_problem_index(WriteProblems* instance, uint8_t problem_index) { + furi_assert(instance); + + with_view_model( + instance->view, + WriteProblemsViewModel * model, + { model->problem_index = problem_index; }, + true); +} \ No newline at end of file diff --git a/nfc_magic/views/write_problems.h b/nfc_magic/views/write_problems.h new file mode 100644 index 00000000000..a38b22f3625 --- /dev/null +++ b/nfc_magic/views/write_problems.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +typedef struct WriteProblems WriteProblems; + +typedef enum { + WriteProblemsEventCenterPressed, + WriteProblemsEventLeftPressed, +} WriteProblemsEvent; + +typedef void (*WriteProblemsCallback)(WriteProblemsEvent event, void* context); + +WriteProblems* write_problems_alloc(); + +void write_problems_free(WriteProblems* instance); + +void write_problems_reset(WriteProblems* instance); + +View* write_problems_get_view(WriteProblems* instance); + +void write_problems_set_callback( + WriteProblems* instance, + WriteProblemsCallback callback, + void* context); + +void write_problems_set_content(WriteProblems* instance, const char* content); + +void write_problems_set_problem_index(WriteProblems* instance, uint8_t index); + +void write_problems_set_problems_total(WriteProblems* instance, uint8_t total); diff --git a/picopass/picopass_device.c b/picopass/picopass_device.c index 61cc45cb657..ea8352f5d04 100644 --- a/picopass/picopass_device.c +++ b/picopass/picopass_device.c @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -17,13 +18,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(); @@ -141,6 +150,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); @@ -159,7 +169,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); @@ -171,6 +181,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 { @@ -205,13 +216,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; @@ -236,10 +255,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); @@ -248,6 +267,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); @@ -262,26 +294,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]; @@ -289,16 +334,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); @@ -373,14 +431,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; @@ -452,7 +518,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/picopass/picopass_device.h b/picopass/picopass_device.h index 4b4bed36699..78ed6645c09 100644 --- a/picopass/picopass_device.h +++ b/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/picopass/protocol/picopass_listener.c b/picopass/protocol/picopass_listener.c index 15db3b44b1c..1a91a9c681c 100644 --- a/picopass/protocol/picopass_listener.c +++ b/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/picopass/protocol/picopass_poller.c b/picopass/protocol/picopass_poller.c index ec6023915ef..eafb26ed245 100644 --- a/picopass/protocol/picopass_poller.c +++ b/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/picopass/scenes/picopass_scene_card_menu.c b/picopass/scenes/picopass_scene_card_menu.c index 68081c4f360..6caf5588713 100644 --- a/picopass/scenes/picopass_scene_card_menu.c +++ b/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/picopass/scenes/picopass_scene_device_info.c b/picopass/scenes/picopass_scene_device_info.c index 17d66fdf1d2..c08adfda236 100644 --- a/picopass/scenes/picopass_scene_device_info.c +++ b/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/picopass/scenes/picopass_scene_loclass.c b/picopass/scenes/picopass_scene_loclass.c index 616cba05747..0015bda6840 100644 --- a/picopass/scenes/picopass_scene_loclass.c +++ b/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/picopass/scenes/picopass_scene_more_info.c b/picopass/scenes/picopass_scene_more_info.c index 4c075825c4d..28790fd5a5b 100644 --- a/picopass/scenes/picopass_scene_more_info.c +++ b/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/picopass/scenes/picopass_scene_read_card_success.c b/picopass/scenes/picopass_scene_read_card_success.c index 18e9e2d56c0..a6d9ba2abc1 100644 --- a/picopass/scenes/picopass_scene_read_card_success.c +++ b/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/picopass/scenes/picopass_scene_saved_menu.c b/picopass/scenes/picopass_scene_saved_menu.c index 35e4573adb9..e8e0771cda5 100644 --- a/picopass/scenes/picopass_scene_saved_menu.c +++ b/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/picopass/views/loclass.c b/picopass/views/loclass.c index 4f9da2a4548..f46a9dfcecd 100644 --- a/picopass/views/loclass.c +++ b/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/picopass/views/loclass.h b/picopass/views/loclass.h index 0e39b6083b0..fc5a49d5f1f 100644 --- a/picopass/views/loclass.h +++ b/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); diff --git a/qrcode/.github/workflows/release.yml b/qrcode/.github/workflows/release.yml index 4f682c6d924..c4ff8a01519 100644 --- a/qrcode/.github/workflows/release.yml +++ b/qrcode/.github/workflows/release.yml @@ -6,7 +6,7 @@ on: - 'v[0-9]+.[0-9]+.[0-9]+' env: - firmware_version: '0.99.1' + firmware_version: '0.100.3' jobs: build: