From 52b76e2e2e28c3539f1b9e79b6bab7a54272815d Mon Sep 17 00:00:00 2001 From: koterba <85573908+koterba@users.noreply.github.com> Date: Tue, 11 Nov 2025 18:33:32 +0000 Subject: [PATCH 01/18] Fix typo in README warning about scammers --- ReadMe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index 5a133ffb9..d7005a097 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -18,7 +18,7 @@ This firmware is a fork of the original (OFW) version of [flipperdevices/flipper > > This project is developed independently and is not affiliated with Flipper Devices. > -> Also be aware, DarkFlippers/unleashed-firmware is the only official page of the project, there is no paid, premium or closed source versions and if someone contacts you and say they are from our team and try to offer something like that - they are scammers, block that users ASAP +> Also be aware, DarkFlippers/unleashed-firmware is the only official page of the project, there is no paid, premium or closed source versions and if someone contacts you and say they are from our team and try to offer something like that - they are scammers, block that user ASAP
From 7fd30911fe2f27f5f6e26d4fae3e8e67d9200b48 Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Fri, 21 Nov 2025 16:58:38 +0700 Subject: [PATCH 02/18] Start working on subghz counter editor --- .../main/subghz/scenes/subghz_scene_config.h | 1 + .../subghz/scenes/subghz_scene_saved_menu.c | 4 +- .../scenes/subghz_scene_signal_settings.c | 22 ++ .../subghz_scene_signal_settings_counter.c | 233 ++++++++++++++++++ 4 files changed, 258 insertions(+), 2 deletions(-) create mode 100644 applications/main/subghz/scenes/subghz_scene_signal_settings_counter.c diff --git a/applications/main/subghz/scenes/subghz_scene_config.h b/applications/main/subghz/scenes/subghz_scene_config.h index 7be062eba..2a72ca6d6 100644 --- a/applications/main/subghz/scenes/subghz_scene_config.h +++ b/applications/main/subghz/scenes/subghz_scene_config.h @@ -26,3 +26,4 @@ ADD_SCENE(subghz, delete_raw, DeleteRAW) ADD_SCENE(subghz, need_saving, NeedSaving) ADD_SCENE(subghz, rpc, Rpc) ADD_SCENE(subghz, signal_settings, SignalSettings) +ADD_SCENE(subghz, signal_settings_counter, SignalSettingsCounter) diff --git a/applications/main/subghz/scenes/subghz_scene_saved_menu.c b/applications/main/subghz/scenes/subghz_scene_saved_menu.c index 0e753dcaf..b3ae563d5 100644 --- a/applications/main/subghz/scenes/subghz_scene_saved_menu.c +++ b/applications/main/subghz/scenes/subghz_scene_saved_menu.c @@ -34,14 +34,14 @@ void subghz_scene_saved_menu_on_enter(void* context) { SubmenuIndexDelete, subghz_scene_saved_menu_submenu_callback, subghz); - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + // if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { submenu_add_item( subghz->submenu, "Signal Settings", SubmenuIndexSignalSettings, subghz_scene_saved_menu_submenu_callback, subghz); - }; + // }; submenu_set_selected_item( subghz->submenu, scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneSavedMenu)); diff --git a/applications/main/subghz/scenes/subghz_scene_signal_settings.c b/applications/main/subghz/scenes/subghz_scene_signal_settings.c index 6c0b71acf..14abaeb6b 100644 --- a/applications/main/subghz/scenes/subghz_scene_signal_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_signal_settings.c @@ -49,6 +49,16 @@ void subghz_scene_signal_settings_counter_mode_changed(VariableItem* item) { counter_mode = counter_mode_value[index]; } +void subghz_scene_signal_settings_variable_item_list_enter_callback(void* context, uint32_t index) { + SubGhz* subghz = context; + + if(index == 1) { + // view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdByteInput); + // view_dispatcher_send_custom_event(subghz->view_dispatcher, 13); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSignalSettingsCounter); + } +} + void subghz_scene_signal_settings_on_enter(void* context) { // When we open saved file we do some check and fill up subghz->file_path. // So now we use it to check is there CounterMode in file or not @@ -98,6 +108,8 @@ void subghz_scene_signal_settings_on_enter(void* context) { int32_t value_index; VariableItem* item; + variable_item_list_set_enter_callback (variable_item_list,subghz_scene_signal_settings_variable_item_list_enter_callback,subghz); + item = variable_item_list_add( variable_item_list, "Counter Mode", @@ -110,6 +122,16 @@ void subghz_scene_signal_settings_on_enter(void* context) { variable_item_set_current_value_text(item, counter_mode_text[value_index]); variable_item_set_locked(item, (counter_mode == 0xff), "Not available\nfor this\nprotocol !"); + item = variable_item_list_add( + variable_item_list, + "Edit Counter", + 1, + NULL, + subghz); + variable_item_set_current_value_index(item, 0); + variable_item_set_current_value_text(item, "----"); + + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList); } diff --git a/applications/main/subghz/scenes/subghz_scene_signal_settings_counter.c b/applications/main/subghz/scenes/subghz_scene_signal_settings_counter.c new file mode 100644 index 000000000..2d9398e4d --- /dev/null +++ b/applications/main/subghz/scenes/subghz_scene_signal_settings_counter.c @@ -0,0 +1,233 @@ +#include "../subghz_i.h" +#include "../helpers/subghz_txrx_create_protocol_key.h" + +#include + +#define TAG "SubGhzSignalSettingsCounter" + +void subghz_scene_signal_settings_counter_byte_input_callback(void* context) { + SubGhz* subghz = context; + + view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventByteInputDone); +} + +void subghz_scene_signal_settings_counter_on_enter(void* context) { + SubGhz* subghz = context; + + uint8_t* byte_ptr = NULL; + uint8_t byte_count = 0; + + switch(subghz->gen_info->type) { + case GenFaacSLH: + byte_ptr = (uint8_t*)&subghz->gen_info->faac_slh.cnt; + byte_count = sizeof(subghz->gen_info->faac_slh.cnt); + break; + case GenKeeloq: + byte_ptr = (uint8_t*)&subghz->gen_info->keeloq.cnt; + byte_count = sizeof(subghz->gen_info->keeloq.cnt); + break; + case GenCameAtomo: + byte_ptr = (uint8_t*)&subghz->gen_info->came_atomo.cnt; + byte_count = sizeof(subghz->gen_info->came_atomo.cnt); + break; + case GenKeeloqBFT: + byte_ptr = (uint8_t*)&subghz->gen_info->keeloq_bft.cnt; + byte_count = sizeof(subghz->gen_info->keeloq_bft.cnt); + break; + case GenAlutechAt4n: + byte_ptr = (uint8_t*)&subghz->gen_info->alutech_at_4n.cnt; + byte_count = sizeof(subghz->gen_info->alutech_at_4n.cnt); + break; + case GenSomfyTelis: + byte_ptr = (uint8_t*)&subghz->gen_info->somfy_telis.cnt; + byte_count = sizeof(subghz->gen_info->somfy_telis.cnt); + break; + case GenNiceFlorS: + byte_ptr = (uint8_t*)&subghz->gen_info->nice_flor_s.cnt; + byte_count = sizeof(subghz->gen_info->nice_flor_s.cnt); + break; + case GenSecPlus2: + byte_ptr = (uint8_t*)&subghz->gen_info->sec_plus_2.cnt; + byte_count = sizeof(subghz->gen_info->sec_plus_2.cnt); + break; + case GenPhoenixV2: + byte_ptr = (uint8_t*)&subghz->gen_info->phoenix_v2.cnt; + byte_count = sizeof(subghz->gen_info->phoenix_v2.cnt); + break; + // Not needed for these types + case GenData: + case GenSecPlus1: + default: + furi_crash("fuck!"); + break; + } + + furi_assert(byte_ptr); + furi_assert(byte_count > 0); + + if(byte_count == 2) { + *((uint16_t*)byte_ptr) = __bswap16(*((uint16_t*)byte_ptr)); // Convert + } else if(byte_count == 4) { + *((uint32_t*)byte_ptr) = __bswap32(*((uint32_t*)byte_ptr)); // Convert + } + + // Setup view + ByteInput* byte_input = subghz->byte_input; + byte_input_set_header_text(byte_input, "Enter COUNTER in hex"); + + byte_input_set_result_callback( + byte_input, + subghz_scene_signal_settings_counter_byte_input_callback, + NULL, + subghz, + byte_ptr, + byte_count); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdByteInput); +} + +bool subghz_scene_signal_settings_counter_on_event(void* context, SceneManagerEvent event) { + SubGhz* subghz = context; + bool consumed = false; + bool generated_protocol = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubGhzCustomEventByteInputDone) { + // Swap bytes + switch(subghz->gen_info->type) { + case GenFaacSLH: + subghz->gen_info->faac_slh.cnt = __bswap32(subghz->gen_info->faac_slh.cnt); + break; + case GenKeeloq: + subghz->gen_info->keeloq.cnt = __bswap16(subghz->gen_info->keeloq.cnt); + break; + case GenCameAtomo: + subghz->gen_info->came_atomo.cnt = __bswap16(subghz->gen_info->came_atomo.cnt); + break; + case GenKeeloqBFT: + subghz->gen_info->keeloq_bft.cnt = __bswap16(subghz->gen_info->keeloq_bft.cnt); + break; + case GenAlutechAt4n: + subghz->gen_info->alutech_at_4n.cnt = + __bswap16(subghz->gen_info->alutech_at_4n.cnt); + break; + case GenSomfyTelis: + subghz->gen_info->somfy_telis.cnt = __bswap16(subghz->gen_info->somfy_telis.cnt); + break; + case GenNiceFlorS: + subghz->gen_info->nice_flor_s.cnt = __bswap16(subghz->gen_info->nice_flor_s.cnt); + break; + case GenSecPlus2: + subghz->gen_info->sec_plus_2.cnt = __bswap32(subghz->gen_info->sec_plus_2.cnt); + break; + case GenPhoenixV2: + subghz->gen_info->phoenix_v2.cnt = __bswap16(subghz->gen_info->phoenix_v2.cnt); + break; + // Not needed for these types + case GenData: + case GenSecPlus1: + default: + furi_crash("Not implemented"); + break; + } + + switch(subghz->gen_info->type) { + case GenFaacSLH: + case GenKeeloqBFT: + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSetSeed); + return true; + case GenKeeloq: + generated_protocol = subghz_txrx_gen_keeloq_protocol( + subghz->txrx, + subghz->gen_info->mod, + subghz->gen_info->freq, + subghz->gen_info->keeloq.serial, + subghz->gen_info->keeloq.btn, + subghz->gen_info->keeloq.cnt, + subghz->gen_info->keeloq.manuf); + break; + case GenCameAtomo: + generated_protocol = subghz_txrx_gen_came_atomo_protocol( + subghz->txrx, + subghz->gen_info->mod, + subghz->gen_info->freq, + subghz->gen_info->came_atomo.serial, + subghz->gen_info->came_atomo.cnt); + break; + case GenAlutechAt4n: + generated_protocol = subghz_txrx_gen_alutech_at_4n_protocol( + subghz->txrx, + subghz->gen_info->mod, + subghz->gen_info->freq, + subghz->gen_info->alutech_at_4n.serial, + subghz->gen_info->alutech_at_4n.btn, + subghz->gen_info->alutech_at_4n.cnt); + break; + case GenSomfyTelis: + generated_protocol = subghz_txrx_gen_somfy_telis_protocol( + subghz->txrx, + subghz->gen_info->mod, + subghz->gen_info->freq, + subghz->gen_info->somfy_telis.serial, + subghz->gen_info->somfy_telis.btn, + subghz->gen_info->somfy_telis.cnt); + break; + case GenNiceFlorS: + generated_protocol = subghz_txrx_gen_nice_flor_s_protocol( + subghz->txrx, + subghz->gen_info->mod, + subghz->gen_info->freq, + subghz->gen_info->nice_flor_s.serial, + subghz->gen_info->nice_flor_s.btn, + subghz->gen_info->nice_flor_s.cnt, + subghz->gen_info->nice_flor_s.nice_one); + break; + case GenSecPlus2: + generated_protocol = subghz_txrx_gen_secplus_v2_protocol( + subghz->txrx, + subghz->gen_info->mod, + subghz->gen_info->freq, + subghz->gen_info->sec_plus_2.serial, + subghz->gen_info->sec_plus_2.btn, + subghz->gen_info->sec_plus_2.cnt); + break; + case GenPhoenixV2: + generated_protocol = subghz_txrx_gen_phoenix_v2_protocol( + subghz->txrx, + subghz->gen_info->mod, + subghz->gen_info->freq, + subghz->gen_info->phoenix_v2.serial, + subghz->gen_info->phoenix_v2.cnt); + break; + // Not needed for these types + case GenData: + case GenSecPlus1: + default: + furi_crash("Not implemented"); + break; + } + + consumed = true; + + if(!generated_protocol) { + furi_string_set( + subghz->error_str, "Function requires\nan SD card with\nfresh databases."); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); + } else { + subghz_file_name_clear(subghz); + + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneSetType, SubGhzCustomEventManagerSet); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); + } + } + } + return consumed; +} + +void subghz_scene_signal_settings_counter_on_exit(void* context) { + SubGhz* subghz = context; + + // Clear view + byte_input_set_result_callback(subghz->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(subghz->byte_input, ""); +} From 259efadaea8b4432d1b094d4a2b44c987ea5ee6d Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 25 Nov 2025 19:09:11 +0300 Subject: [PATCH 03/18] upd changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index deb9905cd..95ec7f98e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * SubGHz: Add experimental counter overflow mode (OFEX), replicates how some key duplicators work, DO NOT USE if you don't know what you are doing, it will reset your counter value! (accesible with debug on in radio settings - counter incr.) * SubGHz: Return Honeywell Sec with fixes and improvements (by htotoo & LiQuiDz & xMasterX) * Display: Remove display_back_light bug from "DisplayBacklightEnforceOn" (PR #928 | by @Dmitry422) +* OFW: Fix Felica standard loading from nfc file * OFW PR 4279: NFC FeliCa Minor Fix: FelicaPollerEventType should only be Incomplete if the tag is FeliCa Lite (by @zinongli) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes From a493612444a3ff161bd628d3dde4a41a3e4f16f0 Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Fri, 28 Nov 2025 20:13:56 +0700 Subject: [PATCH 04/18] From work to home --- .../subghz_scene_signal_settings_counter.c | 108 +++++++++--------- 1 file changed, 56 insertions(+), 52 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_signal_settings_counter.c b/applications/main/subghz/scenes/subghz_scene_signal_settings_counter.c index 2d9398e4d..d5f1ab511 100644 --- a/applications/main/subghz/scenes/subghz_scene_signal_settings_counter.c +++ b/applications/main/subghz/scenes/subghz_scene_signal_settings_counter.c @@ -2,8 +2,11 @@ #include "../helpers/subghz_txrx_create_protocol_key.h" #include +#include +#define TAG "SubGhzSceneSignalSettingsCounter" -#define TAG "SubGhzSignalSettingsCounter" +uint32_t counter32 = 0x0; +uint16_t counter16 = 0x0; void subghz_scene_signal_settings_counter_byte_input_callback(void* context) { SubGhz* subghz = context; @@ -14,66 +17,62 @@ void subghz_scene_signal_settings_counter_byte_input_callback(void* context) { void subghz_scene_signal_settings_counter_on_enter(void* context) { SubGhz* subghz = context; - uint8_t* byte_ptr = NULL; + FuriString* text = furi_string_alloc(); + FuriString* textCnt = furi_string_alloc(); uint8_t byte_count = 0; + uint8_t* byte_ptr = NULL; + + subghz_protocol_decoder_base_get_string(subghz_txrx_get_decoder(subghz->txrx), text); + FURI_LOG_D(TAG, furi_string_get_cstr(text)); - switch(subghz->gen_info->type) { - case GenFaacSLH: - byte_ptr = (uint8_t*)&subghz->gen_info->faac_slh.cnt; - byte_count = sizeof(subghz->gen_info->faac_slh.cnt); - break; - case GenKeeloq: - byte_ptr = (uint8_t*)&subghz->gen_info->keeloq.cnt; - byte_count = sizeof(subghz->gen_info->keeloq.cnt); - break; - case GenCameAtomo: - byte_ptr = (uint8_t*)&subghz->gen_info->came_atomo.cnt; - byte_count = sizeof(subghz->gen_info->came_atomo.cnt); - break; - case GenKeeloqBFT: - byte_ptr = (uint8_t*)&subghz->gen_info->keeloq_bft.cnt; - byte_count = sizeof(subghz->gen_info->keeloq_bft.cnt); - break; - case GenAlutechAt4n: - byte_ptr = (uint8_t*)&subghz->gen_info->alutech_at_4n.cnt; - byte_count = sizeof(subghz->gen_info->alutech_at_4n.cnt); - break; - case GenSomfyTelis: - byte_ptr = (uint8_t*)&subghz->gen_info->somfy_telis.cnt; - byte_count = sizeof(subghz->gen_info->somfy_telis.cnt); - break; - case GenNiceFlorS: - byte_ptr = (uint8_t*)&subghz->gen_info->nice_flor_s.cnt; - byte_count = sizeof(subghz->gen_info->nice_flor_s.cnt); - break; - case GenSecPlus2: - byte_ptr = (uint8_t*)&subghz->gen_info->sec_plus_2.cnt; - byte_count = sizeof(subghz->gen_info->sec_plus_2.cnt); - break; - case GenPhoenixV2: - byte_ptr = (uint8_t*)&subghz->gen_info->phoenix_v2.cnt; - byte_count = sizeof(subghz->gen_info->phoenix_v2.cnt); - break; - // Not needed for these types - case GenData: - case GenSecPlus1: - default: - furi_crash("fuck!"); - break; + // In protocols output we allways have HEX format for "Cnt:" output (text formating like ...Cnt:%05lX\r\n") + // Both "Cnt:0x1111" and "Cnt:1111" always mean 0x1111 hex value, so we just use part after "0x" + // we take 8 simbols starting from "Cnt:0x........" or "Cnt:........" + // default value for textcnt "0000" + furi_string_set_str(textCnt, "0000"); + int8_t cnt_place = furi_string_search_str(text, "Cnt:0x", 0); + + if(cnt_place > 0) { + furi_string_set_n(textCnt, text, cnt_place + 6, 8); + } else { + cnt_place = furi_string_search_str(text, "Cnt:", 0); + if(cnt_place > 0) { + furi_string_set_n(textCnt, text, cnt_place + 4, 8); + } + } + ///// Добавить проверку Cnt:????\r\n" + + + + furi_string_trim(textCnt); + FURI_LOG_D(TAG,"Counter from file %s", furi_string_get_cstr(textCnt)); + + //convert 8 simbols string to uint based on base 16 (hex); + strint_to_uint32(furi_string_get_cstr(textCnt), NULL, &counter32, 16); + + // Check will be counter 2(less than 65535) or 4 (more than 65535) hex bytes long and use corresponding variable for ByteInput + // To correct display hex value we must revert bytes for ByteInput view and display 2 or 4 hex bytes to edit + if (counter32 > 0xFFFF) { + byte_count = 4; + counter32 = __bswap32(counter32); + byte_ptr = (uint8_t*)&counter32; + + } else { + counter16 = counter32; + byte_count = 2; + counter16 = __bswap16(counter16); + byte_ptr = (uint8_t*)&counter16; } + FURI_LOG_D(TAG,"Byte count %i",byte_count); + FURI_LOG_D(TAG, "Counter Int %li", counter32); + furi_assert(byte_ptr); furi_assert(byte_count > 0); - if(byte_count == 2) { - *((uint16_t*)byte_ptr) = __bswap16(*((uint16_t*)byte_ptr)); // Convert - } else if(byte_count == 4) { - *((uint32_t*)byte_ptr) = __bswap32(*((uint32_t*)byte_ptr)); // Convert - } - // Setup view ByteInput* byte_input = subghz->byte_input; - byte_input_set_header_text(byte_input, "Enter COUNTER in hex"); + byte_input_set_header_text(byte_input, "Enter COUNTER in HEX"); byte_input_set_result_callback( byte_input, @@ -82,7 +81,12 @@ void subghz_scene_signal_settings_counter_on_enter(void* context) { subghz, byte_ptr, byte_count); + + furi_string_free(text); + furi_string_free(textCnt); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdByteInput); + } bool subghz_scene_signal_settings_counter_on_event(void* context, SceneManagerEvent event) { From b8bec12974f8a190adf1a415106601e842878737 Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Sun, 30 Nov 2025 21:57:10 +0700 Subject: [PATCH 05/18] from home to work. one step from the end --- .../main/subghz/scenes/subghz_scene_config.h | 3 +- .../scenes/subghz_scene_signal_settings.c | 191 ++++++++++++-- .../subghz_scene_signal_settings_counter.c | 237 ------------------ lib/subghz/protocols/alutech_at_4n.c | 2 +- lib/subghz/protocols/came_atomo.c | 2 +- lib/subghz/protocols/hay21.c | 8 +- lib/subghz/protocols/kinggates_stylo_4k.c | 2 +- lib/subghz/protocols/phoenix_v2.c | 4 +- lib/subghz/protocols/secplus_v1.c | 4 +- lib/subghz/protocols/secplus_v2.c | 2 +- lib/subghz/protocols/somfy_keytis.c | 2 +- lib/subghz/protocols/somfy_telis.c | 2 +- 12 files changed, 187 insertions(+), 272 deletions(-) delete mode 100644 applications/main/subghz/scenes/subghz_scene_signal_settings_counter.c diff --git a/applications/main/subghz/scenes/subghz_scene_config.h b/applications/main/subghz/scenes/subghz_scene_config.h index 2a72ca6d6..5d7bf31ff 100644 --- a/applications/main/subghz/scenes/subghz_scene_config.h +++ b/applications/main/subghz/scenes/subghz_scene_config.h @@ -25,5 +25,4 @@ ADD_SCENE(subghz, decode_raw, DecodeRAW) ADD_SCENE(subghz, delete_raw, DeleteRAW) ADD_SCENE(subghz, need_saving, NeedSaving) ADD_SCENE(subghz, rpc, Rpc) -ADD_SCENE(subghz, signal_settings, SignalSettings) -ADD_SCENE(subghz, signal_settings_counter, SignalSettingsCounter) +ADD_SCENE(subghz, signal_settings, SignalSettings) \ No newline at end of file diff --git a/applications/main/subghz/scenes/subghz_scene_signal_settings.c b/applications/main/subghz/scenes/subghz_scene_signal_settings.c index 14abaeb6b..6ce14c92c 100644 --- a/applications/main/subghz/scenes/subghz_scene_signal_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_signal_settings.c @@ -2,10 +2,18 @@ #include "subghz/types.h" #include "../helpers/subghz_custom_event.h" #include +#include +#include +#include #define TAG "SubGhzSceneSignalSettings" static uint32_t counter_mode = 0xff; +static uint32_t loaded_counter32 = 0x0; +static uint32_t counter32 = 0x0; +static uint16_t counter16 = 0x0; +static uint8_t byte_count = 0; +static uint8_t* byte_ptr = NULL; #define COUNTER_MODE_COUNT 7 static const char* const counter_mode_text[COUNTER_MODE_COUNT] = { @@ -48,22 +56,40 @@ void subghz_scene_signal_settings_counter_mode_changed(VariableItem* item) { variable_item_set_current_value_text(item, counter_mode_text[index]); counter_mode = counter_mode_value[index]; } +void subghz_scene_signal_settings_byte_input_callback(void* context) { + SubGhz* subghz = context; + view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventByteInputDone); +} void subghz_scene_signal_settings_variable_item_list_enter_callback(void* context, uint32_t index) { SubGhz* subghz = context; if(index == 1) { - // view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdByteInput); - // view_dispatcher_send_custom_event(subghz->view_dispatcher, 13); - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSignalSettingsCounter); + // Setup byte_input view + ByteInput* byte_input = subghz->byte_input; + byte_input_set_header_text(byte_input, "Enter counter in HEX"); + + byte_input_set_result_callback( + byte_input, + subghz_scene_signal_settings_byte_input_callback, + NULL, + subghz, + byte_ptr, + byte_count); + + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdByteInput); + // view_dispatcher_send_custom_event(subghz->view_dispatcher, 13); + // scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSignalSettingsCounter); } } void subghz_scene_signal_settings_on_enter(void* context) { - // When we open saved file we do some check and fill up subghz->file_path. - // So now we use it to check is there CounterMode in file or not SubGhz* subghz = context; + // ### Counter mode section ### + + // When we open saved file we do some check and fill up subghz->file_path. + // So now we use it to check is there CounterMode in file or not const char* file_path = furi_string_get_cstr(subghz->file_path); furi_assert(subghz); @@ -108,7 +134,13 @@ void subghz_scene_signal_settings_on_enter(void* context) { int32_t value_index; VariableItem* item; - variable_item_list_set_enter_callback (variable_item_list,subghz_scene_signal_settings_variable_item_list_enter_callback,subghz); + variable_item_list_set_selected_item(subghz->variable_item_list, 0); + variable_item_list_reset(subghz->variable_item_list); + + variable_item_list_set_enter_callback( + variable_item_list, + subghz_scene_signal_settings_variable_item_list_enter_callback, + subghz); item = variable_item_list_add( variable_item_list, @@ -122,26 +154,144 @@ void subghz_scene_signal_settings_on_enter(void* context) { variable_item_set_current_value_text(item, counter_mode_text[value_index]); variable_item_set_locked(item, (counter_mode == 0xff), "Not available\nfor this\nprotocol !"); - item = variable_item_list_add( - variable_item_list, - "Edit Counter", - 1, - NULL, - subghz); - variable_item_set_current_value_index(item, 0); - variable_item_set_current_value_text(item, "----"); + // ### Counter edit section ### + FuriString* tmp_text = furi_string_alloc_set_str(""); + FuriString* textCnt = furi_string_alloc_set_str(""); + bool counter_not_available = true; + // decode loaded sugbhz file and take information string from decoder + subghz_protocol_decoder_base_get_string(subghz_txrx_get_decoder(subghz->txrx), tmp_text); + + // In protocols output we allways have HEX format for "Cnt:" output (text formating like ...Cnt:%05lX\r\n") + // we take 8 simbols starting from "Cnt:........" + // at first we search "Cnt:????" that mean for this protocol counter cannot be decoded + + int8_t place = furi_string_search_str(tmp_text, "Cnt:??", 0); + if(place > 0) { + FURI_LOG_D(TAG, "Founded Cnt:???? - counter not available for this protocol"); + } else { + place = furi_string_search_str(tmp_text, "Cnt:", 0); + if(place > 0) { + furi_string_set_n(textCnt, tmp_text, place + 4, 8); + FURI_LOG_D( + TAG, "Found 8 bytes string starting with Cnt:%s", furi_string_get_cstr(textCnt)); + counter_not_available = false; + // trim and convert 8 simbols string to uint32 by base 16 (hex) by strint_to_uint32(); + // later we use loaded_counter in subghz_scene_signal_settings_on_event to check is there 0 or not - special case + strint_to_uint32(furi_string_get_cstr(textCnt), NULL, &loaded_counter32, 16); + + // Check it there counter 2 (less than 65535) or 4 (more than 65535) hex bytes long and use corresponding variable for ByteInput + // To show hex value we must revert bytes for ByteInput view and display 2 or 4 hex bytes to edit + if(counter32 > 0xFFFF) { + byte_count = 4; + counter32 = loaded_counter32; + furi_string_printf(tmp_text, "%08lX", counter32); + FURI_LOG_D(TAG, "Byte count %i", byte_count); + FURI_LOG_D(TAG, "Counter DEC %li, HEX %lX", counter32, counter32); + counter32 = __bswap32(counter32); + byte_ptr = (uint8_t*)&counter32; + } else { + counter16 = loaded_counter32; + byte_count = 2; + furi_string_printf(tmp_text, "%04X", counter16); + FURI_LOG_D(TAG, "Byte count %i", byte_count); + FURI_LOG_D(TAG, "Counter DEC %i, HEX %X", counter16, counter16); + counter16 = __bswap16(counter16); + byte_ptr = (uint8_t*)&counter16; + } + } else { + FURI_LOG_D(TAG, "Counter not available for this protocol"); + } + } + + furi_assert(byte_ptr); + furi_assert(byte_count > 0); + + item = variable_item_list_add(variable_item_list, "Edit Counter", 1, NULL, subghz); + variable_item_set_current_value_index(item, 0); + variable_item_set_current_value_text(item, furi_string_get_cstr(tmp_text)); + variable_item_set_locked(item, (counter_not_available), "Not available\nfor this\nprotocol !"); + + furi_string_free(tmp_text); + furi_string_free(textCnt); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList); } bool subghz_scene_signal_settings_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; - if(event.type == SceneManagerEventTypeBack) { - scene_manager_previous_scene(subghz->scene_manager); - return true; - } else - return false; + int32_t tmp_counter = 0; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubGhzCustomEventByteInputDone) { + switch(byte_count) { + case 2: + // when readed from file original signal has Cnt:00 we can step to 0000+FFFF = FFFF, but we need 0000 for next step + // for this case we must use +1 additional step to increace Cnt from FFFF to 0000. + + // save current user definded counter increase value (mult) + tmp_counter = furi_hal_subghz_get_rolling_counter_mult(); + + // increase signal counter to max value - at result it must be 0000 in most cases + // but can be FFFF in case Cnt:0000 (for this we have +1 additional step + furi_hal_subghz_set_rolling_counter_mult(0xFFFF); + subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx)); + subghz_txrx_stop(subghz->txrx); + + // if file Cnt:00 then do +1 additional step + if(loaded_counter32 == 0) { + furi_hal_subghz_set_rolling_counter_mult(1); + subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx)); + subghz_txrx_stop(subghz->txrx); + } + // at this point we have signal Cnt:00 + // convert back after byte_view and do one send with our new mult (counter16) - at end we must have signal Cnt = counter16 + counter16 = __bswap16(counter16); + if(counter16 > 0) { + furi_hal_subghz_set_rolling_counter_mult(counter16); + subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx)); + subghz_txrx_stop(subghz->txrx); + } + // restore user definded counter increase value (mult) + furi_hal_subghz_set_rolling_counter_mult(tmp_counter); + break; + case 4: + // the same for 32 bit Counter + tmp_counter = furi_hal_subghz_get_rolling_counter_mult(); + + furi_hal_subghz_set_rolling_counter_mult(0xFFFFFFFF); + subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx)); + subghz_txrx_stop(subghz->txrx); + + if(loaded_counter32 == 0) { + furi_hal_subghz_set_rolling_counter_mult(1); + subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx)); + subghz_txrx_stop(subghz->txrx); + } + + counter32 = __bswap32(counter32); + if(counter32 > 0) { + furi_hal_subghz_set_rolling_counter_mult(counter32); + subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx)); + subghz_txrx_stop(subghz->txrx); + } + furi_hal_subghz_set_rolling_counter_mult(tmp_counter); + break; + default: + break; + } + // ??????????????????? + scene_manager_previous_scene(subghz->scene_manager); + return true; + + } else { + if(event.type == SceneManagerEventTypeBack) { + scene_manager_previous_scene(subghz->scene_manager); + return true; + } + } + } + return false; } void subghz_scene_signal_settings_on_exit(void* context) { @@ -174,6 +324,9 @@ void subghz_scene_signal_settings_on_exit(void* context) { furi_record_close(RECORD_STORAGE); } + // Clear views variable_item_list_set_selected_item(subghz->variable_item_list, 0); variable_item_list_reset(subghz->variable_item_list); + byte_input_set_result_callback(subghz->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(subghz->byte_input, ""); } diff --git a/applications/main/subghz/scenes/subghz_scene_signal_settings_counter.c b/applications/main/subghz/scenes/subghz_scene_signal_settings_counter.c deleted file mode 100644 index d5f1ab511..000000000 --- a/applications/main/subghz/scenes/subghz_scene_signal_settings_counter.c +++ /dev/null @@ -1,237 +0,0 @@ -#include "../subghz_i.h" -#include "../helpers/subghz_txrx_create_protocol_key.h" - -#include -#include -#define TAG "SubGhzSceneSignalSettingsCounter" - -uint32_t counter32 = 0x0; -uint16_t counter16 = 0x0; - -void subghz_scene_signal_settings_counter_byte_input_callback(void* context) { - SubGhz* subghz = context; - - view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventByteInputDone); -} - -void subghz_scene_signal_settings_counter_on_enter(void* context) { - SubGhz* subghz = context; - - FuriString* text = furi_string_alloc(); - FuriString* textCnt = furi_string_alloc(); - uint8_t byte_count = 0; - uint8_t* byte_ptr = NULL; - - subghz_protocol_decoder_base_get_string(subghz_txrx_get_decoder(subghz->txrx), text); - FURI_LOG_D(TAG, furi_string_get_cstr(text)); - - // In protocols output we allways have HEX format for "Cnt:" output (text formating like ...Cnt:%05lX\r\n") - // Both "Cnt:0x1111" and "Cnt:1111" always mean 0x1111 hex value, so we just use part after "0x" - // we take 8 simbols starting from "Cnt:0x........" or "Cnt:........" - // default value for textcnt "0000" - furi_string_set_str(textCnt, "0000"); - int8_t cnt_place = furi_string_search_str(text, "Cnt:0x", 0); - - if(cnt_place > 0) { - furi_string_set_n(textCnt, text, cnt_place + 6, 8); - } else { - cnt_place = furi_string_search_str(text, "Cnt:", 0); - if(cnt_place > 0) { - furi_string_set_n(textCnt, text, cnt_place + 4, 8); - } - } - ///// Добавить проверку Cnt:????\r\n" - - - - furi_string_trim(textCnt); - FURI_LOG_D(TAG,"Counter from file %s", furi_string_get_cstr(textCnt)); - - //convert 8 simbols string to uint based on base 16 (hex); - strint_to_uint32(furi_string_get_cstr(textCnt), NULL, &counter32, 16); - - // Check will be counter 2(less than 65535) or 4 (more than 65535) hex bytes long and use corresponding variable for ByteInput - // To correct display hex value we must revert bytes for ByteInput view and display 2 or 4 hex bytes to edit - if (counter32 > 0xFFFF) { - byte_count = 4; - counter32 = __bswap32(counter32); - byte_ptr = (uint8_t*)&counter32; - - } else { - counter16 = counter32; - byte_count = 2; - counter16 = __bswap16(counter16); - byte_ptr = (uint8_t*)&counter16; - } - - FURI_LOG_D(TAG,"Byte count %i",byte_count); - FURI_LOG_D(TAG, "Counter Int %li", counter32); - - furi_assert(byte_ptr); - furi_assert(byte_count > 0); - - // Setup view - ByteInput* byte_input = subghz->byte_input; - byte_input_set_header_text(byte_input, "Enter COUNTER in HEX"); - - byte_input_set_result_callback( - byte_input, - subghz_scene_signal_settings_counter_byte_input_callback, - NULL, - subghz, - byte_ptr, - byte_count); - - furi_string_free(text); - furi_string_free(textCnt); - - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdByteInput); - -} - -bool subghz_scene_signal_settings_counter_on_event(void* context, SceneManagerEvent event) { - SubGhz* subghz = context; - bool consumed = false; - bool generated_protocol = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubGhzCustomEventByteInputDone) { - // Swap bytes - switch(subghz->gen_info->type) { - case GenFaacSLH: - subghz->gen_info->faac_slh.cnt = __bswap32(subghz->gen_info->faac_slh.cnt); - break; - case GenKeeloq: - subghz->gen_info->keeloq.cnt = __bswap16(subghz->gen_info->keeloq.cnt); - break; - case GenCameAtomo: - subghz->gen_info->came_atomo.cnt = __bswap16(subghz->gen_info->came_atomo.cnt); - break; - case GenKeeloqBFT: - subghz->gen_info->keeloq_bft.cnt = __bswap16(subghz->gen_info->keeloq_bft.cnt); - break; - case GenAlutechAt4n: - subghz->gen_info->alutech_at_4n.cnt = - __bswap16(subghz->gen_info->alutech_at_4n.cnt); - break; - case GenSomfyTelis: - subghz->gen_info->somfy_telis.cnt = __bswap16(subghz->gen_info->somfy_telis.cnt); - break; - case GenNiceFlorS: - subghz->gen_info->nice_flor_s.cnt = __bswap16(subghz->gen_info->nice_flor_s.cnt); - break; - case GenSecPlus2: - subghz->gen_info->sec_plus_2.cnt = __bswap32(subghz->gen_info->sec_plus_2.cnt); - break; - case GenPhoenixV2: - subghz->gen_info->phoenix_v2.cnt = __bswap16(subghz->gen_info->phoenix_v2.cnt); - break; - // Not needed for these types - case GenData: - case GenSecPlus1: - default: - furi_crash("Not implemented"); - break; - } - - switch(subghz->gen_info->type) { - case GenFaacSLH: - case GenKeeloqBFT: - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSetSeed); - return true; - case GenKeeloq: - generated_protocol = subghz_txrx_gen_keeloq_protocol( - subghz->txrx, - subghz->gen_info->mod, - subghz->gen_info->freq, - subghz->gen_info->keeloq.serial, - subghz->gen_info->keeloq.btn, - subghz->gen_info->keeloq.cnt, - subghz->gen_info->keeloq.manuf); - break; - case GenCameAtomo: - generated_protocol = subghz_txrx_gen_came_atomo_protocol( - subghz->txrx, - subghz->gen_info->mod, - subghz->gen_info->freq, - subghz->gen_info->came_atomo.serial, - subghz->gen_info->came_atomo.cnt); - break; - case GenAlutechAt4n: - generated_protocol = subghz_txrx_gen_alutech_at_4n_protocol( - subghz->txrx, - subghz->gen_info->mod, - subghz->gen_info->freq, - subghz->gen_info->alutech_at_4n.serial, - subghz->gen_info->alutech_at_4n.btn, - subghz->gen_info->alutech_at_4n.cnt); - break; - case GenSomfyTelis: - generated_protocol = subghz_txrx_gen_somfy_telis_protocol( - subghz->txrx, - subghz->gen_info->mod, - subghz->gen_info->freq, - subghz->gen_info->somfy_telis.serial, - subghz->gen_info->somfy_telis.btn, - subghz->gen_info->somfy_telis.cnt); - break; - case GenNiceFlorS: - generated_protocol = subghz_txrx_gen_nice_flor_s_protocol( - subghz->txrx, - subghz->gen_info->mod, - subghz->gen_info->freq, - subghz->gen_info->nice_flor_s.serial, - subghz->gen_info->nice_flor_s.btn, - subghz->gen_info->nice_flor_s.cnt, - subghz->gen_info->nice_flor_s.nice_one); - break; - case GenSecPlus2: - generated_protocol = subghz_txrx_gen_secplus_v2_protocol( - subghz->txrx, - subghz->gen_info->mod, - subghz->gen_info->freq, - subghz->gen_info->sec_plus_2.serial, - subghz->gen_info->sec_plus_2.btn, - subghz->gen_info->sec_plus_2.cnt); - break; - case GenPhoenixV2: - generated_protocol = subghz_txrx_gen_phoenix_v2_protocol( - subghz->txrx, - subghz->gen_info->mod, - subghz->gen_info->freq, - subghz->gen_info->phoenix_v2.serial, - subghz->gen_info->phoenix_v2.cnt); - break; - // Not needed for these types - case GenData: - case GenSecPlus1: - default: - furi_crash("Not implemented"); - break; - } - - consumed = true; - - if(!generated_protocol) { - furi_string_set( - subghz->error_str, "Function requires\nan SD card with\nfresh databases."); - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); - } else { - subghz_file_name_clear(subghz); - - scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneSetType, SubGhzCustomEventManagerSet); - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); - } - } - } - return consumed; -} - -void subghz_scene_signal_settings_counter_on_exit(void* context) { - SubGhz* subghz = context; - - // Clear view - byte_input_set_result_callback(subghz->byte_input, NULL, NULL, NULL, NULL, 0); - byte_input_set_header_text(subghz->byte_input, ""); -} diff --git a/lib/subghz/protocols/alutech_at_4n.c b/lib/subghz/protocols/alutech_at_4n.c index a966382e0..5d0f7a6b8 100644 --- a/lib/subghz/protocols/alutech_at_4n.c +++ b/lib/subghz/protocols/alutech_at_4n.c @@ -881,7 +881,7 @@ void subghz_protocol_decoder_alutech_at_4n_get_string(void* context, FuriString* "%s\r\n" "Key:0x%08lX%08lX\nCRC:%02X %dbit\r\n" "Sn:0x%08lX Btn:0x%01X\r\n" - "Cnt:0x%04lX\r\n", + "Cnt:%04lX\r\n", instance->generic.protocol_name, code_found_hi, code_found_lo, diff --git a/lib/subghz/protocols/came_atomo.c b/lib/subghz/protocols/came_atomo.c index 56ed6ce38..ea94e36be 100644 --- a/lib/subghz/protocols/came_atomo.c +++ b/lib/subghz/protocols/came_atomo.c @@ -831,7 +831,7 @@ void subghz_protocol_decoder_came_atomo_get_string(void* context, FuriString* ou "%s %db\r\n" "Key:%08lX%08lX\r\n" "Sn:0x%08lX Btn:%01X\r\n" - "Cnt:0x%04lX\r\n" + "Cnt:%04lX\r\n" "Btn_Cnt:0x%02X", instance->generic.protocol_name, diff --git a/lib/subghz/protocols/hay21.c b/lib/subghz/protocols/hay21.c index d622e4446..0d2c1059e 100644 --- a/lib/subghz/protocols/hay21.c +++ b/lib/subghz/protocols/hay21.c @@ -468,10 +468,10 @@ void subghz_protocol_decoder_hay21_get_string(void* context, FuriString* output) furi_string_cat_printf( output, "%s - %dbit\r\n" - "Key: 0x%06lX\r\n" - "Serial: 0x%02X\r\n" - "Btn: 0x%01X - %s\r\n" - "Cnt: 0x%01X\r\n", + "Key:0x%06lX\r\n" + "Serial:0x%02X\r\n" + "Btn:0x%01X - %s\r\n" + "Cnt:%01X\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)(instance->generic.data & 0xFFFFFFFF), diff --git a/lib/subghz/protocols/kinggates_stylo_4k.c b/lib/subghz/protocols/kinggates_stylo_4k.c index 77624f5dc..eccff1950 100644 --- a/lib/subghz/protocols/kinggates_stylo_4k.c +++ b/lib/subghz/protocols/kinggates_stylo_4k.c @@ -591,7 +591,7 @@ void subghz_protocol_decoder_kinggates_stylo_4k_get_string(void* context, FuriSt "%s\r\n" "Key:0x%llX%07llX %dbit\r\n" "Sn:0x%08lX Btn:0x%01X\r\n" - "Cnt:0x%04lX\r\n", + "Cnt:%04lX\r\n", instance->generic.protocol_name, instance->generic.data, instance->generic.data_2, diff --git a/lib/subghz/protocols/phoenix_v2.c b/lib/subghz/protocols/phoenix_v2.c index 5764871fb..afc2cbb82 100644 --- a/lib/subghz/protocols/phoenix_v2.c +++ b/lib/subghz/protocols/phoenix_v2.c @@ -599,8 +599,8 @@ void subghz_protocol_decoder_phoenix_v2_get_string(void* context, FuriString* ou "V2 Phoenix %dbit\r\n" "Key:%05lX%08lX\r\n" "Sn:0x%07lX \r\n" - "Cnt: 0x%04lX\r\n" - "Btn: %X\r\n", + "Cnt:0x%04lX\r\n" + "Btn:%X\r\n", instance->generic.data_count_bit, (uint32_t)(instance->generic.data >> 32) & 0xFFFFFFFF, (uint32_t)(instance->generic.data & 0xFFFFFFFF), diff --git a/lib/subghz/protocols/secplus_v1.c b/lib/subghz/protocols/secplus_v1.c index e1b21d4fd..21c9dbb1c 100644 --- a/lib/subghz/protocols/secplus_v1.c +++ b/lib/subghz/protocols/secplus_v1.c @@ -599,7 +599,7 @@ void subghz_protocol_decoder_secplus_v1_get_string(void* context, FuriString* ou furi_string_cat_printf( output, "Sn:0x%08lX\r\n" - "Cnt:0x%03lX " + "Cnt:%03lX " "SwID:0x%X\r\n", instance->generic.serial, instance->generic.cnt, @@ -618,7 +618,7 @@ void subghz_protocol_decoder_secplus_v1_get_string(void* context, FuriString* ou furi_string_cat_printf( output, "Sn:0x%08lX\r\n" - "Cnt:0x%03lX " + "Cnt:%03lX " "SwID:0x%X\r\n", instance->generic.serial, instance->generic.cnt, diff --git a/lib/subghz/protocols/secplus_v2.c b/lib/subghz/protocols/secplus_v2.c index 36fe95b81..ee4a19d7c 100644 --- a/lib/subghz/protocols/secplus_v2.c +++ b/lib/subghz/protocols/secplus_v2.c @@ -945,7 +945,7 @@ void subghz_protocol_decoder_secplus_v2_get_string(void* context, FuriString* ou "Pk1:0x%lX%08lX\r\n" "Pk2:0x%lX%08lX\r\n" "Sn:0x%08lX Btn:0x%01X\r\n" - "Cnt:0x%03lX\r\n", + "Cnt:%03lX\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, diff --git a/lib/subghz/protocols/somfy_keytis.c b/lib/subghz/protocols/somfy_keytis.c index 07f950095..1225f5d32 100644 --- a/lib/subghz/protocols/somfy_keytis.c +++ b/lib/subghz/protocols/somfy_keytis.c @@ -803,7 +803,7 @@ void subghz_protocol_decoder_somfy_keytis_get_string(void* context, FuriString* "%s %db\r\n" "%lX%08lX%06lX\r\n" "Sn:0x%06lX \r\n" - "Cnt:0x%04lX\r\n" + "Cnt:%04lX\r\n" "Btn:%s\r\n", instance->generic.protocol_name, diff --git a/lib/subghz/protocols/somfy_telis.c b/lib/subghz/protocols/somfy_telis.c index 4218ad8c5..2f9fd1f10 100644 --- a/lib/subghz/protocols/somfy_telis.c +++ b/lib/subghz/protocols/somfy_telis.c @@ -759,7 +759,7 @@ void subghz_protocol_decoder_somfy_telis_get_string(void* context, FuriString* o "%s %db\r\n" "Key:0x%lX%08lX\r\n" "Sn:0x%06lX \r\n" - "Cnt:0x%04lX\r\n" + "Cnt:%04lX\r\n" "Btn:%s\r\n", instance->generic.protocol_name, From 5bd0f642dd6fe0aa05ff0351754c88285993820f Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 1 Dec 2025 05:31:26 +0300 Subject: [PATCH 06/18] ir cli fix by WillyJL --- applications/main/infrared/application.fam | 1 + 1 file changed, 1 insertion(+) diff --git a/applications/main/infrared/application.fam b/applications/main/infrared/application.fam index 3ed928ad8..ae5ec907e 100644 --- a/applications/main/infrared/application.fam +++ b/applications/main/infrared/application.fam @@ -21,4 +21,5 @@ App( entry_point="cli_ir_ep", requires=["cli"], sources=["infrared_cli.c"], + fap_libs=["infrared"], ) From 3821c9049e8e618f87da346571ff1ac48a78f191 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 1 Dec 2025 05:36:10 +0300 Subject: [PATCH 07/18] Add MFUL counters to Info page by aaronjamt --- .../protocol_support/mf_ultralight/mf_ultralight_render.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c index ef83d1942..8d20109f8 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c @@ -9,6 +9,11 @@ static void nfc_render_mf_ultralight_pages_count(const MfUltralightData* data, F } } +static void nfc_render_mf_ultralight_counters(const MfUltralightData* data, FuriString* str) { + for(uint8_t i = 0; i < MF_ULTRALIGHT_COUNTER_NUM; i++) + furi_string_cat_printf(str, "\nCounter %u: %lu", i, data->counter[i].counter); +} + void nfc_render_mf_ultralight_pwd_pack(const MfUltralightData* data, FuriString* str) { MfUltralightConfigPages* config; @@ -35,6 +40,8 @@ void nfc_render_mf_ultralight_pwd_pack(const MfUltralightData* data, FuriString* } nfc_render_mf_ultralight_pages_count(data, str); + + nfc_render_mf_ultralight_counters(data, str); } void nfc_render_mf_ultralight_info( From a7561bee985e5cdf46fde3ab1ca2c66a02726600 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 1 Dec 2025 05:40:27 +0300 Subject: [PATCH 08/18] Add Saflok MFUL Parser Support by aaronjamt --- applications/main/nfc/application.fam | 13 +++- .../main/nfc/plugins/supported_cards/saflok.c | 78 +++++++++++++++---- 2 files changed, 77 insertions(+), 14 deletions(-) diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index ed5039ade..275da7417 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -369,8 +369,19 @@ App( ) App( - appid="saflok_parser", + appid="saflok_mfc_parser", apptype=FlipperAppType.PLUGIN, + cdefines=[("SL_PROTO", "SL_PROTO_MFC")], + entry_point="saflok_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/saflok.c"], +) + +App( + appid="saflok_ul_parser", + apptype=FlipperAppType.PLUGIN, + cdefines=[("SL_PROTO", "SL_PROTO_UL")], entry_point="saflok_plugin_ep", targets=["f7"], requires=["nfc"], diff --git a/applications/main/nfc/plugins/supported_cards/saflok.c b/applications/main/nfc/plugins/supported_cards/saflok.c index 9cab61ee5..da31548de 100644 --- a/applications/main/nfc/plugins/supported_cards/saflok.c +++ b/applications/main/nfc/plugins/supported_cards/saflok.c @@ -11,6 +11,8 @@ #include #include +#include + #include #include @@ -18,9 +20,24 @@ #include #include +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" + #define TAG "Saflok" -#define MAGIC_TABLE_SIZE 192 +#define MAGIC_TABLE_SIZE 192 +#define SL_PROTO_INVALID (-1) +#define SL_PROTO_MFC (0) +#define SL_PROTO_UL (1) +#define SL_PROTO_TOTAL (2) + +#ifndef SL_PROTO +#error Must specify what protocol to use with SL_PROTO define! +#endif +#if SL_PROTO <= SL_PROTO_INVALID || SL_PROTO >= SL_PROTO_TOTAL +#error Invalid SL_PROTO specified! +#endif + #define KEY_LENGTH 6 #define UID_LENGTH 4 #define CHECK_SECTOR 1 @@ -255,25 +272,40 @@ static bool saflok_read(Nfc* nfc, NfcDevice* device) { bool saflok_parse(const NfcDevice* device, FuriString* parsed_data) { furi_assert(device); +#if SL_PROTO == SL_PROTO_MFC const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); +#elif SL_PROTO == SL_PROTO_UL + const MfUltralightData* data = nfc_device_get_data(device, NfcProtocolMfUltralight); +#endif bool parsed = false; do { +#if SL_PROTO == SL_PROTO_MFC // Check card type if(data->type != MfClassicType1k) break; - // Verify key const MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, CHECK_SECTOR); - const uint64_t key_a = bit_lib_bytes_to_num_be(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); if(key_a != saflok_1k_keys[CHECK_SECTOR].a) break; - - // Decrypt basic access + // Init basic access uint8_t basicAccess[BASIC_ACCESS_BYTE_NUM]; memcpy(&basicAccess, &data->block[1].data, 16); memcpy(&basicAccess[16], &data->block[2].data[0], 1); +#elif SL_PROTO == SL_PROTO_UL + // Check card type + if(data->type != MfUltralightTypeMfulC) break; + // Init basic access + uint8_t basicAccess[BASIC_ACCESS_BYTE_NUM]; + memcpy(&basicAccess[0 * 4], &data->page[34].data, 4); + memcpy(&basicAccess[1 * 4], &data->page[35].data, 4); + memcpy(&basicAccess[2 * 4], &data->page[36].data, 4); + memcpy(&basicAccess[3 * 4], &data->page[37].data, 4); + memcpy(&basicAccess[4 * 4], &data->page[38].data[0], 1); +#endif + + // Decrypt basic access uint8_t decodedBA[BASIC_ACCESS_BYTE_NUM]; DecryptCard(basicAccess, BASIC_ACCESS_BYTE_NUM, decodedBA); @@ -290,8 +322,7 @@ bool saflok_parse(const NfcDevice* device, FuriString* parsed_data) { uint16_t key_record = (key_record_high << 8) | decodedBA[3]; // Byte 4 & 5: Pass level in reversed binary - // This part is commented because the relevance of this info is still unknown - // uint16_t pass_level = ((decodedBA[4] & 0xFF) << 8) | decodedBA[5]; + uint16_t pass_level = ((decodedBA[4] & 0xFF) << 8) | decodedBA[5]; // uint8_t pass_levels[12]; // int pass_levels_count = 0; @@ -390,26 +421,31 @@ bool saflok_parse(const NfcDevice* device, FuriString* parsed_data) { uint8_t checksum = decodedBA[16]; uint8_t checksum_calculated = CalculateCheckSum(decodedBA); bool checksum_valid = (checksum_calculated == checksum); - for(int i = 0; i < 17; i++) { + for(int i = 0; i < BASIC_ACCESS_BYTE_NUM; i++) { FURI_LOG_D(TAG, "%02X", decodedBA[i]); } FURI_LOG_D(TAG, "CS decrypted: %02X", checksum); FURI_LOG_D(TAG, "CS calculated: %02X", checksum_calculated); - - furi_string_cat_printf(parsed_data, "\e#Saflok Card\n"); +#if SL_PROTO == SL_PROTO_MFC + furi_string_cat_printf(parsed_data, "\e#Saflok MFC 1K Card\n"); +#elif SL_PROTO == SL_PROTO_UL + furi_string_cat_printf(parsed_data, "\e#Saflok UL-C Card\n"); +#endif + furi_string_cat_printf(parsed_data, "Property Number: %u\n", property_id); furi_string_cat_printf( parsed_data, "Key Level: %u, %s\n", key_levels[key_level].level_num, key_levels[key_level].level_name); - furi_string_cat_printf(parsed_data, "LED Exp. Warning: %s\n", led_warning ? "Yes" : "No"); furi_string_cat_printf(parsed_data, "Key ID: %02X\n", key_id); furi_string_cat_printf(parsed_data, "Key Record: %04X\n", key_record); - furi_string_cat_printf(parsed_data, "Opening key: %s\n", opening_key ? "Yes" : "No"); furi_string_cat_printf( parsed_data, "Seq. & Combination: %04X\n", sequence_combination_number); + furi_string_cat_printf(parsed_data, "Pass Level: %04X\n", pass_level); + furi_string_cat_printf(parsed_data, "Opening Key: %s\n", opening_key ? "Yes" : "No"); furi_string_cat_printf( parsed_data, "Override Deadbolt: %s\n", override_deadbolt ? "Yes" : "No"); + furi_string_cat_printf(parsed_data, "LED Exp. Warning: %s\n", led_warning ? "Yes" : "No"); furi_string_cat_printf( parsed_data, "Restricted Weekday: %s\n", @@ -430,19 +466,33 @@ bool saflok_parse(const NfcDevice* device, FuriString* parsed_data) { expire_day, expire_hour, expire_minute); - furi_string_cat_printf(parsed_data, "Property Number: %u\n", property_id); furi_string_cat_printf(parsed_data, "Checksum Valid: %s", checksum_valid ? "Yes" : "No"); +#if SL_PROTO == SL_PROTO_MFC + // MFC returns parsed = true since we have proper verify and read functions parsed = true; +#elif SL_PROTO == SL_PROTO_UL + // UL returns parsed = checksum_valid since we don't have proper verify and read functions + // TODO: change to true after verify and read are implemented + parsed = checksum_valid; +#endif + } while(false); return parsed; } /* Actual implementation of app<>plugin interface */ static const NfcSupportedCardsPlugin saflok_plugin = { +#if SL_PROTO == SL_PROTO_MFC .protocol = NfcProtocolMfClassic, .verify = saflok_verify, .read = saflok_read, .parse = saflok_parse, +#elif SL_PROTO == SL_PROTO_UL + .protocol = NfcProtocolMfUltralight, + .verify = NULL, + .read = NULL, + .parse = saflok_parse, +#endif }; /* Plugin descriptor to comply with basic plugin specification */ @@ -456,3 +506,5 @@ static const FlipperAppPluginDescriptor saflok_plugin_descriptor = { const FlipperAppPluginDescriptor* saflok_plugin_ep(void) { return &saflok_plugin_descriptor; } + +#pragma GCC diagnostic pop \ No newline at end of file From 6abd2b0e9f1d677a4cfe424868357911c5e34854 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 1 Dec 2025 06:06:30 +0300 Subject: [PATCH 09/18] Add date/time input module ofw pr 4261 by aaronjamt --- .../example_date_time_input/ReadMe.md | 13 + .../example_date_time_input/application.fam | 9 + .../example_date_time_input.c | 79 +++ .../example_date_time_input.h | 36 ++ .../scenes/example_date_time_input_scene.c | 31 ++ .../scenes/example_date_time_input_scene.h | 29 ++ .../example_date_time_input_scene_config.h | 2 + ...le_date_time_input_scene_input_date_time.c | 47 ++ ...ple_date_time_input_scene_show_date_time.c | 94 ++++ applications/services/gui/application.fam | 1 + .../services/gui/modules/date_time_input.c | 479 ++++++++++++++++++ .../services/gui/modules/date_time_input.h | 82 +++ targets/f7/api_symbols.csv | 6 + 13 files changed, 908 insertions(+) create mode 100644 applications/examples/example_date_time_input/ReadMe.md create mode 100644 applications/examples/example_date_time_input/application.fam create mode 100644 applications/examples/example_date_time_input/example_date_time_input.c create mode 100644 applications/examples/example_date_time_input/example_date_time_input.h create mode 100644 applications/examples/example_date_time_input/scenes/example_date_time_input_scene.c create mode 100644 applications/examples/example_date_time_input/scenes/example_date_time_input_scene.h create mode 100644 applications/examples/example_date_time_input/scenes/example_date_time_input_scene_config.h create mode 100644 applications/examples/example_date_time_input/scenes/example_date_time_input_scene_input_date_time.c create mode 100644 applications/examples/example_date_time_input/scenes/example_date_time_input_scene_show_date_time.c create mode 100644 applications/services/gui/modules/date_time_input.c create mode 100644 applications/services/gui/modules/date_time_input.h diff --git a/applications/examples/example_date_time_input/ReadMe.md b/applications/examples/example_date_time_input/ReadMe.md new file mode 100644 index 000000000..b153965cc --- /dev/null +++ b/applications/examples/example_date_time_input/ReadMe.md @@ -0,0 +1,13 @@ +# Date/Time Input {#example_date_time_input} + +Simple view that allows the user to adjust a date and/or time. + +## Source code + +Source code for this example can be found [here](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/examples/example_date_time_input). + +## General principle + +Callbacks can be defined for every time a value is edited (useful for application-specific bounds checking or validation) and for when the user is done editing (back button is pressed). The provided DateTime object is used both as the initial value and as the place where the result is stored. + +The fields which the user is allowed to edit can be defined using `date_time_input_set_editable_fields()`. Disabled fields are shown but aren't able to be selected and don't have an outer box. If all fields are disabled, the view is read-only and no cursor will be shown. diff --git a/applications/examples/example_date_time_input/application.fam b/applications/examples/example_date_time_input/application.fam new file mode 100644 index 000000000..7f6435840 --- /dev/null +++ b/applications/examples/example_date_time_input/application.fam @@ -0,0 +1,9 @@ +App( + appid="example_date_time_input", + name="Example: Date/Time Input", + apptype=FlipperAppType.EXTERNAL, + entry_point="example_date_time_input", + requires=["gui"], + stack_size=1 * 1024, + fap_category="Examples", +) diff --git a/applications/examples/example_date_time_input/example_date_time_input.c b/applications/examples/example_date_time_input/example_date_time_input.c new file mode 100644 index 000000000..7510f5c52 --- /dev/null +++ b/applications/examples/example_date_time_input/example_date_time_input.c @@ -0,0 +1,79 @@ +#include "example_date_time_input.h" + +bool example_date_time_input_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + ExampleDateTimeInput* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool example_date_time_input_back_event_callback(void* context) { + furi_assert(context); + ExampleDateTimeInput* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static ExampleDateTimeInput* example_date_time_input_alloc() { + ExampleDateTimeInput* app = malloc(sizeof(ExampleDateTimeInput)); + app->gui = furi_record_open(RECORD_GUI); + + app->view_dispatcher = view_dispatcher_alloc(); + + app->scene_manager = scene_manager_alloc(&example_date_time_input_scene_handlers, app); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, example_date_time_input_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, example_date_time_input_back_event_callback); + + app->date_time_input = date_time_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + ExampleDateTimeInputViewIdDateTimeInput, + date_time_input_get_view(app->date_time_input)); + + app->dialog_ex = dialog_ex_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + ExampleDateTimeInputViewIdShowDateTime, + dialog_ex_get_view(app->dialog_ex)); + + // Fill in current date & time + furi_hal_rtc_get_datetime(&app->date_time); + app->edit_date = false; + app->edit_time = false; + + return app; +} + +static void example_date_time_input_free(ExampleDateTimeInput* app) { + furi_assert(app); + + view_dispatcher_remove_view(app->view_dispatcher, ExampleDateTimeInputViewIdShowDateTime); + dialog_ex_free(app->dialog_ex); + + view_dispatcher_remove_view(app->view_dispatcher, ExampleDateTimeInputViewIdDateTimeInput); + date_time_input_free(app->date_time_input); + + scene_manager_free(app->scene_manager); + view_dispatcher_free(app->view_dispatcher); + + furi_record_close(RECORD_GUI); + app->gui = NULL; + + free(app); +} + +int32_t example_date_time_input(void* p) { + UNUSED(p); + ExampleDateTimeInput* app = example_date_time_input_alloc(); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + scene_manager_next_scene(app->scene_manager, ExampleDateTimeInputSceneShowDateTime); + + view_dispatcher_run(app->view_dispatcher); + + example_date_time_input_free(app); + + return 0; +} diff --git a/applications/examples/example_date_time_input/example_date_time_input.h b/applications/examples/example_date_time_input/example_date_time_input.h new file mode 100644 index 000000000..6363535f9 --- /dev/null +++ b/applications/examples/example_date_time_input/example_date_time_input.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "scenes/example_date_time_input_scene.h" + +typedef struct ExampleDateTimeInputShowDateTime ExampleDateTimeInputShowDateTime; + +typedef enum { + ExampleDateTimeInputViewIdShowDateTime, + ExampleDateTimeInputViewIdDateTimeInput, +} ExampleDateTimeInputViewId; + +typedef struct { + Gui* gui; + SceneManager* scene_manager; + ViewDispatcher* view_dispatcher; + + DateTimeInput* date_time_input; + DialogEx* dialog_ex; + + DateTime date_time; + + bool edit_date; + bool edit_time; +} ExampleDateTimeInput; diff --git a/applications/examples/example_date_time_input/scenes/example_date_time_input_scene.c b/applications/examples/example_date_time_input/scenes/example_date_time_input_scene.c new file mode 100644 index 000000000..ed3f538f2 --- /dev/null +++ b/applications/examples/example_date_time_input/scenes/example_date_time_input_scene.c @@ -0,0 +1,31 @@ +#include "example_date_time_input_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const example_date_time_input_on_enter_handlers[])(void*) = { +#include "example_date_time_input_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const example_date_time_input_on_event_handlers[])(void* context, SceneManagerEvent event) = + { +#include "example_date_time_input_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const example_date_time_input_on_exit_handlers[])(void* context) = { +#include "example_date_time_input_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers example_date_time_input_scene_handlers = { + .on_enter_handlers = example_date_time_input_on_enter_handlers, + .on_event_handlers = example_date_time_input_on_event_handlers, + .on_exit_handlers = example_date_time_input_on_exit_handlers, + .scene_num = ExampleDateTimeInputSceneNum, +}; diff --git a/applications/examples/example_date_time_input/scenes/example_date_time_input_scene.h b/applications/examples/example_date_time_input/scenes/example_date_time_input_scene.h new file mode 100644 index 000000000..5664bad3d --- /dev/null +++ b/applications/examples/example_date_time_input/scenes/example_date_time_input_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) ExampleDateTimeInputScene##id, +typedef enum { +#include "example_date_time_input_scene_config.h" + ExampleDateTimeInputSceneNum, +} ExampleDateTimeInputScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers example_date_time_input_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "example_date_time_input_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "example_date_time_input_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "example_date_time_input_scene_config.h" +#undef ADD_SCENE diff --git a/applications/examples/example_date_time_input/scenes/example_date_time_input_scene_config.h b/applications/examples/example_date_time_input/scenes/example_date_time_input_scene_config.h new file mode 100644 index 000000000..db3b128da --- /dev/null +++ b/applications/examples/example_date_time_input/scenes/example_date_time_input_scene_config.h @@ -0,0 +1,2 @@ +ADD_SCENE(example_date_time_input, input_date_time, InputDateTime) +ADD_SCENE(example_date_time_input, show_date_time, ShowDateTime) diff --git a/applications/examples/example_date_time_input/scenes/example_date_time_input_scene_input_date_time.c b/applications/examples/example_date_time_input/scenes/example_date_time_input_scene_input_date_time.c new file mode 100644 index 000000000..8a1288c38 --- /dev/null +++ b/applications/examples/example_date_time_input/scenes/example_date_time_input_scene_input_date_time.c @@ -0,0 +1,47 @@ +#include "../example_date_time_input.h" + +void example_date_time_input_scene_input_date_time_callback(void* context) { + ExampleDateTimeInput* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, 0); +} + +void example_date_time_input_scene_input_date_time_on_enter(void* context) { + furi_assert(context); + ExampleDateTimeInput* app = context; + DateTimeInput* date_time_input = app->date_time_input; + + date_time_input_set_result_callback( + date_time_input, + NULL, + example_date_time_input_scene_input_date_time_callback, + context, + &app->date_time); + + date_time_input_set_editable_fields( + date_time_input, + + app->edit_date, + app->edit_date, + app->edit_date, + + app->edit_time, + app->edit_time, + app->edit_time); + + view_dispatcher_switch_to_view(app->view_dispatcher, ExampleDateTimeInputViewIdDateTimeInput); +} + +bool example_date_time_input_scene_input_date_time_on_event(void* context, SceneManagerEvent event) { + ExampleDateTimeInput* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { //Back button pressed + scene_manager_previous_scene(app->scene_manager); + return true; + } + return consumed; +} + +void example_date_time_input_scene_input_date_time_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/examples/example_date_time_input/scenes/example_date_time_input_scene_show_date_time.c b/applications/examples/example_date_time_input/scenes/example_date_time_input_scene_show_date_time.c new file mode 100644 index 000000000..15e4cce08 --- /dev/null +++ b/applications/examples/example_date_time_input/scenes/example_date_time_input_scene_show_date_time.c @@ -0,0 +1,94 @@ +#include "../example_date_time_input.h" + +static void + example_date_time_input_scene_confirm_dialog_callback(DialogExResult result, void* context) { + ExampleDateTimeInput* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, result); +} + +static void example_date_time_input_scene_update_view(void* context) { + ExampleDateTimeInput* app = context; + DialogEx* dialog_ex = app->dialog_ex; + + dialog_ex_set_header(dialog_ex, "The date and time are", 64, 0, AlignCenter, AlignTop); + + uint8_t hour = app->date_time.hour; + char label_hour[4] = ""; + if(furi_hal_rtc_get_locale_timeformat() == FuriHalRtcLocaleTimeFormat12h) { + if(hour < 12) { + snprintf(label_hour, sizeof(label_hour), " AM"); + } else { + snprintf(label_hour, sizeof(label_hour), " PM"); + } + hour %= 12; + if(hour == 0) hour = 12; + } + + char buffer[29] = {}; + snprintf( + buffer, + sizeof(buffer), + "%04d-%02d-%02d\n%02d:%02d:%02d%s", + app->date_time.year, + app->date_time.month, + app->date_time.day, + hour, + app->date_time.minute, + app->date_time.second, + label_hour); + dialog_ex_set_text(dialog_ex, buffer, 64, 29, AlignCenter, AlignCenter); + + dialog_ex_set_left_button_text(dialog_ex, "Date"); + dialog_ex_set_right_button_text(dialog_ex, "Time"); + dialog_ex_set_center_button_text(dialog_ex, "Both"); + + dialog_ex_set_result_callback( + dialog_ex, example_date_time_input_scene_confirm_dialog_callback); + dialog_ex_set_context(dialog_ex, app); +} + +void example_date_time_input_scene_show_date_time_on_enter(void* context) { + furi_assert(context); + ExampleDateTimeInput* app = context; + + example_date_time_input_scene_update_view(app); + + view_dispatcher_switch_to_view(app->view_dispatcher, ExampleDateTimeInputViewIdShowDateTime); +} + +bool example_date_time_input_scene_show_date_time_on_event(void* context, SceneManagerEvent event) { + ExampleDateTimeInput* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case DialogExResultCenter: + app->edit_date = true; + app->edit_time = true; + scene_manager_next_scene(app->scene_manager, ExampleDateTimeInputSceneInputDateTime); + consumed = true; + break; + case DialogExResultLeft: + app->edit_date = true; + app->edit_time = false; + scene_manager_next_scene(app->scene_manager, ExampleDateTimeInputSceneInputDateTime); + consumed = true; + break; + case DialogExResultRight: + app->edit_date = false; + app->edit_time = true; + scene_manager_next_scene(app->scene_manager, ExampleDateTimeInputSceneInputDateTime); + consumed = true; + break; + default: + break; + } + } + + return consumed; +} + +void example_date_time_input_scene_show_date_time_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/services/gui/application.fam b/applications/services/gui/application.fam index b24f5bbb6..4eb09ff25 100644 --- a/applications/services/gui/application.fam +++ b/applications/services/gui/application.fam @@ -35,5 +35,6 @@ App( "modules/submenu.h", "modules/widget_elements/widget_element.h", "modules/empty_screen.h", + "modules/date_time_input.h", ], ) diff --git a/applications/services/gui/modules/date_time_input.c b/applications/services/gui/modules/date_time_input.c new file mode 100644 index 000000000..be64ed165 --- /dev/null +++ b/applications/services/gui/modules/date_time_input.c @@ -0,0 +1,479 @@ +#include "date_time_input.h" +#include "furi_hal_rtc.h" +#include +#include + +#define get_state(m, r, c, f) \ + ((m)->editable.f ? ((m)->row == (r) && (m)->column == (c) ? \ + ((m)->editing ? EditStateActiveEditing : EditStateActive) : \ + EditStateNone) : \ + EditStateDisabled) +#define ROW_0_Y (9) +#define ROW_0_H (20) + +#define ROW_1_Y (40) +#define ROW_1_H (20) + +#define ROW_COUNT 2 +#define COLUMN_COUNT 3 + +struct DateTimeInput { + View* view; +}; + +typedef struct { + DateTime* datetime; + + uint8_t row; + uint8_t column; + bool editing; + + struct { + bool year; + bool month; + bool day; + bool hour; + bool minute; + bool second; + } editable; + + DateTimeChangedCallback changed_callback; + DateTimeDoneCallback done_callback; + void* callback_context; +} DateTimeInputModel; + +typedef enum { + EditStateNone, + EditStateActive, + EditStateActiveEditing, + EditStateDisabled +} EditState; + +static inline void date_time_input_cleanup_date(DateTime* dt) { + uint8_t day_per_month = + datetime_get_days_per_month(datetime_is_leap_year(dt->year), dt->month); + if(dt->day > day_per_month) { + dt->day = day_per_month; + } +} +static inline void date_time_input_draw_block( + Canvas* canvas, + int32_t x, + int32_t y, + size_t w, + size_t h, + Font font, + EditState state, + const char* text) { + furi_assert(canvas); + furi_assert(text); + + canvas_set_color(canvas, ColorBlack); + if(state != EditStateDisabled) { + if(state != EditStateNone) { + if(state == EditStateActiveEditing) { + canvas_draw_icon(canvas, x + w / 2 - 2, y - 1 - 3, &I_SmallArrowUp_3x5); + canvas_draw_icon(canvas, x + w / 2 - 2, y + h + 1, &I_SmallArrowDown_3x5); + } + canvas_draw_rbox(canvas, x, y, w, h, 1); + canvas_set_color(canvas, ColorWhite); + } else { + canvas_draw_rframe(canvas, x, y, w, h, 1); + } + } + + canvas_set_font(canvas, font); + canvas_draw_str_aligned(canvas, x + w / 2, y + h / 2, AlignCenter, AlignCenter, text); + if(state != EditStateNone) { + canvas_set_color(canvas, ColorBlack); + } +} + +static inline void date_time_input_draw_text( + Canvas* canvas, + int32_t x, + int32_t y, + size_t w, + size_t h, + Font font, + EditState state, + const char* text) { + furi_assert(canvas); + furi_assert(text); + + canvas_set_color(canvas, ColorBlack); + if(state != EditStateDisabled && state != EditStateNone) { + canvas_set_color(canvas, ColorWhite); + } + + canvas_set_font(canvas, font); + canvas_draw_str_aligned(canvas, x + w / 2, y + h / 2, AlignCenter, AlignCenter, text); + if(state != EditStateNone) { + canvas_set_color(canvas, ColorBlack); + } +} + +static void date_time_input_draw_hour_24hr_callback(Canvas* canvas, DateTimeInputModel* model) { + char buffer[4]; + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 0, ROW_1_Y - 2, " H H M M S S"); + canvas_set_font(canvas, FontPrimary); + + snprintf(buffer, sizeof(buffer), "%02u", model->datetime->hour); + date_time_input_draw_block( + canvas, 30, ROW_1_Y, 28, ROW_1_H, FontBigNumbers, get_state(model, 1, 0, hour), buffer); + canvas_draw_box(canvas, 60, ROW_1_Y + ROW_1_H - 7, 2, 2); + canvas_draw_box(canvas, 60, ROW_1_Y + ROW_1_H - 7 - 6, 2, 2); +} + +static void date_time_input_draw_hour_12hr_callback(Canvas* canvas, DateTimeInputModel* model) { + char buffer[4]; + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 0, ROW_1_Y - 2, " H H M M S S"); + canvas_set_font(canvas, FontPrimary); + + uint8_t hour = model->datetime->hour % 12; + // Show 12:00 instead of 00:00 for 12-hour time + if(hour == 0) hour = 12; + + // Placeholder spaces to make room for AM/PM since FontBigNumbers can't draw letters + date_time_input_draw_block( + canvas, 8, ROW_1_Y, 50, ROW_1_H, FontBigNumbers, get_state(model, 1, 0, hour), buffer); + canvas_draw_box(canvas, 60, ROW_1_Y + ROW_1_H - 7, 2, 2); + canvas_draw_box(canvas, 60, ROW_1_Y + ROW_1_H - 7 - 6, 2, 2); + + snprintf(buffer, sizeof(buffer), "%02u", hour); + date_time_input_draw_text( + canvas, 8, ROW_1_Y, 30, ROW_1_H, FontBigNumbers, get_state(model, 1, 0, hour), buffer); + + // The AM and PM text shift by 1 pixel so compensate to make them line up + if(model->datetime->hour < 12) { + date_time_input_draw_text( + canvas, 30, ROW_1_Y + 3, 30, ROW_1_H, FontPrimary, get_state(model, 1, 0, hour), "AM"); + } else { + date_time_input_draw_text( + canvas, 31, ROW_1_Y + 3, 30, ROW_1_H, FontPrimary, get_state(model, 1, 0, hour), "PM"); + } +} + +static void date_time_input_draw_time_callback(Canvas* canvas, DateTimeInputModel* model) { + furi_check(model->datetime); + + char buffer[4]; + + // Draw hour depending on RTC time format + if(furi_hal_rtc_get_locale_timeformat() == FuriHalRtcLocaleTimeFormat24h) { + date_time_input_draw_hour_24hr_callback(canvas, model); + } else { + date_time_input_draw_hour_12hr_callback(canvas, model); + } + + snprintf(buffer, sizeof(buffer), "%02u", model->datetime->minute); + date_time_input_draw_block( + canvas, 64, ROW_1_Y, 28, ROW_1_H, FontBigNumbers, get_state(model, 1, 1, minute), buffer); + canvas_draw_box(canvas, 94, ROW_1_Y + ROW_1_H - 7, 2, 2); + canvas_draw_box(canvas, 94, ROW_1_Y + ROW_1_H - 7 - 6, 2, 2); + + snprintf(buffer, sizeof(buffer), "%02u", model->datetime->second); + date_time_input_draw_block( + canvas, 98, ROW_1_Y, 28, ROW_1_H, FontBigNumbers, get_state(model, 1, 2, second), buffer); +} + +static void date_time_input_draw_date_callback(Canvas* canvas, DateTimeInputModel* model) { + furi_check(model->datetime); + + char buffer[6]; + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 0, ROW_0_Y - 2, " Y Y Y Y M M D D"); + canvas_set_font(canvas, FontPrimary); + snprintf(buffer, sizeof(buffer), "%04u", model->datetime->year); + date_time_input_draw_block( + canvas, 2, ROW_0_Y, 56, ROW_0_H, FontBigNumbers, get_state(model, 0, 0, year), buffer); + snprintf(buffer, sizeof(buffer), "%02u", model->datetime->month); + date_time_input_draw_block( + canvas, 64, ROW_0_Y, 28, ROW_0_H, FontBigNumbers, get_state(model, 0, 1, month), buffer); + canvas_draw_box(canvas, 64 - 5, ROW_0_Y + (ROW_0_H / 2), 4, 2); + snprintf(buffer, sizeof(buffer), "%02u", model->datetime->day); + date_time_input_draw_block( + canvas, 98, ROW_0_Y, 28, ROW_0_H, FontBigNumbers, get_state(model, 0, 2, day), buffer); + canvas_draw_box(canvas, 98 - 5, ROW_0_Y + (ROW_0_H / 2), 4, 2); +} + +static void date_time_input_view_draw_callback(Canvas* canvas, void* _model) { + DateTimeInputModel* model = _model; + canvas_clear(canvas); + date_time_input_draw_time_callback(canvas, model); + date_time_input_draw_date_callback(canvas, model); +} + +static inline bool is_allowed_to_edit(DateTimeInputModel* model) { + return (model->row == 0 && ((model->column == 0 && model->editable.year) | + (model->column == 1 && model->editable.month) | + (model->column == 2 && model->editable.day))) || + ((model->row == 1) && ((model->column == 0 && model->editable.hour) | + (model->column == 1 && model->editable.minute) | + (model->column == 2 && model->editable.second))); +} + +static bool date_time_input_navigation_callback(InputEvent* event, DateTimeInputModel* model) { + if(event->key == InputKeyUp) { + if(model->row > 0) model->row--; + if(!is_allowed_to_edit(model)) model->row++; + } else if(event->key == InputKeyDown) { + if(model->row < ROW_COUNT - 1) model->row++; + if(!is_allowed_to_edit(model)) model->row--; + } else if(event->key == InputKeyOk) { + model->editing = !model->editing; + } else if(event->key == InputKeyRight) { + if(model->column < COLUMN_COUNT - 1) model->column++; + while(model->column < COLUMN_COUNT - 1 && !is_allowed_to_edit(model)) + model->column++; + while(model->column > 0 && !is_allowed_to_edit(model)) + model->column--; + } else if(event->key == InputKeyLeft) { + if(model->column > 0) model->column--; + while(model->column > 0 && !is_allowed_to_edit(model)) + model->column--; + while(model->column < COLUMN_COUNT - 1 && !is_allowed_to_edit(model)) + model->column++; + } else if(event->key == InputKeyBack && model->editing) { + model->editing = false; + } else if(event->key == InputKeyBack && model->done_callback) { + model->done_callback(model->callback_context); + } else { + return false; + } + + return true; +} + +static bool date_time_input_time_callback(InputEvent* event, DateTimeInputModel* model) { + furi_check(model->datetime); + + if(event->key == InputKeyUp) { + if(model->column == 0) { + model->datetime->hour++; + model->datetime->hour = model->datetime->hour % 24; + } else if(model->column == 1) { + model->datetime->minute++; + model->datetime->minute = model->datetime->minute % 60; + } else if(model->column == 2) { + model->datetime->second++; + model->datetime->second = model->datetime->second % 60; + } else { + furi_crash(); + } + } else if(event->key == InputKeyDown) { + if(model->column == 0) { + if(model->datetime->hour > 0) { + model->datetime->hour--; + } else { + model->datetime->hour = 23; + } + model->datetime->hour = model->datetime->hour % 24; + } else if(model->column == 1) { + if(model->datetime->minute > 0) { + model->datetime->minute--; + } else { + model->datetime->minute = 59; + } + model->datetime->minute = model->datetime->minute % 60; + } else if(model->column == 2) { + if(model->datetime->second > 0) { + model->datetime->second--; + } else { + model->datetime->second = 59; + } + model->datetime->second = model->datetime->second % 60; + } else { + furi_crash(); + } + } else { + return date_time_input_navigation_callback(event, model); + } + + return true; +} + +static bool date_time_input_date_callback(InputEvent* event, DateTimeInputModel* model) { + furi_check(model->datetime); + + if(event->key == InputKeyUp) { + if(model->column == 0) { + if(model->datetime->year < 2099) { + model->datetime->year++; + } + } else if(model->column == 1) { + if(model->datetime->month < 12) { + model->datetime->month++; + } + } else if(model->column == 2) { + if(model->datetime->day < 31) model->datetime->day++; + } else { + furi_crash(); + } + } else if(event->key == InputKeyDown) { + if(model->column == 0) { + if(model->datetime->year > 1980) { + model->datetime->year--; + } + } else if(model->column == 1) { + if(model->datetime->month > 1) { + model->datetime->month--; + } + } else if(model->column == 2) { + if(model->datetime->day > 1) { + model->datetime->day--; + } + } else { + furi_crash(); + } + } else { + return date_time_input_navigation_callback(event, model); + } + + date_time_input_cleanup_date(model->datetime); + + return true; +} + +static bool date_time_input_view_input_callback(InputEvent* event, void* context) { + DateTimeInput* instance = context; + bool consumed = false; + + with_view_model( + instance->view, + DateTimeInputModel * model, + { + if(event->type == InputTypeShort || event->type == InputTypeRepeat) { + if(model->editing) { + if(model->row == 0) { + consumed = date_time_input_date_callback(event, model); + } else if(model->row == 1) { + consumed = date_time_input_time_callback(event, model); + } else { + furi_crash(); + } + + if(model->changed_callback) { + model->changed_callback(model->callback_context); + } + } else { + consumed = date_time_input_navigation_callback(event, model); + } + } + }, + true); + + return consumed; +} + +/** Reset all input-related data in model + * + * @param model The model + */ +static void date_time_input_reset_model_input_data(DateTimeInputModel* model) { + model->row = 0; + model->column = 0; + + model->datetime = NULL; + + model->editable.year = true; + model->editable.month = true; + model->editable.day = true; + model->editable.hour = true; + model->editable.minute = true; + model->editable.second = true; +} + +DateTimeInput* date_time_input_alloc(void) { + DateTimeInput* date_time_input = malloc(sizeof(DateTimeInput)); + date_time_input->view = view_alloc(); + view_allocate_model(date_time_input->view, ViewModelTypeLocking, sizeof(DateTimeInputModel)); + view_set_context(date_time_input->view, date_time_input); + view_set_draw_callback(date_time_input->view, date_time_input_view_draw_callback); + view_set_input_callback(date_time_input->view, date_time_input_view_input_callback); + + with_view_model( + date_time_input->view, + DateTimeInputModel * model, + { + model->changed_callback = NULL; + model->callback_context = NULL; + date_time_input_reset_model_input_data(model); + }, + true); + + return date_time_input; +} + +void date_time_input_free(DateTimeInput* date_time_input) { + furi_check(date_time_input); + view_free(date_time_input->view); + free(date_time_input); +} + +View* date_time_input_get_view(DateTimeInput* date_time_input) { + furi_check(date_time_input); + return date_time_input->view; +} + +void date_time_input_set_result_callback( + DateTimeInput* date_time_input, + DateTimeChangedCallback changed_callback, + DateTimeDoneCallback done_callback, + void* callback_context, + DateTime* current_datetime) { + furi_check(date_time_input); + + with_view_model( + date_time_input->view, + DateTimeInputModel * model, + { + date_time_input_reset_model_input_data(model); + model->changed_callback = changed_callback; + model->done_callback = done_callback; + model->callback_context = callback_context; + model->datetime = current_datetime; + }, + true); +} + +void date_time_input_set_editable_fields( + DateTimeInput* date_time_input, + bool year, + bool month, + bool day, + bool hour, + bool minute, + bool second) { + furi_check(date_time_input); + + with_view_model( + date_time_input->view, + DateTimeInputModel * model, + { + model->editable.year = year; + model->editable.month = month; + model->editable.day = day; + model->editable.hour = hour; + model->editable.minute = minute; + model->editable.second = second; + + // Select first editable field + model->row = 0; + model->column = 0; + while(!is_allowed_to_edit(model)) { + // Cycle to next column and wrap around at end + model->column = (model->column + 1) % COLUMN_COUNT; + // If the column is 0, we wrapped, so go to next row + if(model->column == 0) model->row++; + // If we passed the last row, give up + if(model->row >= ROW_COUNT) break; + }; + }, + true); +} diff --git a/applications/services/gui/modules/date_time_input.h b/applications/services/gui/modules/date_time_input.h new file mode 100644 index 000000000..1c8cdd779 --- /dev/null +++ b/applications/services/gui/modules/date_time_input.h @@ -0,0 +1,82 @@ +/** + * @file date_time_input.h + * GUI: DateTimeInput view module API + */ + +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Date/time input anonymous structure */ +typedef struct DateTimeInput DateTimeInput; + +/** callback that is executed on value change */ +typedef void (*DateTimeChangedCallback)(void* context); + +/** callback that is executed on back button press */ +typedef void (*DateTimeDoneCallback)(void* context); + +/** Allocate and initialize date/time input + * + * This screen used to input a date and time + * + * @return DateTimeInput instance + */ +DateTimeInput* date_time_input_alloc(void); + +/** Deinitialize and free date/time input + * + * @param date_time_input Date/time input instance + */ +void date_time_input_free(DateTimeInput* date_time_input); + +/** Get date/time input view + * + * @param date_time_input Date/time input instance + * + * @return View instance that can be used for embedding + */ +View* date_time_input_get_view(DateTimeInput* date_time_input); + +/** Set date/time input result callback + * + * @param date_time_input date/time input instance + * @param changed_callback changed callback fn + * @param done_callback finished callback fn + * @param callback_context callback context + * @param datetime date/time value + */ +void date_time_input_set_result_callback( + DateTimeInput* date_time_input, + DateTimeChangedCallback changed_callback, + DateTimeDoneCallback done_callback, + void* callback_context, + DateTime* datetime); + +/** Set date/time fields which can be edited + * + * @param date_time_input date/time input instance + * @param year whether to allow editing the year + * @param month whether to allow editing the month + * @param day whether to allow editing the day + * @param hour whether to allow editing the hour + * @param minute whether to allow editing the minute + * @param second whether to allow editing the second + */ +void date_time_input_set_editable_fields( + DateTimeInput* date_time_input, + bool year, + bool month, + bool day, + bool hour, + bool minute, + bool second); + +#ifdef __cplusplus +} +#endif diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 0b6f7fae0..1213d09e7 100755 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -14,6 +14,7 @@ Header,+,applications/services/gui/icon_i.h,, Header,+,applications/services/gui/modules/button_menu.h,, Header,+,applications/services/gui/modules/button_panel.h,, Header,+,applications/services/gui/modules/byte_input.h,, +Header,+,applications/services/gui/modules/date_time_input.h,, Header,+,applications/services/gui/modules/dialog_ex.h,, Header,+,applications/services/gui/modules/empty_screen.h,, Header,+,applications/services/gui/modules/file_browser.h,, @@ -951,6 +952,11 @@ Function,+,crypto1_reset,void,Crypto1* Function,+,crypto1_word,uint32_t,"Crypto1*, uint32_t, int" Function,-,ctermid,char*,char* Function,-,cuserid,char*,char* +Function,+,date_time_input_alloc,DateTimeInput*, +Function,+,date_time_input_free,void,DateTimeInput* +Function,+,date_time_input_get_view,View*,DateTimeInput* +Function,+,date_time_input_set_editable_fields,void,"DateTimeInput*, _Bool, _Bool, _Bool, _Bool, _Bool, _Bool" +Function,+,date_time_input_set_result_callback,void,"DateTimeInput*, DateTimeChangedCallback, DateTimeDoneCallback, void*, DateTime*" Function,+,datetime_datetime_to_timestamp,uint32_t,DateTime* Function,+,datetime_get_days_per_month,uint8_t,"_Bool, uint8_t" Function,+,datetime_get_days_per_year,uint16_t,uint16_t From 0f3eb9ae1297683fd3cf58d11d4df4d3ada45a4f Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Mon, 1 Dec 2025 10:08:23 +0700 Subject: [PATCH 10/18] Subghz counter edit finished --- .../subghz/scenes/subghz_scene_saved_menu.c | 6 +++-- .../scenes/subghz_scene_signal_settings.c | 24 +++++++++++-------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_saved_menu.c b/applications/main/subghz/scenes/subghz_scene_saved_menu.c index b3ae563d5..f156b8d17 100644 --- a/applications/main/subghz/scenes/subghz_scene_saved_menu.c +++ b/applications/main/subghz/scenes/subghz_scene_saved_menu.c @@ -34,14 +34,16 @@ void subghz_scene_saved_menu_on_enter(void* context) { SubmenuIndexDelete, subghz_scene_saved_menu_submenu_callback, subghz); - // if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { submenu_add_item( subghz->submenu, "Signal Settings", SubmenuIndexSignalSettings, subghz_scene_saved_menu_submenu_callback, subghz); - // }; + }; + submenu_set_selected_item( subghz->submenu, scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneSavedMenu)); diff --git a/applications/main/subghz/scenes/subghz_scene_signal_settings.c b/applications/main/subghz/scenes/subghz_scene_signal_settings.c index 6ce14c92c..728be91b5 100644 --- a/applications/main/subghz/scenes/subghz_scene_signal_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_signal_settings.c @@ -4,7 +4,6 @@ #include #include #include -#include #define TAG "SubGhzSceneSignalSettings" @@ -63,7 +62,7 @@ void subghz_scene_signal_settings_byte_input_callback(void* context) { void subghz_scene_signal_settings_variable_item_list_enter_callback(void* context, uint32_t index) { SubGhz* subghz = context; - + // when we click OK on "Edit counter" item if(index == 1) { // Setup byte_input view ByteInput* byte_input = subghz->byte_input; @@ -78,8 +77,6 @@ void subghz_scene_signal_settings_variable_item_list_enter_callback(void* contex byte_count); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdByteInput); - // view_dispatcher_send_custom_event(subghz->view_dispatcher, 13); - // scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSignalSettingsCounter); } } @@ -158,9 +155,15 @@ void subghz_scene_signal_settings_on_enter(void* context) { FuriString* tmp_text = furi_string_alloc_set_str(""); FuriString* textCnt = furi_string_alloc_set_str(""); bool counter_not_available = true; + SubGhzProtocolDecoderBase* decoder = subghz_txrx_get_decoder(subghz->txrx); - // decode loaded sugbhz file and take information string from decoder - subghz_protocol_decoder_base_get_string(subghz_txrx_get_decoder(subghz->txrx), tmp_text); + // deserialaze and decode loaded sugbhz file and take information string from decoder + if(subghz_protocol_decoder_base_deserialize(decoder, subghz_txrx_get_fff_data(subghz->txrx)) == + SubGhzProtocolStatusOk) { + subghz_protocol_decoder_base_get_string(decoder, tmp_text); + } else { + FURI_LOG_E(TAG, "Cant deserialize this subghz file"); + } // In protocols output we allways have HEX format for "Cnt:" output (text formating like ...Cnt:%05lX\r\n") // we take 8 simbols starting from "Cnt:........" @@ -176,6 +179,7 @@ void subghz_scene_signal_settings_on_enter(void* context) { FURI_LOG_D( TAG, "Found 8 bytes string starting with Cnt:%s", furi_string_get_cstr(textCnt)); counter_not_available = false; + // trim and convert 8 simbols string to uint32 by base 16 (hex) by strint_to_uint32(); // later we use loaded_counter in subghz_scene_signal_settings_on_event to check is there 0 or not - special case strint_to_uint32(furi_string_get_cstr(textCnt), NULL, &loaded_counter32, 16); @@ -226,14 +230,14 @@ bool subghz_scene_signal_settings_on_event(void* context, SceneManagerEvent even if(event.event == SubGhzCustomEventByteInputDone) { switch(byte_count) { case 2: - // when readed from file original signal has Cnt:00 we can step to 0000+FFFF = FFFF, but we need 0000 for next step + // when signal has Cnt:00 we can step only to 0000+FFFF = FFFF, but we need 0000 for next step // for this case we must use +1 additional step to increace Cnt from FFFF to 0000. // save current user definded counter increase value (mult) tmp_counter = furi_hal_subghz_get_rolling_counter_mult(); // increase signal counter to max value - at result it must be 0000 in most cases - // but can be FFFF in case Cnt:0000 (for this we have +1 additional step + // but can be FFFF in case Cnt:0000 (for this we have +1 additional step below) furi_hal_subghz_set_rolling_counter_mult(0xFFFF); subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx)); subghz_txrx_stop(subghz->txrx); @@ -244,7 +248,7 @@ bool subghz_scene_signal_settings_on_event(void* context, SceneManagerEvent even subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx)); subghz_txrx_stop(subghz->txrx); } - // at this point we have signal Cnt:00 + // at this point we must have signal Cnt:00 // convert back after byte_view and do one send with our new mult (counter16) - at end we must have signal Cnt = counter16 counter16 = __bswap16(counter16); if(counter16 > 0) { @@ -280,7 +284,7 @@ bool subghz_scene_signal_settings_on_event(void* context, SceneManagerEvent even default: break; } - // ??????????????????? + scene_manager_previous_scene(subghz->scene_manager); return true; From 7409b51da5830fdf0f1d218c79dbff20e249f4d5 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 1 Dec 2025 06:41:00 +0300 Subject: [PATCH 11/18] upd changelog [ci skip] --- CHANGELOG.md | 17 +++++++++++------ .../resources/badusb/assets/layouts/colemak.kl | Bin 0 -> 256 bytes 2 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 applications/main/bad_usb/resources/badusb/assets/layouts/colemak.kl diff --git a/CHANGELOG.md b/CHANGELOG.md index 95ec7f98e..9c9c3a839 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,21 @@ ## Main changes - Current API: 87.1 -* SubGHz: UI for SubGHz Counter Experimental Mode (PR #930 | by @Dmitry422) (with Debug enabled) (Saved - open file - Signal Settings - Counter Mode) (see docs below) -* SubGHz: Counter modes for Keeloq, CAME Atomo, Nice Flor S, AlutechAT4N - [see docs](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzCounterMode.md) +* SubGHz: **UI for SubGHz Counter Experimental Mode** (PR #930 | by @Dmitry422) (with Debug enabled) (Saved - open file - Signal Settings - Counter Mode) (see docs below) +* SubGHz: **Counter modes for Keeloq, CAME Atomo, Nice Flor S, AlutechAT4N** - [see docs](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzCounterMode.md) * SubGHz: Add AN-Motors AT4 button on arrow keys (0xC) * SubGHz: Add IL-100 Smart support for Add manually -* SubGHz: Add experimental counter overflow mode (OFEX), replicates how some key duplicators work, DO NOT USE if you don't know what you are doing, it will reset your counter value! (accesible with debug on in radio settings - counter incr.) -* SubGHz: Return Honeywell Sec with fixes and improvements (by htotoo & LiQuiDz & xMasterX) -* Display: Remove display_back_light bug from "DisplayBacklightEnforceOn" (PR #928 | by @Dmitry422) +* SubGHz: Add **experimental counter overflow mode** (OFEX), replicates how some key duplicators work, DO NOT USE if you don't know what you are doing, it will reset your counter value! (accesible with debug on in radio settings - counter incr.) +* SubGHz: **Return Honeywell Sec** with fixes and improvements (by htotoo & LiQuiDz & xMasterX) +* NFC: **Add Saflok MFUL Parser Support** (by @aaronjamt) +* NFC: **Add MFUL counters to Info page** (by @aaronjamt) * OFW: Fix Felica standard loading from nfc file -* OFW PR 4279: NFC FeliCa Minor Fix: FelicaPollerEventType should only be Incomplete if the tag is FeliCa Lite (by @zinongli) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes +* Bad USB: Colemak keyboard layout (by @Ashe-Sterling) +* Display: Remove display_back_light bug from "DisplayBacklightEnforceOn" (PR #928 | by @Dmitry422) +* OFW PR 4279: NFC FeliCa Minor Fix: FelicaPollerEventType should only be Incomplete if the tag is FeliCa Lite (by @zinongli) +* OFW PR 4261: Add date/time input module (by @aaronjamt) +* OFW PR 4312: Infrared: Fix infrared CLI plugin MissingImports (by @WillyJL) * Disable halloween anim

#### Known NFC post-refactor regressions list: diff --git a/applications/main/bad_usb/resources/badusb/assets/layouts/colemak.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/colemak.kl new file mode 100644 index 0000000000000000000000000000000000000000..47b9a9cfffb7d0f30d3d824b0d7d267138a0b897 GIT binary patch literal 256 zcmaLL#|{Aj007a+L|xrBoz+&bN(heP|3Avv#oXS&Z+i~xe%D_enR$0%=F+uyw&rd; z`Jmv*i<}vA7Gx}0v1Y@T(2$2Gp1z!0vTVh`wlz~5cAVIAVAqy6V literal 0 HcmV?d00001 From 0b7c9e2eab4d4d3c3d5f6f7940bf2d713425202e Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 1 Dec 2025 06:45:48 +0300 Subject: [PATCH 12/18] update changelog [ci skip] --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c9c3a839..849044e9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## Main changes - Current API: 87.1 -* SubGHz: **UI for SubGHz Counter Experimental Mode** (PR #930 | by @Dmitry422) (with Debug enabled) (Saved - open file - Signal Settings - Counter Mode) (see docs below) +* SubGHz: **SubGHz Counter Edit option with UI** (PR #933 | by @Dmitry422) (with Debug enabled only) (Saved - open file - Signal Settings - Edit Counter) +* SubGHz: **UI for SubGHz Counter Experimental Mode** (PR #930 | by @Dmitry422) (with Debug enabled only) (Saved - open file - Signal Settings - Counter Mode) (see docs below) * SubGHz: **Counter modes for Keeloq, CAME Atomo, Nice Flor S, AlutechAT4N** - [see docs](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzCounterMode.md) * SubGHz: Add AN-Motors AT4 button on arrow keys (0xC) * SubGHz: Add IL-100 Smart support for Add manually @@ -11,6 +12,7 @@ * OFW: Fix Felica standard loading from nfc file * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes +* Fix typo in README warning about scammers (PR #931 | by @koterba) * Bad USB: Colemak keyboard layout (by @Ashe-Sterling) * Display: Remove display_back_light bug from "DisplayBacklightEnforceOn" (PR #928 | by @Dmitry422) * OFW PR 4279: NFC FeliCa Minor Fix: FelicaPollerEventType should only be Incomplete if the tag is FeliCa Lite (by @zinongli) From e2e839fb2c48e13d47e996201f96a394bd55c0bf Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 1 Dec 2025 07:04:38 +0300 Subject: [PATCH 13/18] fmt --- CHANGELOG.md | 4 ++-- applications/main/nfc/plugins/supported_cards/saflok.c | 2 +- applications/main/subghz/scenes/subghz_scene_config.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 849044e9a..da44778c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ ## Main changes - Current API: 87.1 -* SubGHz: **SubGHz Counter Edit option with UI** (PR #933 | by @Dmitry422) (with Debug enabled only) (Saved - open file - Signal Settings - Edit Counter) -* SubGHz: **UI for SubGHz Counter Experimental Mode** (PR #930 | by @Dmitry422) (with Debug enabled only) (Saved - open file - Signal Settings - Counter Mode) (see docs below) +* SubGHz: **Counter Edit option with UI** (PR #933 | by @Dmitry422) (with Debug enabled only) (Saved - open file - Signal Settings - Edit Counter) +* SubGHz: **UI for Counter Experimental Mode** (PR #930 | by @Dmitry422) (with Debug enabled only) (Saved - open file - Signal Settings - Counter Mode) (see docs below) * SubGHz: **Counter modes for Keeloq, CAME Atomo, Nice Flor S, AlutechAT4N** - [see docs](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzCounterMode.md) * SubGHz: Add AN-Motors AT4 button on arrow keys (0xC) * SubGHz: Add IL-100 Smart support for Add manually diff --git a/applications/main/nfc/plugins/supported_cards/saflok.c b/applications/main/nfc/plugins/supported_cards/saflok.c index da31548de..5036dbffc 100644 --- a/applications/main/nfc/plugins/supported_cards/saflok.c +++ b/applications/main/nfc/plugins/supported_cards/saflok.c @@ -507,4 +507,4 @@ const FlipperAppPluginDescriptor* saflok_plugin_ep(void) { return &saflok_plugin_descriptor; } -#pragma GCC diagnostic pop \ No newline at end of file +#pragma GCC diagnostic pop diff --git a/applications/main/subghz/scenes/subghz_scene_config.h b/applications/main/subghz/scenes/subghz_scene_config.h index 5d7bf31ff..7be062eba 100644 --- a/applications/main/subghz/scenes/subghz_scene_config.h +++ b/applications/main/subghz/scenes/subghz_scene_config.h @@ -25,4 +25,4 @@ ADD_SCENE(subghz, decode_raw, DecodeRAW) ADD_SCENE(subghz, delete_raw, DeleteRAW) ADD_SCENE(subghz, need_saving, NeedSaving) ADD_SCENE(subghz, rpc, Rpc) -ADD_SCENE(subghz, signal_settings, SignalSettings) \ No newline at end of file +ADD_SCENE(subghz, signal_settings, SignalSettings) From 05925868d2f9df42a5d8359c4f644978066f792c Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 3 Dec 2025 19:59:03 +0300 Subject: [PATCH 14/18] upd subremote --- applications/main/subghz_remote | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/subghz_remote b/applications/main/subghz_remote index 0cec9db02..885187e22 160000 --- a/applications/main/subghz_remote +++ b/applications/main/subghz_remote @@ -1 +1 @@ -Subproject commit 0cec9db022cdca2f5ead27115bf54c715abe1ca7 +Subproject commit 885187e22ccd934093719aac0309b5e1b829f83a From 13b79f0246940dd3de9edd34b84b57a751feb083 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 3 Dec 2025 22:50:43 +0300 Subject: [PATCH 15/18] upd changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index da44778c9..edef23001 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ * OFW: Fix Felica standard loading from nfc file * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes +* SubGHz Remote: Add default remote and clear slot features (by @jknlsn) * Fix typo in README warning about scammers (PR #931 | by @koterba) * Bad USB: Colemak keyboard layout (by @Ashe-Sterling) * Display: Remove display_back_light bug from "DisplayBacklightEnforceOn" (PR #928 | by @Dmitry422) From c8e756a3c52a615514c74951f9ec375b1f956223 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 3 Dec 2025 22:52:09 +0300 Subject: [PATCH 16/18] enable winter anims --- .../L1_Happy_holidays_128x64/frame_0.png | Bin 0 -> 858 bytes .../L1_Happy_holidays_128x64/frame_1.png | Bin 0 -> 855 bytes .../L1_Happy_holidays_128x64/frame_10.png | Bin 0 -> 872 bytes .../L1_Happy_holidays_128x64/frame_11.png | Bin 0 -> 861 bytes .../L1_Happy_holidays_128x64/frame_12.png | Bin 0 -> 853 bytes .../L1_Happy_holidays_128x64/frame_2.png | Bin 0 -> 851 bytes .../L1_Happy_holidays_128x64/frame_3.png | Bin 0 -> 852 bytes .../L1_Happy_holidays_128x64/frame_4.png | Bin 0 -> 856 bytes .../L1_Happy_holidays_128x64/frame_5.png | Bin 0 -> 850 bytes .../L1_Happy_holidays_128x64/frame_6.png | Bin 0 -> 851 bytes .../L1_Happy_holidays_128x64/frame_7.png | Bin 0 -> 860 bytes .../L1_Happy_holidays_128x64/frame_8.png | Bin 0 -> 857 bytes .../L1_Happy_holidays_128x64/frame_9.png | Bin 0 -> 863 bytes .../L1_Happy_holidays_128x64/meta.txt | 23 ++++++++++++++++++ .../external/L1_New_year_128x64/frame_0.png | Bin 0 -> 1740 bytes .../external/L1_New_year_128x64/frame_1.png | Bin 0 -> 1783 bytes .../external/L1_New_year_128x64/frame_2.png | Bin 0 -> 1754 bytes .../external/L1_New_year_128x64/frame_3.png | Bin 0 -> 1745 bytes .../external/L1_New_year_128x64/meta.txt | 14 +++++++++++ .../L1_Sleigh_ride_128x64/frame_0.png | Bin 0 -> 820 bytes .../L1_Sleigh_ride_128x64/frame_1.png | Bin 0 -> 881 bytes .../L1_Sleigh_ride_128x64/frame_10.png | Bin 0 -> 788 bytes .../L1_Sleigh_ride_128x64/frame_11.png | Bin 0 -> 816 bytes .../L1_Sleigh_ride_128x64/frame_12.png | Bin 0 -> 864 bytes .../L1_Sleigh_ride_128x64/frame_13.png | Bin 0 -> 798 bytes .../L1_Sleigh_ride_128x64/frame_14.png | Bin 0 -> 813 bytes .../L1_Sleigh_ride_128x64/frame_15.png | Bin 0 -> 879 bytes .../L1_Sleigh_ride_128x64/frame_16.png | Bin 0 -> 855 bytes .../L1_Sleigh_ride_128x64/frame_17.png | Bin 0 -> 772 bytes .../L1_Sleigh_ride_128x64/frame_18.png | Bin 0 -> 817 bytes .../L1_Sleigh_ride_128x64/frame_19.png | Bin 0 -> 867 bytes .../L1_Sleigh_ride_128x64/frame_2.png | Bin 0 -> 866 bytes .../L1_Sleigh_ride_128x64/frame_20.png | Bin 0 -> 809 bytes .../L1_Sleigh_ride_128x64/frame_21.png | Bin 0 -> 795 bytes .../L1_Sleigh_ride_128x64/frame_22.png | Bin 0 -> 870 bytes .../L1_Sleigh_ride_128x64/frame_23.png | Bin 0 -> 852 bytes .../L1_Sleigh_ride_128x64/frame_24.png | Bin 0 -> 805 bytes .../L1_Sleigh_ride_128x64/frame_25.png | Bin 0 -> 858 bytes .../L1_Sleigh_ride_128x64/frame_26.png | Bin 0 -> 830 bytes .../L1_Sleigh_ride_128x64/frame_27.png | Bin 0 -> 828 bytes .../L1_Sleigh_ride_128x64/frame_28.png | Bin 0 -> 585 bytes .../L1_Sleigh_ride_128x64/frame_29.png | Bin 0 -> 431 bytes .../L1_Sleigh_ride_128x64/frame_3.png | Bin 0 -> 812 bytes .../L1_Sleigh_ride_128x64/frame_30.png | Bin 0 -> 281 bytes .../L1_Sleigh_ride_128x64/frame_31.png | Bin 0 -> 270 bytes .../L1_Sleigh_ride_128x64/frame_32.png | Bin 0 -> 236 bytes .../L1_Sleigh_ride_128x64/frame_33.png | Bin 0 -> 485 bytes .../L1_Sleigh_ride_128x64/frame_34.png | Bin 0 -> 771 bytes .../L1_Sleigh_ride_128x64/frame_35.png | Bin 0 -> 887 bytes .../L1_Sleigh_ride_128x64/frame_36.png | Bin 0 -> 809 bytes .../L1_Sleigh_ride_128x64/frame_4.png | Bin 0 -> 890 bytes .../L1_Sleigh_ride_128x64/frame_5.png | Bin 0 -> 819 bytes .../L1_Sleigh_ride_128x64/frame_6.png | Bin 0 -> 799 bytes .../L1_Sleigh_ride_128x64/frame_7.png | Bin 0 -> 817 bytes .../L1_Sleigh_ride_128x64/frame_8.png | Bin 0 -> 875 bytes .../L1_Sleigh_ride_128x64/frame_9.png | Bin 0 -> 823 bytes .../external/L1_Sleigh_ride_128x64/meta.txt | 23 ++++++++++++++++++ assets/dolphin/external/manifest.txt | 21 ++++++++++++++++ 58 files changed, 81 insertions(+) create mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_0.png create mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_1.png create mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_10.png create mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_11.png create mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_12.png create mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_2.png create mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_3.png create mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_4.png create mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_5.png create mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_6.png create mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_7.png create mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_8.png create mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_9.png create mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/meta.txt create mode 100644 assets/dolphin/external/L1_New_year_128x64/frame_0.png create mode 100644 assets/dolphin/external/L1_New_year_128x64/frame_1.png create mode 100644 assets/dolphin/external/L1_New_year_128x64/frame_2.png create mode 100644 assets/dolphin/external/L1_New_year_128x64/frame_3.png create mode 100644 assets/dolphin/external/L1_New_year_128x64/meta.txt create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_0.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_1.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_11.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_12.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_13.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_14.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_16.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_17.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_18.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_19.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_2.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_20.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_21.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_22.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_23.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_24.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_25.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_26.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_27.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_28.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_29.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_3.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_30.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_31.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_32.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_33.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_35.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_36.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_5.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_9.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/meta.txt diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_0.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_0.png new file mode 100755 index 0000000000000000000000000000000000000000..f1207ed14b35cd01eafa00342f2801d74b46c622 GIT binary patch literal 858 zcmV-g1Eu_lP)b3G}_zPLJOisK}_wXw<>5vl)fnyEcmLJ)0YM`-qsd9LG<*g zg3uHHfWB#8LN=zM6$A}^iEZj_uEAVvNj6Dq_mbTmAFd!cFTa@&W`4{t&64fs z1O6+|kM;zum_y0;LR;%E{4|RT5jh|65*n~`w!6HK#2L%5CukdP2q1}?sRvZR12EsS zP6BKKYE)f>e;Pi(R|nZq8>DGO9+1VXzqs;Y z>zzX{C5FwW0Jpr#S{j^JpU8;?=-uOueU6(!BS9RndoqvP1aOo&p}tH{j*$Y>F2CbY zJTRxpb}`rb=#NmV8-pBq^GnU*vUM}Lrs&3DVX2NWQRPbV9Eu_gHSp)RdG_xa2L$n& zAzo{&Ly7kt@}aaYyloeLFiehuLtK&CfvZv9i~0aRdxU@_`$?=4uDcSHyHB@B=3sjv z1Yp-C*-y?RIqd_Gb4_y*`NdA5GkU`(19o4%V_h1`c<^#Zb!*T(J_a>H!@b?Pw{we% z1dHI&$2X)22_qD0fIX;@)4QwO+?wU}hx4OTV?~Yn&m|Y9uV0s?!lVme6X@3g$i9tJ zAw<+xNi6@`UU{o9>1u#x3Q(N{@}pCiFtmK`&hbpo$P?pJF1bH9`LkVT#6U2cFbE**&wk(4~aXcues_UO>rxTx;jl=<04a40ce_veR#+-lBGlmLLRKtj8 k04biGE0u;J2K9h{0X->d_eOezlK=n!07*qoM6N<$g2W|{hX4Qo literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_1.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_1.png new file mode 100755 index 0000000000000000000000000000000000000000..9d9012281f0193620bc458455f6a93538fad2e96 GIT binary patch literal 855 zcmV-d1E~CoP);NOWB)|bSkT1aRXRBA7yY|TxF{FSfXu8*lUk&&qCXhO8F)FkoA>H2*0;2g2fv& z(d#Xs;?yt-?E~*0`D{sLX^yAU0@$ekWb2zI3N8X{Fs83orr0sQ#X|guwcpNP1=Z{G zSdat3yA1rXr`2nki#Nt*x!NSyKj4u+UwHlRdp$A`_S7#~2^ znalc2P!kjpqG|x})i68iBGTxNe8ACYahNl>a$BAkJMa8EWISSW9x^|7!7He$DqzaK zHH~UJtLg*3q0pI4GT^O->aCwBq?&T_iw|3e_BWDe59<_EmFTb&R`!3M<{$oTix6M< z>)gGM21gU)@>GCLe`>uG{-^#CUE~ZdT5WwA&`g90koSOKH57+|(uvFv@ zu5jrw5_rGj>oQ@wX!V1Y;}RF$?qIcGbR!}y4R^HsbN*(0SXHAg%c)Z)Sl#Bty=5=L zsgMqRmyS4iNFy-1b&V$Edv0=qawEuV>%O>0>(QCF8Rm}903b;}$!G~1tcE58ti4NG z#Li1EDY$>yq#tK|B{A92+mN-R_2;-oJ?~InHFlt@zLj6-(yP6H#HwvaCccFQEMv5L zf=vPm?`HFc&NPYoHp(3VcND9qw+G8F!`Z&vP|e+|N$-{9)>3?5mJ2g}w){{ph6J4J z%MI1syar5eO=xJuFUo-&*Dz1D2A`uKvLp&GC&Xeh15D$Dcrr0Le)4jNZU3t?tW+aRjFfo7> hF5f7XLU{2m{sTJ-XIlu4y4L^z002ovPDHLkV1fh!ld%8* literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_10.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_10.png new file mode 100755 index 0000000000000000000000000000000000000000..cb8f173b0bdd8ae661a0e3c000fa88fe256b3a30 GIT binary patch literal 872 zcmV-u1DE`XP)?*-2wV(L$SR8{9F5vK7gWF*K9z%sdYhFTTrr?}P9A_;@_Xs~B$& zdb=C`2i$J}PvkF<5Lip!C9yJmJk zyD)1^<~b8;r*9}UQQZePJA_5QLN`m13s@3OkFb+2eNEr3q|e{3Do1W(`LOHQ*{Ccc zB5$Q4jUTjXj@h}Yt9&X@PQ4@to~kt#+CNgxBxB&i*V>Ql>6XdOn;M1U`%k`JdYzb# zxvXjd@yA2gSI!^4_f;b4ZVha>V{2(}QKcwlYoN5(9sVe;;QMGaV0UFMDS=xLDQI_k zl`c30L1j1u&AC6(zViz%&P+gL(A6Pd>t%<3qVh2Bofy|h5cY3#mMO5li|Y^PWBb5vv#*6Jr_FPc<~!% zzI?osA0I4#%i;aLLUqKAW@~uXa`(Ts#~MgIadhi(T6M8oL-0000;RtW zYC;GgHWEVi?&N>ACW2t*m9`GWO9_C1mL9w88*tuE7R2n%(<4e*f~ghW9``l2w{(Idzmy zutB!vuh7(q6FumKEzuwW3j>H)yTC$Q)<$cl-5z|y-D(1|M{-+S4~zn(Om^kCIMPHS9NRn-wo zb-G{73WfIdhPR*xoUE3%!LeNDq5+&-b6)u9wbmmCRLA3PDHsgjUH*FU4P5pLwkgFV zmxg|AeEVE-xl4Bs3UJRKUC+WFO%Y=k1n4^CS3kxxCIG~RLNWKb&+NDt@uDzso1(=) ziO{b`1j1wkskuLG`gr?8T!vo%MDt|Wxs$H6RMRzvDm|*B)6Vsz{HJQVCiK<`7I%66 zpD+s`5q~AZZ&Y`vK%F#^#?`Zh(igxfh#;bQczHYO!l%F7Qcd2Iw2#!O6k`4kFx=;dmqLL2aI=r`kQ=1B2Is`ynS&V z<-m2T+}iw1!W-ao64<|YDyCoJj>kKX`oA%;cSfI$NThnHV{pp1@th#<3}a7jmBu`P zDqsa5kKKJg0*?!;XMmeM@@8qw(-6&dL7+AUbPP_-V{~{se>K-hd3t2Z!}?=`PrJ;# zQnh39NRQzpt(~lq3*y4nYpOBlET<#Dr92W2XR56aRW)_yVsXJ!W4uiuWfyz#sBd~p naeM(t04ZO&R;l#2#RBjz<}zf`VzpON00000NkvXXu0mjf*Yuj_ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_12.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_12.png new file mode 100755 index 0000000000000000000000000000000000000000..5d4c7e7c55fb7012fa223c9084bfe754c5b50e4f GIT binary patch literal 853 zcmV-b1FHOqP)24#t z)$%{!Q45Ty-Up3=wft=c7FG4Oco@|*;BzJ3rR*z7I#_C0{0L^xB_+HdKGwmDt*hX6 zDH20Ibn!pi0Fyv8^LjZu2{ML(&ZIO+ZL(v#u~+Zi{QU%ZB`c)=A=;6(xnT!)w=IGt z=rYN(4d4fF8i~Na_14g#ID(kx5x^!L#~NS7XoQad>-6bk!-Etiv{*>*vHHuoDNw!k z6Bgu&=ryXNZat%G8DUk&U%K4Vh#e5;u5{tcO?gnuU;Vpo0uJJxMn=zn8dOzP6(9`7 zEDrjmYFK79AIVDKNJDk`d#Z&N9J_G3ad7`tJ->0As;ZJa<+8$t^j++69Ulc2wFZz}~l6 z2i~c<%n15&MiH}mc>V#t(I9v-Q%ybCJHJ&K+pAM;f5?b#dvfM$7{k&>F~d5MxP|iG zuzq5)*a~gZ@(lPVvHC{youmzh+FR<={yxpxF6ZCPB+GWTdbU|UpW2_RHsYTJ;tjyJJ$FNDVQx?~LS0I5z~ f@9uVx0t);KGZb(kKhPw)00000NkvXXu0mjfXi1u-?7W>nA=QF^N=Sn#Tt)k_1K?$nA#5M6H- z3SIFJs3+S?$SlbLxQwjlT}@4XMa_vL+g&bDiVrlO%h|I11tkAJR*h4?o`ocsAzjOk`v+05A z6S0AbLLD1$CbL%`nE^E%K>+(g<*^MDqF^JyR=fZHhu$#kT7e)w$r|myavG?9auyq; zKYZndksi}ZIGTPdw6*@?p1uO!w znz&u}0w4q@tEFA=Y*Y2x6`WjihCh3K<3L|C-CkX!q^iUR3udD4T<)wc*+BBe^$%O` z9DO;pT($(b6I9nTb45d9FA<=3UoiGLeh!Tk@xblL1OCRq3Fd|R3OxmeO3e8Dibr{1 zUeld&q4m*kk=8c%bL`D8HBUyJ+v!b3H;0AgI@Uyu>*@0-ig2hwxUj=>f6jU!NYIQ3 zT4Nn5ydO}Eq;>UexAYxw0t`u2rUq`rdtV~p$Dq)4{V=Ihabs3ZrTcVC0t^1*bx8)pZ36wySQ=&?|`G43`_qCmSb|uFJ9h)-AGPdfGG$gIuCG%s+c7RI1Xef zDMOT|0C&IEz1}^e&Fwi(K3p7`9xLnjeV5W}Gq-NZa%s{B7{ln-2ISGra}oGVt&uw6 z&4cn*Y0_7N<_b`o1d1cmKVj+UrGHN4dS0F!pY|#IzQs2U8)eLn$s>IRC)sFNgSj9c zT%A{qnQ&zVJj&x{G*jL9R6CvI^js_+_-a}HHpK^%`VRKOQ-O7;6vtCo$t)n{i}RJr dAS9px@GqP{Wg0BXhk5`2002ovPDHLkV1gwEk39eY literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_3.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_3.png new file mode 100755 index 0000000000000000000000000000000000000000..3a47dfc170224127ad04528accd7b47104c33ae9 GIT binary patch literal 852 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_7.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_7.png new file mode 100755 index 0000000000000000000000000000000000000000..da3a78f4c7d2364f251efcf0d311cfeadf3693ab GIT binary patch literal 860 zcmV-i1Ec(jP)HGw^KWKzD1O2?a4gD9ViVO(ALi=Ao3~!3nM+a`evB?;V{tr$^<9Ht z@Ozx-`5N$}TShE+x8EB(Sg}yy2m+X>`*`iE3Tp8XV3Q$zVtka;2@D3}hg5HzodeaM zuV6r)vTsm07j7o>+()SD%qy4MY8GPL03l%(8fhil9EnrbU|)z+i~uB2(+ps1v~_mb zU^43xhMmAih-NLo^Z-r$6p@vVC;(iIW>)CMlbf<#Y`*@W-|d2p} z`R|OgGP|3p27rY^b0W!r7i;O6Uw)vFYRd8R@6`71T~K%Z4h2;uI^c%mdrNwLec366 z#9s{jc5kkyehI4U0&E4N8_n>0h2Q;HfR25^&}VTGcrHNzuRRTD68cmrOE9xWI~;@g zRa*h!!Xw1`zY<8^2~%tI?Zu-K7rhR99V&Q&Uq>%2}c?`l}k+I{g3Ek&naV}M(`v%`QS-6Vs37`vu}7BFU+ zw1_z;d5(bhha}y&C&I*$mHU1m6Kn? z6o#RHp1Ib6S_zl4d*i*e8be^Bwr9Z`#3bzima(q3{<4?X#IMh*M(>-2!l_2~{L;4& z!N*-~{betw7I$4WbWE^ts-d8%#zHc2259RqPvK~3t8?OkUX;Tl>$Z m(G*Yzq;TDI@s{A_#w=kO&avP_}B;zHLpOl zBas;Lp^N{;2ABlG>9-nlBtgb7(4I6xQk!hsPVA5OZ~bv>g;d?_qHs8 zCFn58i#6aoZwd*)zy03OfjESiml41wZAWWg#%PF-0BiK>lf#1)##t<+_gVSn>=jVG z{sR`|nea`@qtRMM#}dMdj=y@fsTM+t=MXX`pxCl2s58CN>+<(53EMTR1?@1s!UhNz zA0T1yGfqsHXe9d1ERt5*nnQEm^aDj_SFZivmu{xz3DwlI96M&h#chtg~lh@tevO^(pN5SMgwUHpS{$p^2~#jlQbK(aQno=$AI7%fJCweu7e?5MO* zfPHAQHoOzFnGy80j3Q>`;M^m8qe1X&rkZ-Ndv3Edwp*{X{2?Q{<;lBW!5EfaifPse zC7K5Zz}oT2V$<2A=>_n6u=;lW{iFqkTAQj<{vOR*uI87flO;P-K3Q)(pW^Z8`A<00 z+EktLOB!;xepw@9-OS0lf);uk82K1zs!okzwEJFuxzGuDX<*9X{k6sy6*H-vkc1-A z4-Y&=ZNpqEPzm7r4b_BUlYaz4c_8Y}RcoQDmkiA$5`a@kJquYuQ+46qht*5 j04Y!0=|GNY91Bxel{hfU)#i;bjeXFf0r)cg|m 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_New_year_128x64/meta.txt b/assets/dolphin/external/L1_New_year_128x64/meta.txt new file mode 100644 index 000000000..06c2ee6a9 --- /dev/null +++ b/assets/dolphin/external/L1_New_year_128x64/meta.txt @@ -0,0 +1,14 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 4 +Active frames: 0 +Frames order: 0 1 2 3 +Active cycles: 0 +Frame rate: 2 +Duration: 3600 +Active cooldown: 0 + +Bubble slots: 0 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_0.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_0.png new file mode 100755 index 0000000000000000000000000000000000000000..0e86e6641ad50bc72708a7b895055522815e36d2 GIT binary patch literal 820 zcmV-41Izr0P)1v7=iJOBZEdWHjA-aY7b&xlmQc{Dndu)E{2@XnsnFVNDlS}PQBY`^8LX}% znuTQJqM~4B#zm-92_+Bf zi2p4g0OtWXwe8q|fXG%h&?xNYu0n&b4sL z^QO4X%7Pd6w}FAu%DJt*<9W?LZ}sJzDy)43;DZ5hUZWUqm44ih4)g{eVM}X9JCviI{E9d-bNa6iy*vm1^Sk%H{e(E@vBc# z-aP=|TlA@2q#79;JlIL1;H0w(ER>@j&yA3SNAp3&T#H2>$DO}UvOsY1@;7Fh)u(4; zt7iZxPyTM3-}yp`+0emQD`kSyQQMw0=@Wb0>0bbdoVK6z&aSpu)4j*~fWh82J(ahs zk^ZhEfYWDrc$Q;hgYH!iU><9-BSbx#yM_UN8nQ>e1vk4G9Zmr(R&7rJ9b9?yXnzv? z9;|Z+f{l5Tx$42x*?ZPK)0H*h3u1W~-EK=+bL4|;=wAanQcUf8|2Udo2oy+Cdv8L! zW>BpbXhV_;PA=Cmfr9i4`d*SM(!HKE(xp(jyCf-y=RThl2`wDk>F<@KOZp)req5=I zt7oqy&E?_}H9tt7zmbrnS|wGI<6b=68QGTva1ZnsG5TzOwD^4e+eWX)-a-qNT3C{T yjOM;xOQ2ei-mUtO+R?u!EKPKl0XTtM6#oJKCt7uy9%sA&0000O9aKtjFBFS<5rrbC%}m9M zUgRQo7XRX1b*D;^N?Ky=V?0!jd8EhM#x;n(iGKn8%i01^Q66Wj%mffr}U0-1Ob zAb^oTl>oMustNt26ldfm(WIn8cS^nw*cymV$9HzJdP{oZX z0DX}26@XXvB7mt|#QDeoiB>l%gnMp9<1RSsACR#L4*RrA4QTF}P@O3{P!=X&k|;Kw z{GG>YUjb)6wR^FoRzzG(K&R@4c1B+{Ck6u80E)uu#8e?{x_(D|nn3F|y%Owv`RPM< z0svYxdV1{gIr|r%6wR|rG z_Ipb9%K2T_WLJOdjU!Ia9LO0bFr9z*qjY7Kfbu;WBwM0Eytoa zv${bA+da&=CaBL3UV&D9ecr}20W+2Rq}Z_9H}LJJs7bb1)9v4MC!20Be7t`SouEjX zTsClwx2-l*f?XqA@%P8snc^`e*gjXTY;jv*Dp1Iz~P7fSP6>B%@=;M*NoN z&=$~kJR8Xvu~Q%XbJo)dAR^srD5aiZ-)UR74#8G8#=Rxu>vO8x(`%goMkdxKM;_5P zZ5{Tx07J%srCf@u9_7-oIH4>{e&LkJ13AL?UJoUdiY%}6iF|KsmFfb)$i%E)32)oT z?B9E2TqG~)Q3Yqo~o zxmYX{aZx*b>OB*Z?-*%R3J$iGAKipVIPm_Zv=l_ja&*?#pgOGkGbAD6?ryXZk;1r$ zgpSDY)KMFee7R0UWLTsi-{lZl4&zc)SuWa$RAgPDvy}TEA-!}aN+5?L00000NkvXX Hu0mjfdXb+( literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png new file mode 100755 index 0000000000000000000000000000000000000000..5459ae27c3e0b2d87cd3a2b68996ceda57cd6f41 GIT binary patch literal 788 zcmV+v1MB>WP)EtRqp8xfd%lOk=O*D>zE{8R{hs%EA5QbX z69Qmufyo`u1_Y$GvzL2?9p9?;NLR-=tK3H!AMk?h0wr)JIMuBH8gL~tfxn}}F~9Hl z<$3d+?Z7x3h(1Z$TV(8#I-a(CS;x3zBfP(Wy?+u2f9ZjcsCemK z<&Wal6rHP)KJnQ2Yvtay%j@gj8B=M*sn}#EX+NR&Um>=qX!_pf8Yg>hEzIEY$uM!~ zuUHdl*=yJLoQZl3Qs7L?KGb=y3kHxh*8cX{mbzm{kTmB!c5`-UTn&J< zi;4=Jp|JJtQ2@uGxF8J={k*IYuw2(H9Nf*1CmUz4QUwRhM%rSr=%v%@1-VHpG zX>Y!S*k6sK>@W1)wyDj22^axEz0^)k#2Eth(ON0dDZdreV{iwM$xVfJ3=R;nmUjlM zkIP-KkC1@=DueNqUDFnguRnB%j=2~?_}!KM4^rcK@M}knfZv+b-#PpJX`*(@2BJ)} zaNeHDRUsAFK-0he>Q9Cj2YSXfuX=A1S-C3gx@!a32OlmiuH_RBGzvD5ICz5x0uoat z{>dS!r6pY}w)Cs(4!d3ZqOY|2)f=COOUEq0l(VRO$UN>m{f%P*rjW;&`K2T7Tmw8Z6lW$uEFf_3*{&fP*BdvUl3oztSDYF#=e*{#<6Y z@2PT-f}$j~78AujRW#=~%x_4F-kx6gRJYs_*EOv~0=S@wNBws2I@rmXS`A)kr86X- z{0xjPjE+M4?q1+r4UAUOdqB_spqcWYb+E}M%Vo@AOo3b#kW`T*X<5;T>;4B1IZ1|x S#su9y zXzB&EP@D+}ih{(@X_M%bTA~QHMVd@Y+lgp0)1+x;&YU^F4|6efz3jcey;y7gKP>j} zzX$+u{sV*8eH#$awvz2^UTou3wn^%BjQ2M!)KLP8Whk`@B*5FwGi6BBK^~5Vrc%Bx z!!zfWgB5Ly#h+_Mtr*Q?Q|*O~S36pyZ&YD)F=)-@3=BUtO)xW*`MN8{(m6=&19!`NGz28WWYy2D z+1q#;8#7%4>R+{grs+~ag<|;qeU-2dT)UpNLrnkp%Qp?vkksdoM7O&2zCB2q^rAsE zNbCd5P9!bNM#nGd%QDb@1W7?6sy8)N0=&nOq>e}!Nh$?S`fDTL)RSjdZ#ZYc!?#qd zxhr<#Lh17CddFkQqNHjmoZb7Atjo}DRGw6jONIUf+tP50#f}GqIZXdUSuX>36-<}@nO@B3Kfof;!_8)rw+(TC;ykfz3 z+Zs!+ik#i9-MjZss&8!74os3!|1P@JqO^C)hRgXd3V3~VaW*aQ(dK( zBVB#P?3!PZr114b5K-1ir#FsS0MmVwb-Aa;(!pa$k_xATeam5Q`irBg?^UPoj(3>X zyvk%>=v;R5m{&()cOXqk3LlW9 u3nR;Mq(w=3Ly{^F&W|AlYDO5H8~Yc`rGxxvN7noR0000kltwRJ zHqlzsn8JD1t4U*p9c#2VF|pAm3NBb#Y=vFe5@y-ic^+mVzRUaPd*A#M}wUV0b+eehAL4h6(YK+)S~90XEeon$0@jRSee z1**;&2U2LCpW?xeSzJ_+`+MV|XB|L)n`aCBzmD9N9DuHZ(CO(LZ-gH-r2tr3l^L;t zh=jz-8J6!X#34GaY(O_2UWzjVn5E+Yni{+@Dhu{LyZNn9T?(s@Y97h_%fF3;*|l64 z4QBKGnSYn#VJt@ClE2XvRIamHipXqg_Ee4_ZP<)L#To26V6Da&ME`Geo*HOF!ECLr_vT<^KPyZ&y? zf)c1S`fIsY%e9pgb_i6X!JW^z8JU}?A3#?OD*E0FdvVCq0_AEJVIuEarTvP0NgR%&>G&UbFoz$}i=uVA#~b z@Jo4rqi^uVLmy*$I?%p8W;R6omrW~_o$vgsqp~|Tvx_aeVN(MJ(5tCT&tl7lb->bL z&s5C(Vk+OL&9>t;O9{dBF{%qaEO$IV zv>!x*Ga}WfB&%{VxnU`R>F;g-aq}-PM6T`+1B%6<^tj6iUP-m5bf9+=RR4pYg2rr{ zr37mC`h49p*P9mG&Y}c37`tV9rq~y6ndm7OrU1`WT}F2X?v_Q$YsH{bq_CcF994-# zsQJ&G+jo{N?>y+n71xansHHc-xMG2>;u-iTC;$NOo|M2iWDSBIL?2C`f~p6notk`0000R1tyh>M^I#_qv{QP$XrggLvOd7m!M?9MzFFYo`y^Z8emhj(nv z&MH@wAp>JwRUy-zK)tsfn@+-UZl|( zT7umoary&@;Oto&cW3Bxp*q?DA-WPrA>Hx(-0YX#T9+ErYr6N+8=_3c0}@aG`layq zN0$!)qX#{0U+lCF<*WJUhC+sxnbha4L=+=ZR}5>LR2JCsG3^VgcA{F1 zl`I{k9ksc1ZQRVK zs9{9@mEvq55&=BuE5Rz;SH`(93Ah+Q>i_(-uGBK1U6f$$-P@7 z(E61dDTey6mx6n*{JdiTU@i-Y$m03`yu#^%@1Mr^mB8YP>BfrpKk}dvmX*M-&%Chl z^X>1_z%FK#(CW1d&HkQIfJ|&a3Ab}S3uDRBV(L7#cer#4ps%S;ym8{*ajG1z0Yr|u z{Xpp)Y_$i+0q*3CF-`)`7eyD~@B#Cz2I%*C_Lm^JS}}PwK;(S(miZxq>)*I$GhQa# zoxo!sB6A1#DPd~L8DH6BTi&P74SqA!tz1T%l3RUw>0QI)JDF~1od#ln~EgM8``UT%NteSRUuc||C zBnU9%Ym+e8N}%_NW1xh^KT1|xb;RS-5DZ*8Rsxg*uV%r(BToac4{`w@QomVk$$b$K cTk!q=0mCXct2Z>fF#rGn07*qoM6N<$g2`}%OaK4? literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_14.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_14.png new file mode 100755 index 0000000000000000000000000000000000000000..a5d6c1a7a59b8116e393bfade90b5891981070b4 GIT binary patch literal 813 zcmV+|1JeA7P)1v7b7Rs(o7gnOg*DS66-E$$HkCr=ieeTj1et54L@9!bl#=W!D4i)HB3-mD zBK|A}E3_q3jZ`XySZ7>F0;NuAp%t~swa`vAaemsAnaSKauZzi~iDz@Z?>R3o@Aq*S z=YJ;zz_|-X*F75$(7BfFY8CFvt!#@leT=`rD(Wo(#p+rE5BxZLniW6*8EdNR6~6g% zU6f*RCS2RQZ?Wy0b&8$SvTJiO;lI}MFaX>rG!-oiw{H~M8ml10#-{6slXoSKdF6}U zzj|z0Wy%){3Wx>Gtr(Y0qc}Bo9ARYw*+gAcqm|aUnEQ9r;m&Er6R9{RyRj9f&4}QK ztYe+X#{l%Xz{2$p7vpS50&I2w9r`r-`sFNud7MylDk&`m0A0t8yl*#Mc-|xO$9I6g zOe|Mc?Dup=A>d3-ncTx&#%>q^(%`dfZg;_ro*&JK{&~PT!5#NrtgZLNIfm#<)8&E* zFAn(aFOoeu0sTg(_Rd%F);6L>F!u6u#~{8~orUv8Fm>_F7J$%2U%G&0>g%ntfW-- zec$&Vk}J=g8cHOS-9Et0s%s*2!V{5TuLt1}uGy0fVQ`ky0sQvnlH2_0r6`XaLVs`e z9athzww-R@e{A1pB}yHO`%?`mq5hGW?z`%Al-tgfY^~QLeQIyIH=w4criPVln^##{ z8ajX-yn47^O?;G>jZPlTh*-XmYuriYYEnwSB*aYZ)|G?ji}N<2Ym-sQ+3|VzCzWvm z<1sM&XaM&8L{W{63FTzBrKg3Yq8j_8+tS3Y{qxj%ic;ePrNV@+ovg&j-+C{jM#a8u rQ>r~%IE&P{qDHY?HF?>=f6x90pDlLgl6o*(00000NkvXXu0mjf1J{uq literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png new file mode 100755 index 0000000000000000000000000000000000000000..57b2c9bbee26fbc31ea9e384d8ca8c44936db89f GIT binary patch literal 879 zcmV-#1CacQP)~9or#-tqXwE=trZzs6*sn#Qc^H<9#z~Z znvGTpHdzRj*EHGvpJk|zVmyz3{er~ zRJ4A-qSzE22f*LtK!6Znr32yxpa5V8kOUDc9n>+g7yva{Xara*@Sg)>UsD<0H9F4%#7+c6@3i;;3lqVlFP?gN7EtTWC;%2# z#Rt^ml^V7kc?m2Jsjg(W_|fU|hYloy3n=+GP3T~(?GFi-licd6WZ5MW!Uk-bp0CnH zdV;G!!aw|$6jWZssSq8i``+Ff`{ua=Pdyq(|Kd!Z+7C|A0as;{qC>(E#(S*jb* zz%}pic+asr#b5aDQ=TphvIIU13}^V}&P1 z7AI2-Uz|81a{PEhru0d^T$|K(b8`s#qVomo%#_-Z(oa63UFf(JZk5-&^^##mem^L( zQkLlI9`QY+yJ@_a<)q6fgQ!Q1hAV%kJr^Dbe-7`Zln}2uGrz_1J!{)7|137+9bI#E zCp@g!Y1DQ{b8Xl6lSN-SjJjF%k+ErRSGv>zbOO`Mn$Eh=J2wY_`qSCMt47NYttvkR zpdL=MC505m65=&TyGYK@|CmHX65<^qCsLH7w@vUlBc2OY&dz#+Nb$0G;`s@YvCL6} z$U?t(;>nmuPA()h6rTVf4oyT-h~#c1JSmHa_=&s8e*u(ie_+i17}Nj&002ovPDHLk FV1imcqT>Jn literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_16.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_16.png new file mode 100755 index 0000000000000000000000000000000000000000..4be832c648910992ec977cd567913fc99db14545 GIT binary patch literal 855 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 nfJ)I<216I^jeL^CZ$d11@;kWVw|A_%ItK^W7EB z2jgA3l%Z1jQH?Ge_7uDKzug6#rBm-o<-5Lk-#Z@2?=%7|twr@NBbLC+Rx;hHQD*uD ze|wjxIr5P{yUD$wxhVH7j_J5H_964YtMPjLiaJwT z*7BuKEV*h6#E=>zi_P3y?IG+yQY_t^y_0KI>_uwCYQaJfxd3JyX~TrEbGqdNY&(PI zHcxB)u-+U4@Sd}*?3>sOyv>c01;^{LKq~gcuB(@i0nZmVjOOF_r#PQU=w`oDVBM1X z=@2{|9YO3UvdxHdhkkPtE88iLo`pUm_FWh|A75&BJ7mGF`jx(cLiX>*MYv?a)nvbI z9xW1D^s&+PT5o@l)EAku^Lkz*Mr${E_f)g95^L}I(p_Si#fUT zpLd~H1V&H%ZWJDP$8A2gP)boH+IZHsesm_51t4+468n4!v$y`G4FQ7@t6n*@&D}15 zQ?T&I7+}8k0sg{>@<7Y&!lSBjCxNLXhV6eXKv@8UepLL-izjJl_~_k)&GfiYsYKKjefYg<<;ilyvRvkXk?o z(^{5-Ti!w&T+AqtqDP9;YVGErOHUs2a_KuCX-<-)Dd}3NoLUANfIZOAa`EHXP`8`Pm&ZK4P(h<0c%ii$YU%_p4?zWA_o8@|Yp zf>ie)v-71k0U3(S(Mq!y6%{KunKE4OgKf~dCT+UqCb_xChopF3J~-cx!^h#{Bu$yI z=CtRUgALhN00=iY5D*0-`4;g!-~%`do859zHX z01xIuqcI@iHwpXUb5v9+jdv zfJ667{=}xp!B#6++WM!CtT!AOYZ(`}Zylub_fsBpfI)A1k#eo$-iGpQ0c#n)Cyd2L zW*Qyg)pki2z8TeR*CkvSkZL!irJK_#WwR*t ztyFJUs{V$nYMYDXXH+i+^NkaGHT!AS0^?9B^lsYx;B4 z%D?)F(^6{XS%nQ3DY)j-F3qn@N&Q(Q?K@PsE|n`I(pRnnOxC;qfmq~c!rLyA%4^~A zjFg&D_?v5iSkyd{fbqaH6Rrb9hT1;+19k&5%MQQ?wmS)37oOSqTGFP0`}-D;fq5WL zXO8d6QV=B0N-BhvAF@;fNka)qs_N}UmoSr(zE4R~nE8Ay>zL#?>`4QdG6Qa~idZU6uP07*qoM6N<$g5|EIEdT%j literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_23.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_23.png new file mode 100755 index 0000000000000000000000000000000000000000..639834612c846903283f6ed6628f2c79c3fd6a1e GIT binary patch literal 852 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 IM6N<$f@c1SEdT%j literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_27.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_27.png new file mode 100755 index 0000000000000000000000000000000000000000..962349bfdec5503429689825577af262921ac6c3 GIT binary patch literal 828 zcmV-C1H=4@P);MYf0)t1eLxntVNLyloX+DW?F4xn>Lx4kC|lVz2hP?>BPG^=lt&B&;Nhe z$Nydh0DT7xZunLpKw~}YuQjGxC8&`#9c5=&N6iIbu2e6O2D^vhwGJQ;rz2I`xM9`h z4OZsney!Bc=1tm9ZrFI?M~Uiv@ueNAX(#}uX4f`q5^7~o&di2&DB5-Eug~SqtX1DP z_x?f9>aIt8y%2v0r+v736~o;DEi^xOh-v}}MRKyT9$ND;fSLNc>P3gEkVkZT&0JYT zM56wYHIUNPsBVi}1ts_tc>iJmpgIjuT269gUOB)ft8$JgKlS47!h_?0g`9*kG0=KK zDuDJpm2)Cp87qz(e6XN$&Y7-CY44@1+-%XKU?6$C>!-d;eF*pXpmNUCzFW^uFOL#* zdw|Lnw*J5;@+}xLYJiw^qRKm&Htcw_)b33 zUNqEb3X6BW3)_NBrX2OtSTd4OJrK_;W?Lp)P+=G^$00KuI7fbI-c;0)xY~v9)6S5* zXSZ8bRxvLAY@zUQF?w^c$I(=<;fF$we>%ki@tEa4`EDLf|WcomHtkl$Pt=2S9cG-e) z5_8wBH44S%G)78~Q10~6%8j{+4DxU($qj3*IpoZ5j|~@^Ui)`TxV0C|wSjr>!qu-y zZkvx?IS1WaHM2&Rw-*#8yCq(l6|wakAriAN@4v~-jJ0000C6`P9 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_28.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_28.png new file mode 100755 index 0000000000000000000000000000000000000000..2eb4456cc780a288fda6b71ff3ca99b931c51b2d GIT binary patch literal 585 zcmV-P0=E5$P)>Nh!OB=PyYTx622!q|kdqdsU?O6C zY`-77ka~b3JP3L3LgIs!0Zd;Hk+vjd7p0MO?%AFTaRBBC085+0@+}?!{nZ#{lBAI| zk}kVDr6OOCFON@Iau4&u`Bx3c7{kY_)lM4#Ej_Q+6-Yh8Z=k>B*GUKAX*Ese$ICP0 z8L+!^i5ZYC-uYR90EnFzb<-`4*OguP)Ht&3V|Fb%C>5PO>Cx+2p|}NS-`J?gV=)ir zz70x62H<0TQ-CQh41ETce^co0FIr!Wq z)xD}&RR9ok+5ix`BC#9JkkfQycE+FiSLaaE>t?%f+t<`R>!|U-2%8EfD`o&RA>zGy z-TmLTSAiX{>=J&-=7C$F==OWd64&-!+h`ToJoNh)^6=0kLjn7IB#T*5XBMEG*2HBJ~;}B+3L_Rues8tiw8w|~g@?NH`D5Z=;plA# Z{Rey_OQicOEv5hf002ovPDHLkV1i=g!{7h_ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_3.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_3.png new file mode 100755 index 0000000000000000000000000000000000000000..e5772a672f9a63d511a03cd14cba48df73df3662 GIT binary patch literal 812 zcmV+{1JnG8P)Nkl)On1-IBH1q<;PWKD5!)^K#Gm-NXO<@41Ze zzwiNY{sXUWyEY)8t(lEB61#OO-ymB$!zBLg?gY45Y!*ns=rxXRW&kDlp=4^?JLamh z+pLsx{?psQ;nd2H&9$@0>|@QooFtWY03Qs1^AczMhH1c3pIA$Wnni;CTdm=A6HvBD zGSA)yb9kU=Vo_p8H@9Leot2K0zwqQFTQ5l6j6QXzX0%lrC%R+u;nrjJ>a~VMdA&GS z4(?H~2|`92GnJ8N11I`10J~7|-FPYNg|#{NK?{Ww{z7T#*F1oE#NjSICp*rq_k97d za|n?Nb}N4A-5~(?5K6>r4=ISt2ORHP%UI$1<@7syMgR_H)=m8Lea!rPr*}v=q>$rkwl*0<*&ZC zU?yImHA4m%OwQOyZvL-(F;7H0sSqT)ZQ<>F^-XwX41l(z{m9Jo9PDVVMF`%KQW{s)bzRqu$o?Gz2qm&E!1bC94yDt;WaxMT`~j@f z9R;OVIa7k-8>=Q5U;fO!(|0web&Z8)^pVL4F?q0000P43(XaExcZC)XO5g_}>Ap4BQE&%nc>iptZDLR|u?6uJVm%UC@+kLF@2I$vP@~#&R zxZV{tB$?U`%t5S1;qJ5m9kDuJM}JpjA48EIcNKk+9(EOFv?MK_o~iwzsEP7#?DW_H f$=?+J;Lm^?$5Vh#T320I00000NkvXXu0mjfxI%Y8 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_31.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_31.png new file mode 100755 index 0000000000000000000000000000000000000000..ec47b899c994843115098e2b481d4be9e3545bd9 GIT binary patch literal 270 zcmV+p0rCEcP)g#Y&IE8RMH&I=ZT-N9#$G7Z;2?CV z!MD6x1bD!iYcNdCSF^9tY4hauHh`CC?40-Z-=cvn*3{YeK2>8 ze+2U>lk@GCfAh=B#t>UH24K-BvC9r%eZAQz=j37B8kTNI77Uy6BwY4=Pwc+n50F}o UZhAxmRsaA107*qoM6N<$g4SGi=l}o! literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_32.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_32.png new file mode 100755 index 0000000000000000000000000000000000000000..3f345b563e9eeed887691ea64fd476c58e9eade1 GIT binary patch literal 236 zcmVukXtLRdY<7&Yil?6l5smS!)4=ixyV{H|i zEVZ~qQTSxJvPvwAS+^ZYlHpRc4b{SFksE~%lY85BfJbk@bKKYrf4%(Tbt~+$){Jx@>H0 z9v!U*E>kPOV&QLk{US7P`x)X2@FjRl)L3A-;}s^}z6-}5I>m6R*COBYii7&ERd`uB zg+3&W`T8Obyggs*z8c*$1-S7%TAE9JE`yu<`gW@2bNRjVnA)?Z&*lEIf=!Xm246;w!wPWp`HmW)LcC6j8v((m0$X#-)(gTGH^FsQna8H2~_{aHwIgSCq bjE?>S{e=)!nb_>V00000NkvXXu0mjfi(20( literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png new file mode 100755 index 0000000000000000000000000000000000000000..44c4d0d498b2ebfd6caf104f011430dfe5b1d362 GIT binary patch literal 771 zcmV+e1N{7nP)-)V`@Jcg?b! z8h15C(1l>d;+xnmB8bn0s0B?%Nll~j^76-I($3t^#mqxoUG9(XoO|xacaS8dU}7Ul zvH+yz0pJ4=0SEvKI~0J8h$~QMwbUk6iDv-FZCWX+W})l<4*=N!%mPs`Nk=`_H&U(RQN03dn~zOR4?0F^bcZ+3Jd|f@+2ZE!M+C?0$2hh zw{b3pwgYGh$>~DExEw%JKEP9k2~3drd00M~y~*R%-#{v~l9@dz#;0AHy!)E}^O+Rx z94I<&WVukn2$BWI?n_UP21jE!C^3x zDZI1qZ-3U!AASDMd$7RXZN&e z0fPM>xds;872w$+fB2Cr%~H`{g2wRTyVJ=QSO^(AJ6nsq`o(wFD^F{B%*=)68BWF} zXvTF8XWqhPlMHg|MfEYgan$}7+&OQ0>r%*vAZ@7aw5@LMqSO@9nv+wm?Y$}K6O~oM zi|WC5xD2E}p3qgPtJartKM85w>$fDS`}vAa$%oo~qjz(2j6=ZJSTHBHSJMXE^+B4= zU?IC%uSc~Qb0kp_Bdvd-<4w&=Yi?5=q~4jgms(nr_H|mausIPt6w!cMd<>9Q)~-r& zZE1c*ve0jw1WX8%DOgyo1(dtlNXk_={mO$H@Gr*&ZJJBPG>ZTL002ovPDHLkV1id3 BYq9_U literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_35.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_35.png new file mode 100755 index 0000000000000000000000000000000000000000..f70ecb3abe0a34f98debe1b004eb08b12c98673b GIT binary patch literal 887 zcmV--1Bm>IP)I|X#-uh;n6?-_SZ&e{l2{{$MlW29fAFB}G}ef6 z^We24CdMpP8)HLjLOaozka(ay2$(iyAm9RpvIS%ZcJ_NcEbH}`=Y5{{AKi!E42k#v zphU=jbn`Zm>HmS%9n|H~b!8xcSy~v6Rp-lqj970})rLVV1{%PFlE?{~Lcju=gp(qd z*%bni;C>+|5^&gTdAuyV+9n|%4%uZeCHnQFxLmLD%HZub4lT)5>VmBj+N=Ys2_K~^ zV1&lW)zNY+0M!g#3me@gLq(%!W}f@y-)j(p62OXcD-EhLnzo)IU-P&fxLXyWwT&xb z|HtPlrdaioIrn2=-l|Ag9$u{a^!EC)Ppq60V{voLo_a4%xm$}zC)9i+&-Yb4NaVNu zT}A86)|x5aNgZC!$fT(ViYpi2T@%Uml)-}(Hzy{iL<&;QO|N1JE$x+v{x@M(!=v#?_jqm~nttmr=t z7iljxDh0nY&TkKQ|yw=xuTui4JdN{KB-Euw&K_M zu{BbrM#JrEfIY|m{;WB-CK-FYS(nvM?GAtt1i*TU3t>YvU|1~Pc4N&XpYNDWq}(Rp z%1DKNpNATlm3g=#xh%PX6<18Wv~9zwZ|N?vGQrSH_Q|xNkpJuzI{_f%wM-vl7TrJv3XRa0I{>wWXqt=#1!scW6rfAj(5QHzka zQyVTW<<9`DcMJh@nWN{PdHpKD^JPY&Lac7bwMEwITfBfU#P$ zpzZ?at5EsKk!a|~d!n)&`)d%L`FKEdSYMII!X7zGr3=u>>pS#Q)ytacEojC;7Z+o- zf~u--d0yT_qhcE=ol>1wU2q&x&iL;z`(Ogy;s!}joq9G5+oR*Er}dKKpt^WLd)oE1 z>NPL#d7`Mhy!(hh-O5rh2nM8MqRZx9h;M!aL}7YtJLn0QcYQGMO@3zpl7-zF5M|-c ndusK)F7<&Z%d5Xbx48TZM|mrzzUzGF00000NkvXXu0mjf3B8d` literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png new file mode 100755 index 0000000000000000000000000000000000000000..7d970234f57cdecc7e45bf765ed313ad92c3161b GIT binary patch literal 890 zcmV-=1BLvFP)6U8pcMp@S4V4`@4ocyo`72k70Lo9~?a<@?V4 z7-HkNVF>`RHUVn@0J9D#kj-8!oah5e0M0sEsTMByP~%y4 zifHE_S9;&`9T#56Ak&f!I@>Q6lLt#!o~PvQmWV0$Q@q{!jiW({!Iqm>)!xxJ?AFuZ zU`dwo>wNprw)yQDKtzs+Bql2VEZ#_Xp67&V7#vKE*?+fZ!Vmso8&H_w08_NvAH?=jCdf)!o1&-%i|OKByIox zqfF}OBHD?>C(oWM%kkR4&uP=|I_j5b%BsXW8h&_k_ouieho*b(@fTn6Bc%mb16K9q z*3XMWKdF`T9i{?#J9BX{Js+F+dx0w|yy5+vx-q!{q<+4=$2V`|kdXo3F66A36uoXB} z6B%s-2h^jkvB2)S^h~NLg^1+yYf==Mu3qsHo)V~txG|rV+P@lK2N5wJ)=f>1h#6iy z<#|>LREs*&0;(mlE0f{!s>rQ;P#O`*uOvKAm2H~}_jk`hrE~}|BS|sCby*QHA9$YU zsQ3v~3!Q-4l>sFSHQSiqlS7WB^FM<_LnNJkFb*ot#_xYEA|jF(lkmKM0Xq7HHc@&K QWdHyG07*qoM6N<$g4?&KI{*Lx literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_5.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_5.png new file mode 100755 index 0000000000000000000000000000000000000000..70eab1f3e8c945560bd8aac429f4ae7e15401456 GIT binary patch literal 819 zcmV-31I+x1P) zUuYav6vlt|=B93H!FAKbeW-EU1Z_*GEfkSfldZI*f?}ae8v4g9A}AVcAQbvgb_ad1 z_+L=a2Qj|sjFwP*$U~(g*ir=9^q~+?$RMFRB|@f9lG)Ad93N&k+xu|ge4Ou`?|%0j zHPQd_kqn)qmV5r}K8jA-fW8>+@Sz(>OpOMw#lnx|~>+7u6}GfM*!KBRqQW$9C?c4A!&7q&C) zZR#>l`I#;9n;`}zfIja1F(kDxeFzxcYip8?Y`a8Eaw+*|eJ)F*YtkW27#2cQ3fD9xxO7i8G3U60x|3hHSFj?51%w@g*Fe6W3Pen(-lWthqH zIKkiFo>@#YdUcaWUK;ygeb245ZyzF3y)D)2H^w|bSsEA$o9Abri#(vmLPTOE(QB~) zSe5~l5WL-02egF&)bGsRKK>VQ(gpckWJY9Pc_ctPj$;TQ18@D9pQ6_VgMkYJ99z_qrZHz^_5-O!*)p0$SA@8>``u9&5i?P zA|=hi@vU}}3!lBz`j-#=wU1y@SuW7CZiv^m0KM@Q0KcH*jeYOjEG9ac0J!&rVWd>M zTm-QI%2S3C^+SMqw+Qi_2_+D0d{@?S)rZC17$6rG63sIoBP%l{B_LDHuT0c^g~i>i z$@@l4X0l=b;Z?7{h=@pS`7uw#(~h&(K}4h;O*1O@MIKm|5nrOJfLwFl+f(jocm#${{bV`LSb#O+VcPa002ovPDHLkV1lqyd=>xz literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png new file mode 100755 index 0000000000000000000000000000000000000000..8169e0766fd617850ef249e6919e2c7715e8cd9e GIT binary patch literal 799 zcmV+)1K|9LP)@2%14c(S3tq0sqQ7jEH5Dt=*W+!+tAtp8Hr$`!T0+9nA>H+_Nu>*%{ z!r3O^VVig%GbQn&7sQSz>BdX-(8LRNqlAUFbXQ8s>@Yj8hn=OZ-{pJr``+()pXX(q z|D6y3V*`wCc~&4mdNX^kU6}DLUYoRZlwRfq%GQCJzgfTnJ;GS40;s@jr2P7p4z2EO z`BkgUH8%sL>5Hx;%?&bmOPV#=@MWC5ermDatP_F);K8ay`L*yx0CU$U5dPW*AyM(x z^U`0%jVUtakvX+@=#1S?TU}kPT~eh=ycLt~QG(ax)Axz(Ni^|%eTCC)w+3qW$n?oI2)|Li?GX$SGl8*dSc?ud<`f8{IV6KN8%j`Qq+U;K{fgcF08uYQ&u>V>*Dtc2!ZX}?$gX!6H# zq9Cn==(qf}v5Vg(raVadCdiQojiEb=w{x+vg#B^nIU=LSh3(Hwpb#Cg{#fZvXs8rS zpx-R8%_ni*;!%$Q97uyP7Vy2+OWFi7VFxK|`I`@}h6^VQKwSwCd!Kq$AG)axK=pN@ z)ZD^x=k{?|2~f>$uM}>t@@GFFBGOowS((iZ7bT}nfaD|=o_p+uo70GhNU3w8F8_)= z)mjO_!)u>sms1C9KaF7lba$l=XVnG0*L9g&6OrikiTN+(Z)d=9R3ni9re$PLJ@C(h znY`?I@Kz&}CAt4AP%?e+AOz1kfhiA^Jk0C_dGjZ^Z2waRlkTK#qmH2p^tdhSB@q#k dszd{h^B;3hM?PLq!U_NY002ovPDHLkV1lI=dW!%6 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png new file mode 100755 index 0000000000000000000000000000000000000000..adee1a63dac8b87d57862f233107c1f961f39cac GIT binary patch literal 817 zcmV-11J3-3P)RhGNuC&1^3lbT18NI;gl(OH8CIa|J4-6 z(9S=ILoqD~ih^##tZOy%4^>3nkhO7aYed^*?YbskzI=}tNn_`GbI$pl!}Gk)3x{3& zuL1y^|G>Z{*9HW%EN2fcZERyzc8Sz=jQclD6e|J6GL)JEDR8&*NEuRfkcY2BQz>8a z!ZoKBgJrhG;k3$Z5YIixT?A2j=N`iCBxBp4$A1Hidih4IN`Gk{$) z08G7~Xn=q)d8oQ~xAB^ZOz%*CwyJlUMANgg7yseme0?g$(l$VCe`M3^GyRY*IssWhRbTRzmwo2GAv1_?vkjcY8eA*Zql6w4+_~uC6_XZ@5yYZkJ zq@DvzFOud8@ndK8K^bWM5J^EQt`{{?0^IwMq*yeJrsaW?`NRnL@qv@8)}6B8;#(@# z+?ZG~S2|Z%>$ogflvd5d*|{gpnqgXv%Hs-hUg(dpH3L^!?6F`Vhv~a3>kdP=Q7sal z+%a0YJGrx*gI|q6zmGkh+uTcF(E%j+XHF=YZ_SK!XFi^>K()=g_RFq6cF{f@p10sT zky|(I&Ck1peII>hq4v@}eWf8H!?1Pm7bD8w_(u0hj%`@qWPBZb26TCpn%6&SC_-{V4gwZyxYn-U8Fd&v>xl0)?^# zuHM(>!OCF>?!W@(WZ7Za!0EEuiE^`mY8|g zU7rO>QutJQ;uW3FbW3%h_uie0s&d|)QeA^W<+}BGZFnta8^B+T0{cf3+mI$Cg?CEQ v>5;|Tk>(}o1xc!GnLUCOXsN>Z%vb*cYgu*d%gmGW00000NkvXXu0mjfmz0(k literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png new file mode 100755 index 0000000000000000000000000000000000000000..db6e667fb77d119f4150c6a1ec875be023846c19 GIT binary patch literal 875 zcmV-x1C;!UP)~KgG zR}iEKYkd&s5>XHzgbo{2);@?U76v}7jm|c0SDM(WxlQlAUmue0ynNw%zURyLoI^kL zGTAy>AJ&1*viblBH+3K&3PcJG#WBDGup1&ZI*~%d$8{V4nu?GFD0|!@zybGp`T!?U z0Vu+FXuJxL@-`K&x$fN}fO2^v@PK`OBbv*7;lLJ~Gm(Khvu{j6SulUGk@WbdCub`` z#Z=lc0L-r_Os)Cmk-CR%xAg-4&)Qm_@CkdyHb!T9$TTis^htr($(@hgP-8ijR#wVq z%mpcm4H(sWsqG$YTgG7ffWpkif6KfeWw8MZbNlgLKhph95n^DFO6A(*_O}BS*bbHr zEidX={A}`M!Uli0op_nh zy7S_t!gp1q>P8?#z*R_CPxQ`+cX9UxkHv&I6 zXB;Pcc;Wu^DaQzc_;96iITfyx{hJFr)~AcC`;s&|vYODkUY~ObGd+?@ML(r5^Yv=ZHt8XtwHhg6(hr?2qSD`z z!Y34FRMNLOi`H#fFb6PcT4~E8X;k$v3Nx#@{{d={ku%1$CRzXh002ovPDHLkV1k;- BrV{`F literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_9.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_9.png new file mode 100755 index 0000000000000000000000000000000000000000..a6a37fe29c7f17cec2d4824961e1acfe37f0d353 GIT binary patch literal 823 zcmV-71IYY|P)de9(=SZW1EY1mdVOhDM_c6MG5vwyzZ`^%H}ee=A}$LQ95 zF=jyHg=34CF#l@Q#%R2kI}nFKWUgz1neW&VR+pM! zBwEhe<@Uy2y7&2nVtj@elmP1LJezOG3#aM=kNA}QW9}aFhB*PvWR_uB5A7lEkYhOWwzPxbk_-U#y zg(S~e8g@uwO?$n#9ByJQ zGvG#6dKXzeCxaQ8l3B+|Bi?fK&dC{e`mUQ!fzM=bPgsdwY<$iqS6e%2d0>LNqEgNQ z9~D6fzibBC`8{xD4m8{csK)>{_^E|vBliO?{{st?Q~gh3V Date: Wed, 3 Dec 2025 22:53:01 +0300 Subject: [PATCH 17/18] upd changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index edef23001..72b4ccee6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,8 @@ * OFW PR 4279: NFC FeliCa Minor Fix: FelicaPollerEventType should only be Incomplete if the tag is FeliCa Lite (by @zinongli) * OFW PR 4261: Add date/time input module (by @aaronjamt) * OFW PR 4312: Infrared: Fix infrared CLI plugin MissingImports (by @WillyJL) -* Disable halloween anim +* Dolphin: Enable winter anims +* Dolphin: Disable halloween anim

#### Known NFC post-refactor regressions list: - Mifare Mini clones reading is broken (original mini working fine) (OFW) From c08cb33a769e52d9137552a113d01fac6d0b6184 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 6 Dec 2025 22:59:48 +0300 Subject: [PATCH 18/18] move ofex out of range and fix counter editor scene fixes by @Dmitry422 --- .../scenes/subghz_scene_radio_settings.c | 2 +- .../scenes/subghz_scene_signal_settings.c | 130 ++++++++++++++---- lib/subghz/protocols/alutech_at_4n.c | 4 +- lib/subghz/protocols/came_atomo.c | 4 +- lib/subghz/protocols/faac_slh.c | 4 +- lib/subghz/protocols/hay21.c | 2 +- lib/subghz/protocols/keeloq.c | 4 +- lib/subghz/protocols/kinggates_stylo_4k.c | 4 +- lib/subghz/protocols/nice_flor_s.c | 4 +- lib/subghz/protocols/phoenix_v2.c | 6 +- lib/subghz/protocols/somfy_keytis.c | 4 +- lib/subghz/protocols/somfy_telis.c | 4 +- lib/subghz/protocols/star_line.c | 4 +- 13 files changed, 128 insertions(+), 48 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_radio_settings.c b/applications/main/subghz/scenes/subghz_scene_radio_settings.c index 93afe5e31..8e48d0c91 100644 --- a/applications/main/subghz/scenes/subghz_scene_radio_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_radio_settings.c @@ -55,7 +55,7 @@ const int32_t debug_counter_val[DEBUG_COUNTER_COUNT] = { 10, 50, 65535, - 65534, + -2147483647, 0, -1, -2, diff --git a/applications/main/subghz/scenes/subghz_scene_signal_settings.c b/applications/main/subghz/scenes/subghz_scene_signal_settings.c index 728be91b5..382289513 100644 --- a/applications/main/subghz/scenes/subghz_scene_signal_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_signal_settings.c @@ -13,6 +13,8 @@ static uint32_t counter32 = 0x0; static uint16_t counter16 = 0x0; static uint8_t byte_count = 0; static uint8_t* byte_ptr = NULL; +static uint8_t hex_char_lenght = 0; +static FuriString* byte_input_text; #define COUNTER_MODE_COUNT 7 static const char* const counter_mode_text[COUNTER_MODE_COUNT] = { @@ -50,11 +52,61 @@ static Protocols protocols[] = { #define PROTOCOLS_COUNT (sizeof(protocols) / sizeof(Protocols)); +// our special case function based on strint_to_uint32 from strint.c +StrintParseError strint_to_uint32_base16(const char* str, uint32_t* out, uint8_t* lenght) { + // skip whitespace + while(((*str >= '\t') && (*str <= '\r')) || *str == ' ') { + str++; + } + + // read digits + uint32_t limit = UINT32_MAX; + uint32_t mul_limit = limit / 16; + uint32_t result = 0; + int read_total = 0; + + while(*str != 0) { + int digit_value; + if(*str >= '0' && *str <= '9') { + digit_value = *str - '0'; + } else if(*str >= 'A' && *str <= 'Z') { + digit_value = *str - 'A' + 10; + } else if(*str >= 'a' && *str <= 'z') { + digit_value = *str - 'a' + 10; + } else { + break; + } + + if(digit_value >= 16) { + break; + } + + if(result > mul_limit) return StrintParseOverflowError; + result *= 16; + if(result > limit - digit_value) return StrintParseOverflowError; //-V658 + result += digit_value; + + read_total++; + str++; + } + + if(read_total == 0) { + result = 0; + *lenght = 0; + return StrintParseAbsentError; + } + + if(out) *out = result; + if(lenght) *lenght = read_total; + return StrintParseNoError; +} + void subghz_scene_signal_settings_counter_mode_changed(VariableItem* item) { uint8_t index = variable_item_get_current_value_index(item); variable_item_set_current_value_text(item, counter_mode_text[index]); counter_mode = counter_mode_value[index]; } + void subghz_scene_signal_settings_byte_input_callback(void* context) { SubGhz* subghz = context; view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventByteInputDone); @@ -62,11 +114,15 @@ void subghz_scene_signal_settings_byte_input_callback(void* context) { void subghz_scene_signal_settings_variable_item_list_enter_callback(void* context, uint32_t index) { SubGhz* subghz = context; + // when we click OK on "Edit counter" item if(index == 1) { + furi_string_cat_printf(byte_input_text, "%i", hex_char_lenght * 4); + furi_string_cat_str(byte_input_text, "-bit counter in HEX"); + // Setup byte_input view ByteInput* byte_input = subghz->byte_input; - byte_input_set_header_text(byte_input, "Enter counter in HEX"); + byte_input_set_header_text(byte_input, furi_string_get_cstr(byte_input_text)); byte_input_set_result_callback( byte_input, @@ -75,7 +131,6 @@ void subghz_scene_signal_settings_variable_item_list_enter_callback(void* contex subghz, byte_ptr, byte_count); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdByteInput); } } @@ -154,6 +209,8 @@ void subghz_scene_signal_settings_on_enter(void* context) { // ### Counter edit section ### FuriString* tmp_text = furi_string_alloc_set_str(""); FuriString* textCnt = furi_string_alloc_set_str(""); + byte_input_text = furi_string_alloc_set_str("Enter "); + bool counter_not_available = true; SubGhzProtocolDecoderBase* decoder = subghz_txrx_get_decoder(subghz->txrx); @@ -176,33 +233,50 @@ void subghz_scene_signal_settings_on_enter(void* context) { place = furi_string_search_str(tmp_text, "Cnt:", 0); if(place > 0) { furi_string_set_n(textCnt, tmp_text, place + 4, 8); + furi_string_trim(textCnt); FURI_LOG_D( - TAG, "Found 8 bytes string starting with Cnt:%s", furi_string_get_cstr(textCnt)); - counter_not_available = false; + TAG, + "Taked 8 bytes hex value starting after 'Cnt:' - %s", + furi_string_get_cstr(textCnt)); - // trim and convert 8 simbols string to uint32 by base 16 (hex) by strint_to_uint32(); + // trim and convert 8 simbols string to uint32 by base 16 (hex); // later we use loaded_counter in subghz_scene_signal_settings_on_event to check is there 0 or not - special case - strint_to_uint32(furi_string_get_cstr(textCnt), NULL, &loaded_counter32, 16); - // Check it there counter 2 (less than 65535) or 4 (more than 65535) hex bytes long and use corresponding variable for ByteInput - // To show hex value we must revert bytes for ByteInput view and display 2 or 4 hex bytes to edit - if(counter32 > 0xFFFF) { - byte_count = 4; - counter32 = loaded_counter32; - furi_string_printf(tmp_text, "%08lX", counter32); - FURI_LOG_D(TAG, "Byte count %i", byte_count); - FURI_LOG_D(TAG, "Counter DEC %li, HEX %lX", counter32, counter32); - counter32 = __bswap32(counter32); - byte_ptr = (uint8_t*)&counter32; + if(strint_to_uint32_base16( + furi_string_get_cstr(textCnt), &loaded_counter32, &hex_char_lenght) == + StrintParseNoError) { + counter_not_available = false; + + // calculate and roundup number of hex bytes do display counter in byte_input (every 2 hex simbols = 1 byte for view) + // later must be used in byte_input to restrict number of available byte to edit + // cnt_byte_count = (hex_char_lenght + 1) / 2; + + FURI_LOG_D( + TAG, + "Result of conversion from String to uint_32 DEC %li, HEX %lX, HEX lenght %i symbols", + loaded_counter32, + loaded_counter32, + hex_char_lenght); + + // Check is there byte_count more than 2 hex bytes long (16 bit) or not (32bit) + // To show hex value we must correct revert bytes for ByteInput view + if(hex_char_lenght > 4) { + counter32 = loaded_counter32; + furi_string_printf(tmp_text, "%lX", counter32); + counter32 = __bswap32(counter32); + byte_ptr = (uint8_t*)&counter32; + byte_count = 4; + } else { + counter16 = loaded_counter32; + furi_string_printf(tmp_text, "%X", counter16); + counter16 = __bswap16(counter16); + byte_ptr = (uint8_t*)&counter16; + byte_count = 2; + } } else { - counter16 = loaded_counter32; - byte_count = 2; - furi_string_printf(tmp_text, "%04X", counter16); - FURI_LOG_D(TAG, "Byte count %i", byte_count); - FURI_LOG_D(TAG, "Counter DEC %i, HEX %X", counter16, counter16); - counter16 = __bswap16(counter16); - byte_ptr = (uint8_t*)&counter16; - } + FURI_LOG_E(TAG, "Cant convert text counter value"); + }; + } else { FURI_LOG_D(TAG, "Counter not available for this protocol"); } @@ -248,14 +322,17 @@ bool subghz_scene_signal_settings_on_event(void* context, SceneManagerEvent even subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx)); subghz_txrx_stop(subghz->txrx); } + // at this point we must have signal Cnt:00 - // convert back after byte_view and do one send with our new mult (counter16) - at end we must have signal Cnt = counter16 + // convert back after byte_input and do one send with our new mult (counter16) - at end we must have signal Cnt = counter16 counter16 = __bswap16(counter16); + if(counter16 > 0) { furi_hal_subghz_set_rolling_counter_mult(counter16); subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx)); subghz_txrx_stop(subghz->txrx); } + // restore user definded counter increase value (mult) furi_hal_subghz_set_rolling_counter_mult(tmp_counter); break; @@ -274,11 +351,13 @@ bool subghz_scene_signal_settings_on_event(void* context, SceneManagerEvent even } counter32 = __bswap32(counter32); + if(counter32 > 0) { furi_hal_subghz_set_rolling_counter_mult(counter32); subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx)); subghz_txrx_stop(subghz->txrx); } + furi_hal_subghz_set_rolling_counter_mult(tmp_counter); break; default: @@ -333,4 +412,5 @@ void subghz_scene_signal_settings_on_exit(void* context) { variable_item_list_reset(subghz->variable_item_list); byte_input_set_result_callback(subghz->byte_input, NULL, NULL, NULL, NULL, 0); byte_input_set_header_text(subghz->byte_input, ""); + furi_string_free(byte_input_text); } diff --git a/lib/subghz/protocols/alutech_at_4n.c b/lib/subghz/protocols/alutech_at_4n.c index 5d0f7a6b8..fa64f89f3 100644 --- a/lib/subghz/protocols/alutech_at_4n.c +++ b/lib/subghz/protocols/alutech_at_4n.c @@ -277,7 +277,7 @@ static bool subghz_protocol_alutech_at_4n_gen_data( if(alutech_at4n_counter_mode == 0) { // Check for OFEX (overflow experimental) mode - if(furi_hal_subghz_get_rolling_counter_mult() != 0xFFFE) { + if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { if(instance->generic.cnt < 0xFFFF) { if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { instance->generic.cnt = 0; @@ -293,7 +293,7 @@ static bool subghz_protocol_alutech_at_4n_gen_data( if((instance->generic.cnt + 0x1) > 0xFFFF) { instance->generic.cnt = 0; } else if(instance->generic.cnt >= 0x1 && instance->generic.cnt != 0xFFFE) { - instance->generic.cnt = furi_hal_subghz_get_rolling_counter_mult(); + instance->generic.cnt = 0xFFFE; } else { instance->generic.cnt++; } diff --git a/lib/subghz/protocols/came_atomo.c b/lib/subghz/protocols/came_atomo.c index ea94e36be..e829c4dfe 100644 --- a/lib/subghz/protocols/came_atomo.c +++ b/lib/subghz/protocols/came_atomo.c @@ -189,7 +189,7 @@ static void subghz_protocol_encoder_came_atomo_get_upload( if(came_atomo_counter_mode == 0) { // Check for OFEX (overflow experimental) mode - if(furi_hal_subghz_get_rolling_counter_mult() != 0xFFFE) { + if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { if(instance->generic.cnt < 0xFFFF) { if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { instance->generic.cnt = 0; @@ -205,7 +205,7 @@ static void subghz_protocol_encoder_came_atomo_get_upload( if((instance->generic.cnt + 0x1) > 0xFFFF) { instance->generic.cnt = 0; } else if(instance->generic.cnt >= 0x1 && instance->generic.cnt != 0xFFFE) { - instance->generic.cnt = furi_hal_subghz_get_rolling_counter_mult(); + instance->generic.cnt = 0xFFFE; } else { instance->generic.cnt++; } diff --git a/lib/subghz/protocols/faac_slh.c b/lib/subghz/protocols/faac_slh.c index d44385ac7..9ddccad73 100644 --- a/lib/subghz/protocols/faac_slh.c +++ b/lib/subghz/protocols/faac_slh.c @@ -140,7 +140,7 @@ static bool subghz_protocol_faac_slh_gen_data(SubGhzProtocolEncoderFaacSLH* inst data_prg[0] = 0x00; if(allow_zero_seed || (instance->generic.seed != 0x0)) { - if(!(furi_hal_subghz_get_rolling_counter_mult() >= 0xFFFE)) { + if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { if(instance->generic.cnt < 0xFFFFF) { if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFFF) { @@ -158,7 +158,7 @@ static bool subghz_protocol_faac_slh_gen_data(SubGhzProtocolEncoderFaacSLH* inst } if(temp_counter_backup != 0x0) { - if(!(furi_hal_subghz_get_rolling_counter_mult() >= 0xFFFE)) { + if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { if(temp_counter_backup < 0xFFFFF) { if((temp_counter_backup + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFFF) { diff --git a/lib/subghz/protocols/hay21.c b/lib/subghz/protocols/hay21.c index 0d2c1059e..b6640e570 100644 --- a/lib/subghz/protocols/hay21.c +++ b/lib/subghz/protocols/hay21.c @@ -146,7 +146,7 @@ static void subghz_protocol_encoder_hay21_get_upload(SubGhzProtocolEncoderHay21* // Counter increment // Check for OFEX (overflow experimental) mode - if(furi_hal_subghz_get_rolling_counter_mult() != 0xFFFE) { + if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { if(instance->generic.cnt < 0xF) { if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xF) { instance->generic.cnt = 0; diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index c90cfcf0e..70ccc2f82 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -186,7 +186,7 @@ static bool subghz_protocol_keeloq_gen_data( if(keeloq_counter_mode == 0) { // Check for OFEX (overflow experimental) mode - if(furi_hal_subghz_get_rolling_counter_mult() != 0xFFFE) { + if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { // If counter is 0xFFFF we will reset it to 0 if(instance->generic.cnt < 0xFFFF) { // Increase counter with value set in global settings (mult) @@ -205,7 +205,7 @@ static bool subghz_protocol_keeloq_gen_data( if((instance->generic.cnt + 0x1) > 0xFFFF) { instance->generic.cnt = 0; } else if(instance->generic.cnt >= 0x1 && instance->generic.cnt != 0xFFFE) { - instance->generic.cnt = furi_hal_subghz_get_rolling_counter_mult(); + instance->generic.cnt = 0xFFFE; } else { instance->generic.cnt++; } diff --git a/lib/subghz/protocols/kinggates_stylo_4k.c b/lib/subghz/protocols/kinggates_stylo_4k.c index eccff1950..83dbd5298 100644 --- a/lib/subghz/protocols/kinggates_stylo_4k.c +++ b/lib/subghz/protocols/kinggates_stylo_4k.c @@ -156,7 +156,7 @@ static bool subghz_protocol_kinggates_stylo_4k_gen_data( instance->generic.cnt = decrypt & 0xFFFF; // Check for OFEX (overflow experimental) mode - if(furi_hal_subghz_get_rolling_counter_mult() != 0xFFFE) { + if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { if(instance->generic.cnt < 0xFFFF) { if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { instance->generic.cnt = 0; @@ -172,7 +172,7 @@ static bool subghz_protocol_kinggates_stylo_4k_gen_data( if((instance->generic.cnt + 0x1) > 0xFFFF) { instance->generic.cnt = 0; } else if(instance->generic.cnt >= 0x1 && instance->generic.cnt != 0xFFFE) { - instance->generic.cnt = furi_hal_subghz_get_rolling_counter_mult(); + instance->generic.cnt = 0xFFFE; } else { instance->generic.cnt++; } diff --git a/lib/subghz/protocols/nice_flor_s.c b/lib/subghz/protocols/nice_flor_s.c index f3e1e33bb..92ca20838 100644 --- a/lib/subghz/protocols/nice_flor_s.c +++ b/lib/subghz/protocols/nice_flor_s.c @@ -154,7 +154,7 @@ static void subghz_protocol_encoder_nice_flor_s_get_upload( } if(nice_flors_counter_mode == 0) { // Check for OFEX (overflow experimental) mode - if(furi_hal_subghz_get_rolling_counter_mult() != 0xFFFE) { + if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { if(instance->generic.cnt < 0xFFFF) { if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { instance->generic.cnt = 0; @@ -170,7 +170,7 @@ static void subghz_protocol_encoder_nice_flor_s_get_upload( if((instance->generic.cnt + 0x1) > 0xFFFF) { instance->generic.cnt = 0; } else if(instance->generic.cnt >= 0x1 && instance->generic.cnt != 0xFFFE) { - instance->generic.cnt = furi_hal_subghz_get_rolling_counter_mult(); + instance->generic.cnt = 0xFFFE; } else { instance->generic.cnt++; } diff --git a/lib/subghz/protocols/phoenix_v2.c b/lib/subghz/protocols/phoenix_v2.c index afc2cbb82..6ee6e97d2 100644 --- a/lib/subghz/protocols/phoenix_v2.c +++ b/lib/subghz/protocols/phoenix_v2.c @@ -253,7 +253,7 @@ static bool // Reconstruction of the data // Check for OFEX (overflow experimental) mode - if(furi_hal_subghz_get_rolling_counter_mult() != 0xFFFE) { + if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { if(instance->generic.cnt < 0xFFFF) { if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { instance->generic.cnt = 0; @@ -269,7 +269,7 @@ static bool if((instance->generic.cnt + 0x1) > 0xFFFF) { instance->generic.cnt = 0; } else if(instance->generic.cnt >= 0x1 && instance->generic.cnt != 0xFFFE) { - instance->generic.cnt = furi_hal_subghz_get_rolling_counter_mult(); + instance->generic.cnt = 0xFFFE; } else { instance->generic.cnt++; } @@ -599,7 +599,7 @@ void subghz_protocol_decoder_phoenix_v2_get_string(void* context, FuriString* ou "V2 Phoenix %dbit\r\n" "Key:%05lX%08lX\r\n" "Sn:0x%07lX \r\n" - "Cnt:0x%04lX\r\n" + "Cnt:%04lX\r\n" "Btn:%X\r\n", instance->generic.data_count_bit, (uint32_t)(instance->generic.data >> 32) & 0xFFFFFFFF, diff --git a/lib/subghz/protocols/somfy_keytis.c b/lib/subghz/protocols/somfy_keytis.c index 1225f5d32..7c00a4b71 100644 --- a/lib/subghz/protocols/somfy_keytis.c +++ b/lib/subghz/protocols/somfy_keytis.c @@ -131,7 +131,7 @@ static bool instance->generic.serial = data & 0xFFFFFF; // Check for OFEX (overflow experimental) mode - if(furi_hal_subghz_get_rolling_counter_mult() != 0xFFFE) { + if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { if(instance->generic.cnt < 0xFFFF) { if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { instance->generic.cnt = 0; @@ -147,7 +147,7 @@ static bool if((instance->generic.cnt + 0x1) > 0xFFFF) { instance->generic.cnt = 0; } else if(instance->generic.cnt >= 0x1 && instance->generic.cnt != 0xFFFE) { - instance->generic.cnt = furi_hal_subghz_get_rolling_counter_mult(); + instance->generic.cnt = 0xFFFE; } else { instance->generic.cnt++; } diff --git a/lib/subghz/protocols/somfy_telis.c b/lib/subghz/protocols/somfy_telis.c index 2f9fd1f10..cbf110053 100644 --- a/lib/subghz/protocols/somfy_telis.c +++ b/lib/subghz/protocols/somfy_telis.c @@ -125,7 +125,7 @@ static bool subghz_protocol_somfy_telis_gen_data( btn = subghz_protocol_somfy_telis_get_btn_code(); // Check for OFEX (overflow experimental) mode - if(furi_hal_subghz_get_rolling_counter_mult() != 0xFFFE) { + if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { if(instance->generic.cnt < 0xFFFF) { if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { instance->generic.cnt = 0; @@ -141,7 +141,7 @@ static bool subghz_protocol_somfy_telis_gen_data( if((instance->generic.cnt + 0x1) > 0xFFFF) { instance->generic.cnt = 0; } else if(instance->generic.cnt >= 0x1 && instance->generic.cnt != 0xFFFE) { - instance->generic.cnt = furi_hal_subghz_get_rolling_counter_mult(); + instance->generic.cnt = 0xFFFE; } else { instance->generic.cnt++; } diff --git a/lib/subghz/protocols/star_line.c b/lib/subghz/protocols/star_line.c index d6706704c..56465481b 100644 --- a/lib/subghz/protocols/star_line.c +++ b/lib/subghz/protocols/star_line.c @@ -130,7 +130,7 @@ void subghz_protocol_encoder_star_line_free(void* context) { static bool subghz_protocol_star_line_gen_data(SubGhzProtocolEncoderStarLine* instance, uint8_t btn) { // Check for OFEX (overflow experimental) mode - if(furi_hal_subghz_get_rolling_counter_mult() != 0xFFFE) { + if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { if(instance->generic.cnt < 0xFFFF) { if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { instance->generic.cnt = 0; @@ -146,7 +146,7 @@ static bool if((instance->generic.cnt + 0x1) > 0xFFFF) { instance->generic.cnt = 0; } else if(instance->generic.cnt >= 0x1 && instance->generic.cnt != 0xFFFE) { - instance->generic.cnt = furi_hal_subghz_get_rolling_counter_mult(); + instance->generic.cnt = 0xFFFE; } else { instance->generic.cnt++; }