From 3f34537b5b244ea7935283e04a8d413f985331b1 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 28 Oct 2024 11:08:10 -0400 Subject: [PATCH 001/192] Add dictionary asset --- .../main/nfc/resources/nfc/assets/mf_ul_c_dict.nfc | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 applications/main/nfc/resources/nfc/assets/mf_ul_c_dict.nfc diff --git a/applications/main/nfc/resources/nfc/assets/mf_ul_c_dict.nfc b/applications/main/nfc/resources/nfc/assets/mf_ul_c_dict.nfc new file mode 100644 index 000000000..6d537e36e --- /dev/null +++ b/applications/main/nfc/resources/nfc/assets/mf_ul_c_dict.nfc @@ -0,0 +1,10 @@ +# Hexadecimal-Reversed Sample Key +12E4143455F495649454D4B414542524 +# Byte-Reversed Sample Key (!NACUOYFIEMKAERB) +214E4143554F594649454D4B41455242 +# Sample Key (BREAKMEIFYOUCAN!) +425245414B4D454946594F5543414E21 +# Semnox Key (IEMKAERB!NACUOY ) +49454D4B41455242214E4143554F5900 +# Modified Semnox Key (IEMKAERB!NACUOYF) +49454D4B41455242214E4143554F5946 \ No newline at end of file From 60e0a697fa9dadf5f1587bd20cd2252cfe572522 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 28 Oct 2024 19:56:28 -0400 Subject: [PATCH 002/192] Initial structure for UL-C dictionary attacks --- .../mf_ultralight/mf_ultralight.c | 21 +- applications/main/nfc/nfc_app_i.h | 14 ++ ...ul_c_dict.nfc => mf_ultralight_c_dict.nfc} | 0 .../main/nfc/scenes/nfc_scene_config.h | 1 + .../main/nfc/scenes/nfc_scene_extra_actions.c | 10 + .../nfc_scene_mf_ultralight_c_dict_attack.c | 202 ++++++++++++++++++ .../mf_ultralight/mf_ultralight_poller.c | 64 +++++- .../mf_ultralight/mf_ultralight_poller.h | 21 +- .../mf_ultralight/mf_ultralight_poller_i.h | 8 + 9 files changed, 330 insertions(+), 11 deletions(-) rename applications/main/nfc/resources/nfc/assets/{mf_ul_c_dict.nfc => mf_ultralight_c_dict.nfc} (100%) create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index 3adf2a1f5..6edbc06a2 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -166,15 +166,26 @@ static void nfc_scene_read_on_enter_mf_ultralight(NfcApp* instance) { bool nfc_scene_read_on_event_mf_ultralight(NfcApp* instance, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventCardDetected) { - nfc_unlock_helper_card_detected_handler(instance); - } else if(event.event == NfcCustomEventPollerIncomplete) { - notification_message(instance->notifications, &sequence_semi_success); + if(event.event == NfcCustomEventPollerSuccess) { + notification_message(instance->notifications, &sequence_success); scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); dolphin_deed(DolphinDeedNfcReadSuccess); + return true; + } else if(event.event == NfcCustomEventPollerIncomplete) { + const MfUltralightData* data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight); + if(data->type == MfUltralightTypeMfulC) { + // Start dict attack for MFUL C cards + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCDictAttack); + } else { + notification_message(instance->notifications, &sequence_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + } + return true; } } - return true; + return false; } static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instance) { diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 14e484622..e13af79aa 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -47,6 +47,7 @@ #include #include #include +#include #include #include @@ -80,6 +81,10 @@ #define NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict.nfc") #define NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH \ (NFC_APP_FOLDER "/assets/mf_classic_dict_nested.nfc") +#define NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH \ + (NFC_APP_FOLDER "/assets/mf_ultralight_c_dict_user.nfc") +#define NFC_APP_MF_ULTRALIGHT_C_DICT_SYSTEM_PATH \ + (NFC_APP_FOLDER "/assets/mf_ultralight_c_dict.nfc") typedef enum { NfcRpcStateIdle, @@ -105,6 +110,14 @@ typedef struct { bool enhanced_dict; } NfcMfClassicDictAttackContext; +typedef struct { + KeysDict* dict; + bool auth_success; + bool is_card_present; + size_t dict_keys_total; + size_t dict_keys_current; +} NfcMfUltralightCDictContext; + struct NfcApp { DialogsApp* dialogs; Storage* storage; @@ -143,6 +156,7 @@ struct NfcApp { MfUltralightAuth* mf_ul_auth; SlixUnlock* slix_unlock; NfcMfClassicDictAttackContext nfc_dict_context; + NfcMfUltralightCDictContext mf_ultralight_c_dict_context; Mfkey32Logger* mfkey32_logger; MfUserDict* mf_user_dict; MfClassicKeyCache* mfc_key_cache; diff --git a/applications/main/nfc/resources/nfc/assets/mf_ul_c_dict.nfc b/applications/main/nfc/resources/nfc/assets/mf_ultralight_c_dict.nfc similarity index 100% rename from applications/main/nfc/resources/nfc/assets/mf_ul_c_dict.nfc rename to applications/main/nfc/resources/nfc/assets/mf_ultralight_c_dict.nfc diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index 83c8ffeed..1b9d4367d 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -25,6 +25,7 @@ ADD_SCENE(nfc, retry_confirm, RetryConfirm) ADD_SCENE(nfc, exit_confirm, ExitConfirm) ADD_SCENE(nfc, save_confirm, SaveConfirm) +ADD_SCENE(nfc, mf_ultralight_c_dict_attack, MfUltralightCDictAttack) ADD_SCENE(nfc, mf_ultralight_write, MfUltralightWrite) ADD_SCENE(nfc, mf_ultralight_write_success, MfUltralightWriteSuccess) ADD_SCENE(nfc, mf_ultralight_write_fail, MfUltralightWriteFail) diff --git a/applications/main/nfc/scenes/nfc_scene_extra_actions.c b/applications/main/nfc/scenes/nfc_scene_extra_actions.c index 2943c0c55..5753d6449 100644 --- a/applications/main/nfc/scenes/nfc_scene_extra_actions.c +++ b/applications/main/nfc/scenes/nfc_scene_extra_actions.c @@ -3,6 +3,7 @@ enum SubmenuIndex { SubmenuIndexReadCardType, SubmenuIndexMfClassicKeys, + SubmenuIndexMfUlCKeys, SubmenuIndexMfUltralightUnlock, SubmenuIndexSlixUnlock, }; @@ -29,6 +30,12 @@ void nfc_scene_extra_actions_on_enter(void* context) { SubmenuIndexMfClassicKeys, nfc_scene_extra_actions_submenu_callback, instance); + submenu_add_item( + submenu, + "MIFARE Ultralight C Keys", + SubmenuIndexMfUlCKeys, + nfc_scene_extra_actions_submenu_callback, + instance); submenu_add_item( submenu, "Unlock NTAG/Ultralight", @@ -54,6 +61,9 @@ bool nfc_scene_extra_actions_on_event(void* context, SceneManagerEvent event) { if(event.event == SubmenuIndexMfClassicKeys) { scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicKeys); consumed = true; + } else if(event.event == SubmenuIndexMfUlCKeys) { + // TODO: Add MFUL C key management scene here + consumed = true; } else if(event.event == SubmenuIndexMfUltralightUnlock) { mf_ultralight_auth_reset(instance->mf_ul_auth); scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c new file mode 100644 index 000000000..d1b2c0978 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c @@ -0,0 +1,202 @@ +#include "../nfc_app_i.h" +#include + +#define TAG "NfcMfUlCDictAttack" + +enum { + DictAttackStateUserDictInProgress, + DictAttackStateSystemDictInProgress, +}; + +NfcCommand nfc_mf_ultralight_c_dict_attack_worker_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.protocol == NfcProtocolMfUltralight); + NfcCommand command = NfcCommandContinue; + NfcApp* instance = context; + MfUltralightPollerEvent* poller_event = event.event_data; + + if(poller_event->type == MfUltralightPollerEventTypeRequestMode) { + poller_event->data->poller_mode = MfUltralightPollerModeDictAttack; + command = NfcCommandContinue; + } else if(poller_event->type == MfUltralightPollerEventTypeRequestKey) { + MfUltralightC3DesAuthKey key = {}; + if(keys_dict_get_next_key( + instance->mf_ultralight_c_dict_context.dict, + key.data, + sizeof(MfUltralightC3DesAuthKey))) { + poller_event->data->key_request_data.key = key; + poller_event->data->key_request_data.key_provided = true; + instance->mf_ultralight_c_dict_context.dict_keys_current++; + + if(instance->mf_ultralight_c_dict_context.dict_keys_current % 10 == 0) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); + } + } else { + poller_event->data->key_request_data.key_provided = false; + } + } else if(poller_event->type == MfUltralightPollerEventTypeReadSuccess) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventDictAttackComplete); + command = NfcCommandStop; + } + return command; +} + +void nfc_scene_mf_ultralight_c_dict_attack_dict_attack_result_callback( + DictAttackEvent event, + void* context) { + furi_assert(context); + NfcApp* instance = context; + if(event == DictAttackEventSkipPressed) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventDictAttackSkip); + } +} + +void nfc_scene_mf_ultralight_c_dict_attack_prepare_view(NfcApp* instance) { + uint32_t state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightCDictAttack); + if(state == DictAttackStateUserDictInProgress) { + do { + if(!keys_dict_check_presence(NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH)) { + state = DictAttackStateSystemDictInProgress; + break; + } + instance->mf_ultralight_c_dict_context.dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + if(keys_dict_get_total_keys(instance->mf_ultralight_c_dict_context.dict) == 0) { + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + state = DictAttackStateSystemDictInProgress; + break; + } + dict_attack_set_header(instance->dict_attack, "MFUL C User Dictionary"); + } while(false); + } + if(state == DictAttackStateSystemDictInProgress) { + instance->mf_ultralight_c_dict_context.dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_SYSTEM_PATH, + KeysDictModeOpenExisting, + sizeof(MfUltralightC3DesAuthKey)); + dict_attack_set_header(instance->dict_attack, "MFUL C System Dictionary"); + } + + instance->mf_ultralight_c_dict_context.dict_keys_total = + keys_dict_get_total_keys(instance->mf_ultralight_c_dict_context.dict); + dict_attack_set_total_dict_keys( + instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_total); + instance->mf_ultralight_c_dict_context.dict_keys_current = 0; + dict_attack_set_callback( + instance->dict_attack, + nfc_scene_mf_ultralight_c_dict_attack_dict_attack_result_callback, + instance); + scene_manager_set_scene_state(instance->scene_manager, NfcSceneMfUltralightCDictAttack, state); +} + +void nfc_scene_mf_ultralight_c_dict_attack_on_enter(void* context) { + NfcApp* instance = context; + + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfUltralightCDictAttack, + DictAttackStateUserDictInProgress); + nfc_scene_mf_ultralight_c_dict_attack_prepare_view(instance); + + // Setup and start worker + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); + nfc_poller_start(instance->poller, nfc_mf_ultralight_c_dict_attack_worker_callback, instance); + + nfc_blink_read_start(instance); +} + +void nfc_scene_mf_ul_c_dict_attack_update_view(NfcApp* instance) { + dict_attack_set_card_state( + instance->dict_attack, instance->mf_ultralight_c_dict_context.is_card_present); + dict_attack_set_current_dict_key( + instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_current); +} + +bool nfc_scene_mf_ultralight_c_dict_attack_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + uint32_t state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightCDictAttack); + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventDictAttackComplete) { + if(state == DictAttackStateUserDictInProgress) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfUltralightCDictAttack, + DictAttackStateSystemDictInProgress); + nfc_scene_mf_ultralight_c_dict_attack_prepare_view(instance); + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); + nfc_poller_start( + instance->poller, nfc_mf_ultralight_c_dict_attack_worker_callback, instance); + consumed = true; + } else { + notification_message(instance->notifications, &sequence_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + consumed = true; + } + } else if(event.event == NfcCustomEventDictAttackDataUpdate) { + dict_attack_set_current_dict_key( + instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_current); + consumed = true; + } else if(event.event == NfcCustomEventDictAttackSkip) { + if(instance->mf_ultralight_c_dict_context.is_card_present) { + if(state == DictAttackStateUserDictInProgress) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfUltralightCDictAttack, + DictAttackStateSystemDictInProgress); + nfc_scene_mf_ultralight_c_dict_attack_prepare_view(instance); + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); + nfc_poller_start( + instance->poller, + nfc_mf_ultralight_c_dict_attack_worker_callback, + instance); + } else { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + notification_message(instance->notifications, &sequence_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + } + consumed = true; + } + } + } else if(event.type == SceneManagerEventTypeBack) { + scene_manager_next_scene(instance->scene_manager, NfcSceneExitConfirm); + consumed = true; + } + return consumed; +} + +void nfc_scene_mf_ultralight_c_dict_attack_on_exit(void* context) { + NfcApp* instance = context; + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfUltralightCDictAttack, + DictAttackStateUserDictInProgress); + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + instance->mf_ultralight_c_dict_context.dict_keys_total = 0; + instance->mf_ultralight_c_dict_context.dict_keys_current = 0; + instance->mf_ultralight_c_dict_context.auth_success = false; + instance->mf_ultralight_c_dict_context.is_card_present = false; + nfc_blink_stop(instance); +} diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index 0e5ff0011..08136041c 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -181,6 +181,10 @@ MfUltralightPoller* mf_ultralight_poller_alloc(Iso14443_3aPoller* iso14443_3a_po instance->general_event.protocol = NfcProtocolMfUltralight; instance->general_event.event_data = &instance->mfu_event; instance->general_event.instance = instance; + + instance->dict_attack_ctx.auth_success = false; + instance->dict_attack_ctx.is_card_present = false; + mbedtls_des3_init(&instance->des_context); return instance; } @@ -521,7 +525,7 @@ static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* in } else { if(instance->data->type == MfUltralightTypeMfulC && !mf_ultralight_3des_key_valid(instance->data)) { - instance->state = MfUltralightPollerStateCheckMfulCAuthStatus; + instance->state = MfUltralightPollerStateDictAttack; } else { FURI_LOG_D(TAG, "Read page %d failed", instance->pages_read); if(instance->pages_read) { @@ -730,6 +734,63 @@ static NfcCommand mf_ultralight_poller_handler_write_success(MfUltralightPoller* return command; } +static NfcCommand mf_ultralight_poller_handler_dict_attack(MfUltralightPoller* instance) { + NfcCommand command = NfcCommandContinue; + const MfUltralightData* tag_data = instance->data; + uint32_t features = mf_ultralight_get_feature_support_set(tag_data->type); + FURI_LOG_D(TAG, "Dict Attack"); + do { + if(!mf_ultralight_support_feature(features, MfUltralightFeatureSupportAuthenticate)) { + instance->error = MfUltralightErrorProtocol; + instance->state = MfUltralightPollerStateReadFailed; + break; + } + + instance->dict_attack_ctx.is_card_present = true; + instance->mfu_event.type = MfUltralightPollerEventTypeRequestKey; + command = instance->callback(instance->general_event, instance->context); + if(!instance->mfu_event.data->key_request_data.key_provided) { + instance->state = MfUltralightPollerStateReadSuccess; + break; + } + + FURI_LOG_D(TAG, "Trying next 3DES key"); + instance->error = MfUltralightErrorNone; + instance->auth_context.auth_success = false; + instance->auth_context.tdes_key = instance->mfu_event.data->key_request_data.key; + do { + uint8_t output[MF_ULTRALIGHT_C_AUTH_DATA_SIZE]; + uint8_t RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0}; + furi_hal_random_fill_buf(RndA, sizeof(RndA)); + instance->error = mf_ultralight_poller_authenticate_start(instance, RndA, output); + if(instance->error != MfUltralightErrorNone) break; + + uint8_t decoded_shifted_RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0}; + const uint8_t* RndB = output + MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET; + instance->error = mf_ultralight_poller_authenticate_end( + instance, RndB, output, decoded_shifted_RndA); + if(instance->error != MfUltralightErrorNone) break; + + mf_ultralight_3des_shift_data(RndA); + instance->auth_context.auth_success = + (memcmp(RndA, decoded_shifted_RndA, sizeof(decoded_shifted_RndA)) == 0); + if(instance->auth_context.auth_success) { + FURI_LOG_D(TAG, "Dict attack success"); + instance->state = MfUltralightPollerStateReadPages; + instance->dict_attack_ctx.auth_success = true; + break; + } + } while(false); + + if(!instance->auth_context.auth_success) { + FURI_LOG_D(TAG, "Dict attack auth failed"); + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + } + } while(false); + + return command; +} + static const MfUltralightPollerReadHandler mf_ultralight_poller_read_handler[MfUltralightPollerStateNum] = { [MfUltralightPollerStateIdle] = mf_ultralight_poller_handler_idle, @@ -755,6 +816,7 @@ static const MfUltralightPollerReadHandler [MfUltralightPollerStateWritePages] = mf_ultralight_poller_handler_write_pages, [MfUltralightPollerStateWriteFail] = mf_ultralight_poller_handler_write_fail, [MfUltralightPollerStateWriteSuccess] = mf_ultralight_poller_handler_write_success, + [MfUltralightPollerStateDictAttack] = mf_ultralight_poller_handler_dict_attack, }; diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h index e50017324..2552abeb5 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h @@ -27,6 +27,7 @@ typedef enum { MfUltralightPollerEventTypeCardLocked, /**< Presented card is locked by password, AUTH0 or lock bytes. */ MfUltralightPollerEventTypeWriteSuccess, /**< Poller wrote card successfully. */ MfUltralightPollerEventTypeWriteFail, /**< Poller failed to write card. */ + MfUltralightPollerEventTypeRequestKey, /**< Poller requests key for dict attack. */ } MfUltralightPollerEventType; /** @@ -35,6 +36,7 @@ typedef enum { typedef enum { MfUltralightPollerModeRead, /**< Poller will only read card. It's a default mode. */ MfUltralightPollerModeWrite, /**< Poller will write already saved card to another presented card. */ + MfUltralightPollerModeDictAttack, /**< Poller will perform dictionary attack against card. */ } MfUltralightPollerMode; /** @@ -42,20 +44,29 @@ typedef enum { */ typedef struct { MfUltralightAuthPassword password; /**< Password to be used for authentication. */ - MfUltralightC3DesAuthKey tdes_key; - MfUltralightAuthPack pack; /**< Pack received on successfull authentication. */ + MfUltralightC3DesAuthKey tdes_key; /**< 3DES key to be used for authentication. */ + MfUltralightAuthPack pack; /**< Pack received on successful authentication. */ bool auth_success; /**< Set to true if authentication succeeded, false otherwise. */ bool skip_auth; /**< Set to true if authentication should be skipped, false otherwise. */ } MfUltralightPollerAuthContext; +/** + * @brief MfUltralight poller key request data. + */ +typedef struct { + MfUltralightC3DesAuthKey key; /**< Key to try. */ + bool key_provided; /**< Set to true if key was provided, false to stop attack. */ +} MfUltralightPollerKeyRequestData; + /** * @brief MfUltralight poller event data. */ typedef union { MfUltralightPollerAuthContext auth_context; /**< Authentication context. */ MfUltralightError error; /**< Error code indicating reading fail reason. */ - const MfUltralightData* write_data; - MfUltralightPollerMode poller_mode; + const MfUltralightData* write_data; /**< Data to be written to card. */ + MfUltralightPollerMode poller_mode; /**< Mode to operate in. */ + MfUltralightPollerKeyRequestData key_request_data; /**< Key request data. */ } MfUltralightPollerEventData; /** @@ -64,7 +75,7 @@ typedef union { * Upon emission of an event, an instance of this struct will be passed to the callback. */ typedef struct { - MfUltralightPollerEventType type; /**< Type of emmitted event. */ + MfUltralightPollerEventType type; /**< Type of emitted event. */ MfUltralightPollerEventData* data; /**< Pointer to event specific data. */ } MfUltralightPollerEvent; diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h index b35c49aea..bbd9c6c3e 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h @@ -68,10 +68,17 @@ typedef enum { MfUltralightPollerStateWritePages, MfUltralightPollerStateWriteFail, MfUltralightPollerStateWriteSuccess, + MfUltralightPollerStateDictAttack, MfUltralightPollerStateNum, } MfUltralightPollerState; +typedef struct { + uint8_t sectors_total; + bool auth_success; + bool is_card_present; +} MfUltralightPollerDictAttackContext; + struct MfUltralightPoller { Iso14443_3aPoller* iso14443_3a_poller; MfUltralightPollerState state; @@ -89,6 +96,7 @@ struct MfUltralightPoller { uint8_t tearing_flag_total; uint16_t current_page; MfUltralightError error; + MfUltralightPollerDictAttackContext dict_attack_ctx; mbedtls_des3_context des_context; NfcGenericEvent general_event; From 147a81c38e65faae526f1dc8270ec59fe0facc42 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 28 Oct 2024 22:28:13 -0400 Subject: [PATCH 003/192] Document Ultralight C dictionary file format --- documentation/file_formats/NfcFileFormats.md | 26 +++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/documentation/file_formats/NfcFileFormats.md b/documentation/file_formats/NfcFileFormats.md index da0b0a19d..d89483390 100644 --- a/documentation/file_formats/NfcFileFormats.md +++ b/documentation/file_formats/NfcFileFormats.md @@ -36,7 +36,7 @@ Version differences: ATQA: 00 44 SAK: 00 -### Description +### Description This file format is used to store the UID, SAK and ATQA of an ISO14443-3A device. UID must be either 4 or 7 bytes long. ATQA is 2 bytes long. SAK is 1 byte long. @@ -56,7 +56,7 @@ None, there are no versions yet. Application data: 00 12 34 FF Protocol info: 11 81 E1 -### Description +### Description This file format is used to store the UID, Application data and Protocol info of a ISO14443-3B device. UID must be 4 bytes long. Application data is 4 bytes long. Protocol info is 3 bytes long. @@ -80,7 +80,7 @@ None, there are no versions yet. # ISO14443-4A specific data ATS: 06 75 77 81 02 80 -### Description +### Description This file format is used to store the UID, SAK and ATQA of a ISO14443-4A device. It also stores the Answer to Select (ATS) data of the card. ATS must be no less than 5 bytes long. @@ -303,6 +303,26 @@ None, there are no versions yet. This file contains a list of Mifare Classic keys. Each key is represented as a hex string. Lines starting with '#' are ignored as comments. Blank lines are ignored as well. +## Mifare Ultralight C Dictionary + +### Example + + # Hexadecimal-Reversed Sample Key + 12E4143455F495649454D4B414542524 + # Byte-Reversed Sample Key (!NACUOYFIEMKAERB) + 214E4143554F594649454D4B41455242 + # Sample Key (BREAKMEIFYOUCAN!) + 425245414B4D454946594F5543414E21 + # Semnox Key (IEMKAERB!NACUOY ) + 49454D4B41455242214E4143554F5900 + # Modified Semnox Key (IEMKAERB!NACUOYF) + 49454D4B41455242214E4143554F5946 + ... + +### Description + +This file contains a list of Mifare Ultralight C keys. Each key is represented as a hex string. Lines starting with '#' are ignored as comments. Blank lines are ignored as well. + ## EMV resources ### Example From c3b8c54aa0fb852994b2bf05c0107b55abcd07f6 Mon Sep 17 00:00:00 2001 From: noproto Date: Sun, 10 Nov 2024 16:19:44 -0500 Subject: [PATCH 004/192] Support adding and removing new Ultralight C keys --- .../main/nfc/scenes/nfc_scene_config.h | 6 + .../nfc/scenes/nfc_scene_delete_success.c | 4 + .../main/nfc/scenes/nfc_scene_extra_actions.c | 8 +- .../nfc/scenes/nfc_scene_mf_classic_keys.c | 2 - .../scenes/nfc_scene_mf_classic_keys_add.c | 2 +- .../scenes/nfc_scene_mf_ultralight_c_keys.c | 96 ++++++++++++++++ .../nfc_scene_mf_ultralight_c_keys_add.c | 63 ++++++++++ .../nfc_scene_mf_ultralight_c_keys_delete.c | 108 ++++++++++++++++++ .../nfc_scene_mf_ultralight_c_keys_list.c | 66 +++++++++++ ...cene_mf_ultralight_c_keys_warn_duplicate.c | 49 ++++++++ .../main/nfc/scenes/nfc_scene_save_success.c | 4 + .../services/dolphin/helpers/dolphin_deed.c | 2 +- .../services/dolphin/helpers/dolphin_deed.h | 2 +- applications/system/mfkey/mfkey.c | 2 +- lib/toolbox/keys_dict.c | 19 +-- 15 files changed, 409 insertions(+), 24 deletions(-) create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_delete.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_list.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_warn_duplicate.c diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index 1b9d4367d..399d59b92 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -58,6 +58,12 @@ ADD_SCENE(nfc, mf_classic_keys_delete, MfClassicKeysDelete) ADD_SCENE(nfc, mf_classic_keys_add, MfClassicKeysAdd) ADD_SCENE(nfc, mf_classic_keys_warn_duplicate, MfClassicKeysWarnDuplicate) +ADD_SCENE(nfc, mf_ultralight_c_keys, MfUltralightCKeys) +ADD_SCENE(nfc, mf_ultralight_c_keys_list, MfUltralightCKeysList) +ADD_SCENE(nfc, mf_ultralight_c_keys_delete, MfUltralightCKeysDelete) +ADD_SCENE(nfc, mf_ultralight_c_keys_add, MfUltralightCKeysAdd) +ADD_SCENE(nfc, mf_ultralight_c_keys_warn_duplicate, MfUltralightCKeysWarnDuplicate) + ADD_SCENE(nfc, set_type, SetType) ADD_SCENE(nfc, set_sak, SetSak) ADD_SCENE(nfc, set_atqa, SetAtqa) diff --git a/applications/main/nfc/scenes/nfc_scene_delete_success.c b/applications/main/nfc/scenes/nfc_scene_delete_success.c index d41e52549..d8308addd 100644 --- a/applications/main/nfc/scenes/nfc_scene_delete_success.c +++ b/applications/main/nfc/scenes/nfc_scene_delete_success.c @@ -28,6 +28,10 @@ bool nfc_scene_delete_success_on_event(void* context, SceneManagerEvent event) { if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicKeys)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneMfClassicKeys); + } else if(scene_manager_has_previous_scene( + nfc->scene_manager, NfcSceneMfUltralightCKeys)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneMfUltralightCKeys); } else { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneFileSelect); diff --git a/applications/main/nfc/scenes/nfc_scene_extra_actions.c b/applications/main/nfc/scenes/nfc_scene_extra_actions.c index 5753d6449..6720b2d7b 100644 --- a/applications/main/nfc/scenes/nfc_scene_extra_actions.c +++ b/applications/main/nfc/scenes/nfc_scene_extra_actions.c @@ -3,7 +3,7 @@ enum SubmenuIndex { SubmenuIndexReadCardType, SubmenuIndexMfClassicKeys, - SubmenuIndexMfUlCKeys, + SubmenuIndexMfUltralightCKeys, SubmenuIndexMfUltralightUnlock, SubmenuIndexSlixUnlock, }; @@ -33,7 +33,7 @@ void nfc_scene_extra_actions_on_enter(void* context) { submenu_add_item( submenu, "MIFARE Ultralight C Keys", - SubmenuIndexMfUlCKeys, + SubmenuIndexMfUltralightCKeys, nfc_scene_extra_actions_submenu_callback, instance); submenu_add_item( @@ -61,8 +61,8 @@ bool nfc_scene_extra_actions_on_event(void* context, SceneManagerEvent event) { if(event.event == SubmenuIndexMfClassicKeys) { scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicKeys); consumed = true; - } else if(event.event == SubmenuIndexMfUlCKeys) { - // TODO: Add MFUL C key management scene here + } else if(event.event == SubmenuIndexMfUltralightCKeys) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCKeys); consumed = true; } else if(event.event == SubmenuIndexMfUltralightUnlock) { mf_ultralight_auth_reset(instance->mf_ul_auth); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c index eaa054149..7ee203285 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c @@ -1,7 +1,5 @@ #include "../nfc_app_i.h" -#define NFC_SCENE_MF_CLASSIC_KEYS_MAX (100) - void nfc_scene_mf_classic_keys_widget_callback(GuiButtonType result, InputType type, void* context) { NfcApp* instance = context; if(type == InputTypeShort) { diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c index a963f44ac..131b5e230 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c @@ -39,7 +39,7 @@ bool nfc_scene_mf_classic_keys_add_on_event(void* context, SceneManagerEvent eve instance->scene_manager, NfcSceneMfClassicKeysWarnDuplicate); } else if(keys_dict_add_key(dict, key.data, sizeof(MfClassicKey))) { scene_manager_next_scene(instance->scene_manager, NfcSceneSaveSuccess); - dolphin_deed(DolphinDeedNfcMfcAdd); + dolphin_deed(DolphinDeedNfcKeyAdd); } else { scene_manager_previous_scene(instance->scene_manager); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys.c new file mode 100644 index 000000000..9bf96f0b4 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys.c @@ -0,0 +1,96 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_ultralight_c_keys_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + NfcApp* instance = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(instance->view_dispatcher, result); + } +} + +void nfc_scene_mf_ultralight_c_keys_on_enter(void* context) { + NfcApp* instance = context; + + // Load flipper dict keys total + uint32_t flipper_dict_keys_total = 0; + KeysDict* dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_SYSTEM_PATH, + KeysDictModeOpenExisting, + sizeof(MfUltralightC3DesAuthKey)); + flipper_dict_keys_total = keys_dict_get_total_keys(dict); + keys_dict_free(dict); + + // Load user dict keys total + uint32_t user_dict_keys_total = 0; + dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + user_dict_keys_total = keys_dict_get_total_keys(dict); + keys_dict_free(dict); + + FuriString* temp_str = furi_string_alloc(); + widget_add_string_element( + instance->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "MIFARE Ultralight C Keys"); + furi_string_printf(temp_str, "System dict: %lu", flipper_dict_keys_total); + widget_add_string_element( + instance->widget, + 0, + 20, + AlignLeft, + AlignTop, + FontSecondary, + furi_string_get_cstr(temp_str)); + furi_string_printf(temp_str, "User dict: %lu", user_dict_keys_total); + widget_add_string_element( + instance->widget, + 0, + 32, + AlignLeft, + AlignTop, + FontSecondary, + furi_string_get_cstr(temp_str)); + widget_add_icon_element(instance->widget, 87, 13, &I_Keychain_39x36); + widget_add_button_element( + instance->widget, + GuiButtonTypeCenter, + "Add", + nfc_scene_mf_ultralight_c_keys_widget_callback, + instance); + if(user_dict_keys_total > 0) { + widget_add_button_element( + instance->widget, + GuiButtonTypeRight, + "List", + nfc_scene_mf_ultralight_c_keys_widget_callback, + instance); + } + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_mf_ultralight_c_keys_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeCenter) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCKeysAdd); + consumed = true; + } else if(event.event == GuiButtonTypeRight) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCKeysList); + consumed = true; + } + } + + return consumed; +} + +void nfc_scene_mf_ultralight_c_keys_on_exit(void* context) { + NfcApp* instance = context; + + widget_reset(instance->widget); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c new file mode 100644 index 000000000..63fdaed49 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c @@ -0,0 +1,63 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_ultralight_c_keys_add_byte_input_callback(void* context) { + NfcApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventByteInputDone); +} + +void nfc_scene_mf_ultralight_c_keys_add_on_enter(void* context) { + NfcApp* instance = context; + + // Setup view + ByteInput* byte_input = instance->byte_input; + byte_input_set_header_text(byte_input, "Enter the key in hex"); + byte_input_set_result_callback( + byte_input, + nfc_scene_mf_ultralight_c_keys_add_byte_input_callback, + NULL, + instance, + instance->byte_input_store, + sizeof(MfUltralightC3DesAuthKey)); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewByteInput); +} + +bool nfc_scene_mf_ultralight_c_keys_add_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventByteInputDone) { + // Add key to dict + KeysDict* dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + + MfUltralightC3DesAuthKey key = {}; + memcpy(key.data, instance->byte_input_store, sizeof(MfUltralightC3DesAuthKey)); + if(keys_dict_is_key_present(dict, key.data, sizeof(MfUltralightC3DesAuthKey))) { + scene_manager_next_scene( + instance->scene_manager, NfcSceneMfUltralightCKeysWarnDuplicate); + } else if(keys_dict_add_key(dict, key.data, sizeof(MfUltralightC3DesAuthKey))) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSaveSuccess); + dolphin_deed(DolphinDeedNfcKeyAdd); + } else { + scene_manager_previous_scene(instance->scene_manager); + } + + keys_dict_free(dict); + consumed = true; + } + } + + return consumed; +} + +void nfc_scene_mf_ultralight_c_keys_add_on_exit(void* context) { + NfcApp* instance = context; + + // Clear view + byte_input_set_result_callback(instance->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(instance->byte_input, ""); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_delete.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_delete.c new file mode 100644 index 000000000..db2903939 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_delete.c @@ -0,0 +1,108 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_ultralight_c_keys_delete_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + NfcApp* instance = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(instance->view_dispatcher, result); + } +} + +void nfc_scene_mf_ultralight_c_keys_delete_on_enter(void* context) { + NfcApp* instance = context; + + uint32_t key_index = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightCKeysDelete); + FuriString* key_str = furi_string_alloc(); + + widget_add_string_element( + instance->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Delete this key?"); + widget_add_button_element( + instance->widget, + GuiButtonTypeLeft, + "Cancel", + nfc_scene_mf_ultralight_c_keys_delete_widget_callback, + instance); + widget_add_button_element( + instance->widget, + GuiButtonTypeRight, + "Delete", + nfc_scene_mf_ultralight_c_keys_delete_widget_callback, + instance); + + KeysDict* mf_ultralight_c_user_dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + size_t dict_keys_num = keys_dict_get_total_keys(mf_ultralight_c_user_dict); + furi_assert(key_index < dict_keys_num); + MfUltralightC3DesAuthKey stack_key; + for(size_t i = 0; i < (key_index + 1); i++) { + bool key_loaded = keys_dict_get_next_key( + mf_ultralight_c_user_dict, stack_key.data, sizeof(MfUltralightC3DesAuthKey)); + furi_assert(key_loaded); + } + furi_string_reset(key_str); + for(size_t i = 0; i < sizeof(MfUltralightC3DesAuthKey); i++) { + furi_string_cat_printf(key_str, "%02X", stack_key.data[i]); + } + + widget_add_string_element( + instance->widget, + 64, + 32, + AlignCenter, + AlignCenter, + FontSecondary, + furi_string_get_cstr(key_str)); + + keys_dict_free(mf_ultralight_c_user_dict); + furi_string_free(key_str); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_mf_ultralight_c_keys_delete_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeRight) { + uint32_t key_index = scene_manager_get_scene_state( + instance->scene_manager, NfcSceneMfUltralightCKeysDelete); + KeysDict* mf_ultralight_c_user_dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + size_t dict_keys_num = keys_dict_get_total_keys(mf_ultralight_c_user_dict); + furi_assert(key_index < dict_keys_num); + MfUltralightC3DesAuthKey stack_key; + for(size_t i = 0; i < (key_index + 1); i++) { + bool key_loaded = keys_dict_get_next_key( + mf_ultralight_c_user_dict, stack_key.data, sizeof(MfUltralightC3DesAuthKey)); + furi_assert(key_loaded); + } + bool key_delete_success = keys_dict_delete_key( + mf_ultralight_c_user_dict, stack_key.data, sizeof(MfUltralightC3DesAuthKey)); + keys_dict_free(mf_ultralight_c_user_dict); + if(key_delete_success) { + scene_manager_next_scene(instance->scene_manager, NfcSceneDeleteSuccess); + } else { + scene_manager_previous_scene(instance->scene_manager); + } + } else if(event.event == GuiButtonTypeLeft) { + scene_manager_previous_scene(instance->scene_manager); + } + consumed = true; + } + + return consumed; +} + +void nfc_scene_mf_ultralight_c_keys_delete_on_exit(void* context) { + NfcApp* instance = context; + + widget_reset(instance->widget); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_list.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_list.c new file mode 100644 index 000000000..e2fda3aea --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_list.c @@ -0,0 +1,66 @@ +#include "../nfc_app_i.h" + +#define NFC_SCENE_MF_ULTRALIGHT_C_KEYS_LIST_MAX (100) + +void nfc_scene_mf_ultralight_c_keys_list_submenu_callback(void* context, uint32_t index) { + NfcApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, index); +} + +void nfc_scene_mf_ultralight_c_keys_list_on_enter(void* context) { + NfcApp* instance = context; + + KeysDict* mf_ultralight_c_user_dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + + submenu_set_header(instance->submenu, "Select key to delete:"); + FuriString* temp_str = furi_string_alloc(); + + size_t dict_keys_num = keys_dict_get_total_keys(mf_ultralight_c_user_dict); + size_t keys_num = MIN((size_t)NFC_SCENE_MF_ULTRALIGHT_C_KEYS_LIST_MAX, dict_keys_num); + MfUltralightC3DesAuthKey stack_key; + + if(keys_num > 0) { + for(size_t i = 0; i < keys_num; i++) { + bool key_loaded = keys_dict_get_next_key( + mf_ultralight_c_user_dict, stack_key.data, sizeof(MfUltralightC3DesAuthKey)); + furi_assert(key_loaded); + furi_string_reset(temp_str); + for(size_t i = 0; i < sizeof(MfUltralightC3DesAuthKey); i++) { + furi_string_cat_printf(temp_str, "%02X", stack_key.data[i]); + } + submenu_add_item( + instance->submenu, + furi_string_get_cstr(temp_str), + i, + nfc_scene_mf_ultralight_c_keys_list_submenu_callback, + instance); + } + } + keys_dict_free(mf_ultralight_c_user_dict); + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_mf_ultralight_c_keys_list_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + + bool consumed = false; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneMfUltralightCKeysDelete, event.event); + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCKeysDelete); + } + + return consumed; +} + +void nfc_scene_mf_ultralight_c_keys_list_on_exit(void* context) { + NfcApp* instance = context; + + submenu_reset(instance->submenu); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_warn_duplicate.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_warn_duplicate.c new file mode 100644 index 000000000..c8881e5d4 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_warn_duplicate.c @@ -0,0 +1,49 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_ultralight_c_keys_warn_duplicate_popup_callback(void* context) { + NfcApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventViewExit); +} + +void nfc_scene_mf_ultralight_c_keys_warn_duplicate_on_enter(void* context) { + NfcApp* instance = context; + + // Setup view + Popup* popup = instance->popup; + popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42); + popup_set_header(popup, "Key Already Exists!", 64, 3, AlignCenter, AlignTop); + popup_set_text( + popup, + "Please enter a\n" + "different key.", + 4, + 24, + AlignLeft, + AlignTop); + popup_set_timeout(popup, 1500); + popup_set_context(popup, instance); + popup_set_callback(popup, nfc_scene_mf_ultralight_c_keys_warn_duplicate_popup_callback); + popup_enable_timeout(popup); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); +} + +bool nfc_scene_mf_ultralight_c_keys_warn_duplicate_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventViewExit) { + consumed = scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneMfUltralightCKeysAdd); + } + } + + return consumed; +} + +void nfc_scene_mf_ultralight_c_keys_warn_duplicate_on_exit(void* context) { + NfcApp* instance = context; + + popup_reset(instance->popup); +} diff --git a/applications/main/nfc/scenes/nfc_scene_save_success.c b/applications/main/nfc/scenes/nfc_scene_save_success.c index 1c76b3f87..9dbfd1ae7 100644 --- a/applications/main/nfc/scenes/nfc_scene_save_success.c +++ b/applications/main/nfc/scenes/nfc_scene_save_success.c @@ -28,6 +28,10 @@ bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) { if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicKeys)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneMfClassicKeys); + } else if(scene_manager_has_previous_scene( + nfc->scene_manager, NfcSceneMfUltralightCKeys)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneMfUltralightCKeys); } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSaveConfirm)) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDetectReader); consumed = true; diff --git a/applications/services/dolphin/helpers/dolphin_deed.c b/applications/services/dolphin/helpers/dolphin_deed.c index f1f42b770..43f30dced 100644 --- a/applications/services/dolphin/helpers/dolphin_deed.c +++ b/applications/services/dolphin/helpers/dolphin_deed.c @@ -20,7 +20,7 @@ static const DolphinDeedWeight dolphin_deed_weights[] = { {3, DolphinAppNfc}, // DolphinDeedNfcSave {1, DolphinAppNfc}, // DolphinDeedNfcDetectReader {2, DolphinAppNfc}, // DolphinDeedNfcEmulate - {2, DolphinAppNfc}, // DolphinDeedNfcMfcAdd + {2, DolphinAppNfc}, // DolphinDeedNfcKeyAdd {1, DolphinAppNfc}, // DolphinDeedNfcAddSave {1, DolphinAppNfc}, // DolphinDeedNfcAddEmulate diff --git a/applications/services/dolphin/helpers/dolphin_deed.h b/applications/services/dolphin/helpers/dolphin_deed.h index c9cd18f31..7202dcf07 100644 --- a/applications/services/dolphin/helpers/dolphin_deed.h +++ b/applications/services/dolphin/helpers/dolphin_deed.h @@ -36,7 +36,7 @@ typedef enum { DolphinDeedNfcSave, DolphinDeedNfcDetectReader, DolphinDeedNfcEmulate, - DolphinDeedNfcMfcAdd, + DolphinDeedNfcKeyAdd, DolphinDeedNfcAddSave, DolphinDeedNfcAddEmulate, diff --git a/applications/system/mfkey/mfkey.c b/applications/system/mfkey/mfkey.c index ae5cc90f2..ad39d186b 100644 --- a/applications/system/mfkey/mfkey.c +++ b/applications/system/mfkey/mfkey.c @@ -702,7 +702,7 @@ void mfkey(ProgramState* program_state) { keys_dict_add_key(user_dict, keyarray[i].data, sizeof(MfClassicKey)); } if(keyarray_size > 0) { - dolphin_deed(DolphinDeedNfcMfcAdd); + dolphin_deed(DolphinDeedNfcKeyAdd); } free(nonce_arr); keys_dict_free(user_dict); diff --git a/lib/toolbox/keys_dict.c b/lib/toolbox/keys_dict.c index 602653e8f..c26e9c1e7 100644 --- a/lib/toolbox/keys_dict.c +++ b/lib/toolbox/keys_dict.c @@ -134,22 +134,21 @@ static void keys_dict_int_to_str(KeysDict* instance, const uint8_t* key_int, Fur furi_string_cat_printf(key_str, "%02X", key_int[i]); } -static void keys_dict_str_to_int(KeysDict* instance, FuriString* key_str, uint64_t* key_int) { +static void keys_dict_str_to_int(KeysDict* instance, FuriString* key_str, uint8_t* key_out) { furi_assert(instance); furi_assert(key_str); - furi_assert(key_int); + furi_assert(key_out); uint8_t key_byte_tmp; char h, l; - *key_int = 0ULL; - + // Process two hex characters at a time to create each byte for(size_t i = 0; i < instance->key_size_symbols - 1; i += 2) { h = furi_string_get_char(key_str, i); l = furi_string_get_char(key_str, i + 1); args_char_to_hex(h, l, &key_byte_tmp); - *key_int |= (uint64_t)key_byte_tmp << (8 * (instance->key_size - 1 - i / 2)); + key_out[i / 2] = key_byte_tmp; } } @@ -193,15 +192,7 @@ bool keys_dict_get_next_key(KeysDict* instance, uint8_t* key, size_t key_size) { bool key_read = keys_dict_get_next_key_str(instance, temp_key); if(key_read) { - size_t tmp_len = key_size; - uint64_t key_int = 0; - - keys_dict_str_to_int(instance, temp_key, &key_int); - - while(tmp_len--) { - key[tmp_len] = (uint8_t)key_int; - key_int >>= 8; - } + keys_dict_str_to_int(instance, temp_key, key); } furi_string_free(temp_key); From e583c105b98215e8192dce3a73e8723d20679294 Mon Sep 17 00:00:00 2001 From: noproto Date: Sun, 10 Nov 2024 17:08:55 -0500 Subject: [PATCH 005/192] Fix ByteInput to properly zero bytes --- applications/services/gui/modules/byte_input.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/applications/services/gui/modules/byte_input.c b/applications/services/gui/modules/byte_input.c index be94ed9ab..71a13413f 100644 --- a/applications/services/gui/modules/byte_input.c +++ b/applications/services/gui/modules/byte_input.c @@ -861,6 +861,10 @@ void byte_input_set_result_callback( model->callback_context = callback_context; model->bytes = bytes; model->bytes_count = bytes_count; + // Zero out the bytes + for(uint8_t i = 0; i < bytes_count; i++) { + model->bytes[i] = 0; + } }, true); } From 92b9bd039ee0231f52777d37614e65b9c76ec5d0 Mon Sep 17 00:00:00 2001 From: Nathan N Date: Sun, 10 Nov 2024 22:40:14 -0500 Subject: [PATCH 006/192] Add more misc keys to mf_ultralight_c_dict.nfc --- .../nfc/assets/mf_ultralight_c_dict.nfc | 51 +++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/applications/main/nfc/resources/nfc/assets/mf_ultralight_c_dict.nfc b/applications/main/nfc/resources/nfc/assets/mf_ultralight_c_dict.nfc index 6d537e36e..fa5dbb1fb 100644 --- a/applications/main/nfc/resources/nfc/assets/mf_ultralight_c_dict.nfc +++ b/applications/main/nfc/resources/nfc/assets/mf_ultralight_c_dict.nfc @@ -1,10 +1,55 @@ +# Sample Key (BREAKMEIFYOUCAN!) +425245414B4D454946594F5543414E21 # Hexadecimal-Reversed Sample Key 12E4143455F495649454D4B414542524 # Byte-Reversed Sample Key (!NACUOYFIEMKAERB) 214E4143554F594649454D4B41455242 -# Sample Key (BREAKMEIFYOUCAN!) -425245414B4D454946594F5543414E21 # Semnox Key (IEMKAERB!NACUOY ) 49454D4B41455242214E4143554F5900 # Modified Semnox Key (IEMKAERB!NACUOYF) -49454D4B41455242214E4143554F5946 \ No newline at end of file +49454D4B41455242214E4143554F5946 + +# Mix of Proxmark and ChameleonMiniLiveDebugger +00000000000000000000000000000000 +000102030405060708090A0B0C0D0E0F +01010101010101010101010101010101 +FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +00112233445566778899AABBCCDDEEFF +47454D5850524553534F53414D504C45 +79702553797025537970255379702553 +4E617468616E2E4C6920546564647920 +43464F494D48504E4C4359454E528841 +6AC292FAA1315B4D858AB3A3D7D5933A +404142434445464748494A4B4C4D4E4F +2B7E151628AED2A6ABF7158809CF4F3C +FBEED618357133667C85E08F7236A8DE +F7DDAC306AE266CCF90BC11EE46D513B +54686973206973206D79206B65792020 +A0A1A2A3A4A5A6A7A0A1A2A3A4A5A6A7 +B0B1B2B3B4B5B6B7B0B1B2B3B4B5B6B7 +B0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF +D3F7D3F7D3F7D3F7D3F7D3F7D3F7D3F7 +11111111111111111111111111111111 +22222222222222222222222222222222 +33333333333333333333333333333333 +44444444444444444444444444444444 +55555555555555555555555555555555 +66666666666666666666666666666666 +77777777777777777777777777777777 +88888888888888888888888888888888 +99999999999999999999999999999999 +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC +DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +0102030405060708090A0B0C0D0E0F10 +00010203040506070809101112131415 +01020304050607080910111213141516 +16151413121110090807060504030201 +15141312111009080706050403020100 +0F0E0D0C0B0A09080706050403020100 +100F0E0D0C0B0A090807060504030201 +303132333435363738393A3B3C3D3E3F +9CABF398358405AE2F0E2B3D31C99A8A +605F5E5D5C5B5A59605F5E5D5C5B5A59 From 948dffadcc1f8748d32583455209ee77035a21e8 Mon Sep 17 00:00:00 2001 From: noproto Date: Fri, 20 Dec 2024 18:07:18 -0500 Subject: [PATCH 007/192] Dictionary attack functional --- .../mf_ultralight/mf_ultralight.c | 18 ++- .../mf_ultralight/mf_ultralight_poller.c | 139 ++++++++---------- .../mf_ultralight/mf_ultralight_poller_i.c | 6 +- .../mf_ultralight/mf_ultralight_poller_i.h | 8 - 4 files changed, 81 insertions(+), 90 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index 6edbc06a2..daf9bedea 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -15,6 +15,7 @@ enum { SubmenuIndexUnlockByReader, SubmenuIndexUnlockByPassword, SubmenuIndexWrite, + SubmenuIndexDictAttack }; enum { @@ -150,7 +151,8 @@ static NfcCommand } if(!mf_ultralight_event->data->auth_context.skip_auth) { mf_ultralight_event->data->auth_context.password = instance->mf_ul_auth->password; - mf_ultralight_event->data->auth_context.tdes_key = instance->mf_ul_auth->tdes_key; + mf_ultralight_event->data->key_request_data.key = instance->mf_ul_auth->tdes_key; + // TODO: Key provided attribute is not set } } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthSuccess) { instance->mf_ul_auth->pack = mf_ultralight_event->data->auth_context.pack; @@ -201,6 +203,14 @@ static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instanc SubmenuIndexUnlock, nfc_protocol_support_common_submenu_callback, instance); + if(data->type == MfUltralightTypeMfulC) { + submenu_add_item( + submenu, + "Unlock with Dictionary", + SubmenuIndexDictAttack, + nfc_protocol_support_common_submenu_callback, + instance); + } } else if( data->type == MfUltralightTypeNTAG213 || data->type == MfUltralightTypeNTAG215 || data->type == MfUltralightTypeNTAG216 || data->type == MfUltralightTypeUL11 || @@ -269,6 +279,12 @@ static bool nfc_scene_read_and_saved_menu_on_event_mf_ultralight( } else if(event.event == SubmenuIndexCommonEdit) { scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); consumed = true; + } else if(event.event == SubmenuIndexDictAttack) { + if(!scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneMfUltralightCDictAttack)) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCDictAttack); + } + consumed = true; } } return consumed; diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index f48348c17..c697544c7 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -181,10 +181,6 @@ MfUltralightPoller* mf_ultralight_poller_alloc(Iso14443_3aPoller* iso14443_3a_po instance->general_event.protocol = NfcProtocolMfUltralight; instance->general_event.event_data = &instance->mfu_event; instance->general_event.instance = instance; - - instance->dict_attack_ctx.auth_success = false; - instance->dict_attack_ctx.is_card_present = false; - mbedtls_des3_init(&instance->des_context); return instance; } @@ -255,7 +251,7 @@ static NfcCommand mf_ultralight_poller_handler_read_version(MfUltralightPoller* instance->data->type = mf_ultralight_get_type_by_version(&instance->data->version); instance->state = MfUltralightPollerStateGetFeatureSet; } else { - FURI_LOG_D(TAG, "Didn't response. Check Ultralight C"); + FURI_LOG_D(TAG, "Didn't respond. Check Ultralight C"); iso14443_3a_poller_halt(instance->iso14443_3a_poller); instance->state = MfUltralightPollerStateDetectMfulC; } @@ -270,7 +266,7 @@ static NfcCommand mf_ultralight_poller_handler_check_ultralight_c(MfUltralightPo instance->data->type = MfUltralightTypeMfulC; instance->state = MfUltralightPollerStateGetFeatureSet; } else { - FURI_LOG_D(TAG, "Didn't response. Check NTAG 203"); + FURI_LOG_D(TAG, "Didn't respond. Check NTAG 203"); instance->state = MfUltralightPollerStateDetectNtag203; } iso14443_3a_poller_halt(instance->iso14443_3a_poller); @@ -456,7 +452,28 @@ static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPol command = instance->callback(instance->general_event, instance->context); if(!instance->mfu_event.data->auth_context.skip_auth) { FURI_LOG_D(TAG, "Trying to authenticate with 3des key"); - instance->auth_context.tdes_key = instance->mfu_event.data->auth_context.tdes_key; + instance->auth_context.tdes_key = instance->mfu_event.data->key_request_data.key; + instance->auth_context.auth_success = false; + // For debugging + FURI_LOG_D( + "TAG", + "Key data: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", + instance->auth_context.tdes_key.data[0], + instance->auth_context.tdes_key.data[1], + instance->auth_context.tdes_key.data[2], + instance->auth_context.tdes_key.data[3], + instance->auth_context.tdes_key.data[4], + instance->auth_context.tdes_key.data[5], + instance->auth_context.tdes_key.data[6], + instance->auth_context.tdes_key.data[7], + instance->auth_context.tdes_key.data[8], + instance->auth_context.tdes_key.data[9], + instance->auth_context.tdes_key.data[10], + instance->auth_context.tdes_key.data[11], + instance->auth_context.tdes_key.data[12], + instance->auth_context.tdes_key.data[13], + instance->auth_context.tdes_key.data[14], + instance->auth_context.tdes_key.data[15]); do { uint8_t output[MF_ULTRALIGHT_C_AUTH_DATA_SIZE]; uint8_t RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0}; @@ -473,20 +490,41 @@ static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPol mf_ultralight_3des_shift_data(RndA); instance->auth_context.auth_success = (memcmp(RndA, decoded_shifted_RndA, sizeof(decoded_shifted_RndA)) == 0); - if(instance->auth_context.auth_success) { - FURI_LOG_D(TAG, "Auth success"); + FURI_LOG_E(TAG, "Auth success"); + if(instance->mode == MfUltralightPollerModeDictAttack) { + memcpy( + &instance->data->page[44], + instance->auth_context.tdes_key.data, + MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE); + instance->data->pages_read = instance->pages_total; + instance->pages_read = instance->pages_total; + instance->state = MfUltralightPollerStateReadSuccess; + } } } while(false); - if(instance->error != MfUltralightErrorNone || !instance->auth_context.auth_success) { - FURI_LOG_D(TAG, "Auth failed"); + FURI_LOG_E(TAG, "Auth failed"); iso14443_3a_poller_halt(instance->iso14443_3a_poller); + if(instance->mode == MfUltralightPollerModeDictAttack) { + // Not needed? We already do a callback earlier? + instance->mfu_event.type = MfUltralightPollerEventTypeRequestKey; + command = instance->callback(instance->general_event, instance->context); + if(!instance->mfu_event.data->key_request_data.key_provided) { + instance->state = MfUltralightPollerStateReadPages; + } else { + instance->auth_context.tdes_key = + instance->mfu_event.data->key_request_data.key; + instance->state = MfUltralightPollerStateAuthMfulC; + } + } } } } - instance->state = MfUltralightPollerStateReadPages; - + // Regression review + if(instance->mode != MfUltralightPollerModeDictAttack) { + instance->state = MfUltralightPollerStateReadPages; + } return command; } @@ -509,12 +547,16 @@ static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* in instance->error = mf_ultralight_poller_read_page(instance, start_page, &data); } + // Regression review + const uint8_t read_cnt = instance->data->type == MfUltralightTypeMfulC ? 1 : 4; if(instance->error == MfUltralightErrorNone) { - if(start_page < instance->pages_total) { - FURI_LOG_D(TAG, "Read page %d success", start_page); - instance->data->page[start_page] = data.page[0]; - instance->pages_read++; - instance->data->pages_read = instance->pages_read; + for(size_t i = 0; i < read_cnt; i++) { + if(start_page + i < instance->pages_total) { + FURI_LOG_D(TAG, "Read page %d success", start_page + i); + instance->data->page[start_page + i] = data.page[i]; + instance->pages_read++; + instance->data->pages_read = instance->pages_read; + } } if(instance->pages_read == instance->pages_total) { @@ -523,7 +565,7 @@ static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* in } else { if(instance->data->type == MfUltralightTypeMfulC && !mf_ultralight_3des_key_valid(instance->data)) { - instance->state = MfUltralightPollerStateDictAttack; + instance->state = MfUltralightPollerStateCheckMfulCAuthStatus; } else { FURI_LOG_D(TAG, "Read page %d failed", instance->pages_read); if(instance->pages_read) { @@ -732,63 +774,6 @@ static NfcCommand mf_ultralight_poller_handler_write_success(MfUltralightPoller* return command; } -static NfcCommand mf_ultralight_poller_handler_dict_attack(MfUltralightPoller* instance) { - NfcCommand command = NfcCommandContinue; - const MfUltralightData* tag_data = instance->data; - uint32_t features = mf_ultralight_get_feature_support_set(tag_data->type); - FURI_LOG_D(TAG, "Dict Attack"); - do { - if(!mf_ultralight_support_feature(features, MfUltralightFeatureSupportAuthenticate)) { - instance->error = MfUltralightErrorProtocol; - instance->state = MfUltralightPollerStateReadFailed; - break; - } - - instance->dict_attack_ctx.is_card_present = true; - instance->mfu_event.type = MfUltralightPollerEventTypeRequestKey; - command = instance->callback(instance->general_event, instance->context); - if(!instance->mfu_event.data->key_request_data.key_provided) { - instance->state = MfUltralightPollerStateReadSuccess; - break; - } - - FURI_LOG_D(TAG, "Trying next 3DES key"); - instance->error = MfUltralightErrorNone; - instance->auth_context.auth_success = false; - instance->auth_context.tdes_key = instance->mfu_event.data->key_request_data.key; - do { - uint8_t output[MF_ULTRALIGHT_C_AUTH_DATA_SIZE]; - uint8_t RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0}; - furi_hal_random_fill_buf(RndA, sizeof(RndA)); - instance->error = mf_ultralight_poller_authenticate_start(instance, RndA, output); - if(instance->error != MfUltralightErrorNone) break; - - uint8_t decoded_shifted_RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0}; - const uint8_t* RndB = output + MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET; - instance->error = mf_ultralight_poller_authenticate_end( - instance, RndB, output, decoded_shifted_RndA); - if(instance->error != MfUltralightErrorNone) break; - - mf_ultralight_3des_shift_data(RndA); - instance->auth_context.auth_success = - (memcmp(RndA, decoded_shifted_RndA, sizeof(decoded_shifted_RndA)) == 0); - if(instance->auth_context.auth_success) { - FURI_LOG_D(TAG, "Dict attack success"); - instance->state = MfUltralightPollerStateReadPages; - instance->dict_attack_ctx.auth_success = true; - break; - } - } while(false); - - if(!instance->auth_context.auth_success) { - FURI_LOG_D(TAG, "Dict attack auth failed"); - iso14443_3a_poller_halt(instance->iso14443_3a_poller); - } - } while(false); - - return command; -} - static const MfUltralightPollerReadHandler mf_ultralight_poller_read_handler[MfUltralightPollerStateNum] = { [MfUltralightPollerStateIdle] = mf_ultralight_poller_handler_idle, @@ -814,8 +799,6 @@ static const MfUltralightPollerReadHandler [MfUltralightPollerStateWritePages] = mf_ultralight_poller_handler_write_pages, [MfUltralightPollerStateWriteFail] = mf_ultralight_poller_handler_write_fail, [MfUltralightPollerStateWriteSuccess] = mf_ultralight_poller_handler_write_success, - [MfUltralightPollerStateDictAttack] = mf_ultralight_poller_handler_dict_attack, - }; static NfcCommand mf_ultralight_poller_run(NfcGenericEvent event, void* context) { diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c index 141ab6c46..c769273ec 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c @@ -134,7 +134,7 @@ MfUltralightError mf_ultralight_poller_authenticate_start( uint8_t* RndB = output + MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET; mf_ultralight_3des_decrypt( &instance->des_context, - instance->mfu_event.data->auth_context.tdes_key.data, + instance->auth_context.tdes_key.data, iv, encRndB, sizeof(encRndB), @@ -145,7 +145,7 @@ MfUltralightError mf_ultralight_poller_authenticate_start( mf_ultralight_3des_encrypt( &instance->des_context, - instance->mfu_event.data->auth_context.tdes_key.data, + instance->auth_context.tdes_key.data, encRndB, output, MF_ULTRALIGHT_C_AUTH_DATA_SIZE, @@ -179,7 +179,7 @@ MfUltralightError mf_ultralight_poller_authenticate_end( mf_ultralight_3des_decrypt( &instance->des_context, - instance->mfu_event.data->auth_context.tdes_key.data, + instance->auth_context.tdes_key.data, RndB, bit_buffer_get_data(instance->rx_buffer) + 1, MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE, diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h index bbd9c6c3e..b35c49aea 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h @@ -68,17 +68,10 @@ typedef enum { MfUltralightPollerStateWritePages, MfUltralightPollerStateWriteFail, MfUltralightPollerStateWriteSuccess, - MfUltralightPollerStateDictAttack, MfUltralightPollerStateNum, } MfUltralightPollerState; -typedef struct { - uint8_t sectors_total; - bool auth_success; - bool is_card_present; -} MfUltralightPollerDictAttackContext; - struct MfUltralightPoller { Iso14443_3aPoller* iso14443_3a_poller; MfUltralightPollerState state; @@ -96,7 +89,6 @@ struct MfUltralightPoller { uint8_t tearing_flag_total; uint16_t current_page; MfUltralightError error; - MfUltralightPollerDictAttackContext dict_attack_ctx; mbedtls_des3_context des_context; NfcGenericEvent general_event; From 64a638d7da4a79ef4beac8da812b5d3feeeb57bf Mon Sep 17 00:00:00 2001 From: "Aaron Tulino (Aaronjamt)" Date: Wed, 13 Aug 2025 00:13:26 -0700 Subject: [PATCH 008/192] Add date/time input module --- applications/services/gui/application.fam | 1 + .../services/gui/modules/date_time_input.c | 347 ++++++++++++++++++ .../services/gui/modules/date_time_input.h | 70 ++++ targets/f7/api_symbols.csv | 6 + 4 files changed, 424 insertions(+) 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/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..63f648edb --- /dev/null +++ b/applications/services/gui/modules/date_time_input.c @@ -0,0 +1,347 @@ +#include "date_time_input.h" +#include +#include + +#define get_state(m, r, c) \ + ((m)->row == (r) && (m)->column == (c) ? \ + ((m)->editing ? EditStateActiveEditing : EditStateActive) : \ + EditStateNone) + +#define ROW_0_Y (10) +#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 { + const char* header; + DateTime* datetime; + + uint8_t row; + uint8_t column; + bool editing; + + DateTimeChangedCallback changed_callback; + DateTimeDoneCallback done_callback; + void* callback_context; +} DateTimeInputModel; + +typedef enum { + EditStateNone, + EditStateActive, + EditStateActiveEditing, +} 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 != 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 void date_time_input_draw_time_callback(Canvas* canvas, DateTimeInputModel* model) { + furi_check(model->datetime); + + char buffer[64]; + + 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), 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", model->datetime->minute); + date_time_input_draw_block( + canvas, 64, ROW_1_Y, 28, ROW_1_H, FontBigNumbers, get_state(model, 1, 1), 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), buffer); +} + +static void date_time_input_draw_date_callback(Canvas* canvas, DateTimeInputModel* model) { + furi_check(model->datetime); + + char buffer[64]; + + 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), 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), 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), 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 bool date_time_input_navigation_callback(InputEvent* event, DateTimeInputModel* model) { + if(event->key == InputKeyUp) { + if(model->row > 0) model->row--; + } else if(event->key == InputKeyDown) { + if(model->row < ROW_COUNT - 1) model->row++; + } else if(event->key == InputKeyOk) { + model->editing = !model->editing; + } else if(event->key == InputKeyRight) { + if(model->column < COLUMN_COUNT - 1) model->column++; + } else if(event->key == InputKeyLeft) { + if(model->column > 0) 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; +} + +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->header = ""; + 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_header_text(DateTimeInput* date_time_input, const char* text) { + furi_check(date_time_input); + + with_view_model( + date_time_input->view, DateTimeInputModel * model, { model->header = text; }, 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..3efc6c7a1 --- /dev/null +++ b/applications/services/gui/modules/date_time_input.h @@ -0,0 +1,70 @@ +/** + * @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 input header text + * + * @param date_time_input date/time input instance + * @param text text to be shown + */ +void date_time_input_set_header_text(DateTimeInput* date_time_input, const char* text); + +#ifdef __cplusplus +} +#endif diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index d142a6374..1ea105e00 100644 --- 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,, @@ -925,6 +926,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_header_text,void,"DateTimeInput*, const char*" +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 374f49319b3b960bad7fc3ca086dc83dfc1e7f57 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 18 Aug 2025 00:21:36 -0400 Subject: [PATCH 009/192] Fix uninitialized key attempt --- .../mf_ultralight/mf_ultralight.c | 11 ++++++++-- .../mf_ultralight/mf_ultralight_poller.c | 21 ++++++++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index daf9bedea..3c041c077 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -151,8 +151,15 @@ static NfcCommand } if(!mf_ultralight_event->data->auth_context.skip_auth) { mf_ultralight_event->data->auth_context.password = instance->mf_ul_auth->password; - mf_ultralight_event->data->key_request_data.key = instance->mf_ul_auth->tdes_key; - // TODO: Key provided attribute is not set + + // Only set tdes_key for Manual/Reader auth types, not for dictionary attacks + if(instance->mf_ul_auth->type == MfUltralightAuthTypeManual || + instance->mf_ul_auth->type == MfUltralightAuthTypeReader) { + mf_ultralight_event->data->key_request_data.key = instance->mf_ul_auth->tdes_key; + mf_ultralight_event->data->key_request_data.key_provided = true; + } else { + mf_ultralight_event->data->key_request_data.key_provided = false; + } } } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthSuccess) { instance->mf_ul_auth->pack = mf_ultralight_event->data->auth_context.pack; diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index c697544c7..0c24dadd6 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -452,7 +452,26 @@ static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPol command = instance->callback(instance->general_event, instance->context); if(!instance->mfu_event.data->auth_context.skip_auth) { FURI_LOG_D(TAG, "Trying to authenticate with 3des key"); - instance->auth_context.tdes_key = instance->mfu_event.data->key_request_data.key; + // Only use the key if it was actually provided + if(instance->mfu_event.data->key_request_data.key_provided) { + instance->auth_context.tdes_key = instance->mfu_event.data->key_request_data.key; + } else if(instance->mode == MfUltralightPollerModeDictAttack) { + // TODO: Can logic be rearranged to request this key before reaching mf_ultralight_poller_handler_auth_ultralight_c in poller? + FURI_LOG_D(TAG, "No initial key provided, requesting key from dictionary"); + // Trigger dictionary key request + instance->mfu_event.type = MfUltralightPollerEventTypeRequestKey; + command = instance->callback(instance->general_event, instance->context); + if(!instance->mfu_event.data->key_request_data.key_provided) { + instance->state = MfUltralightPollerStateReadPages; + return command; + } else { + instance->auth_context.tdes_key = instance->mfu_event.data->key_request_data.key; + } + } else { + FURI_LOG_D(TAG, "No key provided, skipping auth"); + instance->state = MfUltralightPollerStateReadPages; + return command; + } instance->auth_context.auth_success = false; // For debugging FURI_LOG_D( From 38b5d9fb16f8b3dc579bb02f88f01c411374405b Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 18 Aug 2025 01:10:23 -0400 Subject: [PATCH 010/192] Properly return when a key is found in user dict --- .../nfc_scene_mf_ultralight_c_dict_attack.c | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c index d1b2c0978..7e850af98 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c @@ -39,6 +39,7 @@ NfcCommand nfc_mf_ultralight_c_dict_attack_worker_callback(NfcGenericEvent event } else if(poller_event->type == MfUltralightPollerEventTypeReadSuccess) { nfc_device_set_data( instance->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(instance->poller)); + instance->mf_ultralight_c_dict_context.auth_success = true; view_dispatcher_send_custom_event( instance->view_dispatcher, NfcCustomEventDictAttackComplete); command = NfcCommandStop; @@ -130,18 +131,25 @@ bool nfc_scene_mf_ultralight_c_dict_attack_on_event(void* context, SceneManagerE if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventDictAttackComplete) { if(state == DictAttackStateUserDictInProgress) { - nfc_poller_stop(instance->poller); - nfc_poller_free(instance->poller); - keys_dict_free(instance->mf_ultralight_c_dict_context.dict); - scene_manager_set_scene_state( - instance->scene_manager, - NfcSceneMfUltralightCDictAttack, - DictAttackStateSystemDictInProgress); - nfc_scene_mf_ultralight_c_dict_attack_prepare_view(instance); - instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); - nfc_poller_start( - instance->poller, nfc_mf_ultralight_c_dict_attack_worker_callback, instance); - consumed = true; + if(instance->mf_ultralight_c_dict_context.auth_success) { + notification_message(instance->notifications, &sequence_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + consumed = true; + } else { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfUltralightCDictAttack, + DictAttackStateSystemDictInProgress); + nfc_scene_mf_ultralight_c_dict_attack_prepare_view(instance); + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); + nfc_poller_start( + instance->poller, nfc_mf_ultralight_c_dict_attack_worker_callback, instance); + consumed = true; + } } else { notification_message(instance->notifications, &sequence_success); scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); From 1c37a0e01cedb99fa107d6600f6ed538a913f7fe Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 18 Aug 2025 01:42:20 -0400 Subject: [PATCH 011/192] Don't skip the system dictionary --- .../nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c index 7e850af98..f0b2e5de6 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c @@ -39,7 +39,12 @@ NfcCommand nfc_mf_ultralight_c_dict_attack_worker_callback(NfcGenericEvent event } else if(poller_event->type == MfUltralightPollerEventTypeReadSuccess) { nfc_device_set_data( instance->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(instance->poller)); - instance->mf_ultralight_c_dict_context.auth_success = true; + // Check if this is a successful authentication by looking at the poller's auth context + const MfUltralightData* data = nfc_poller_get_data(instance->poller); + if(data->pages_read == data->pages_total) { + // Full read indicates successful authentication in dict attack mode + instance->mf_ultralight_c_dict_context.auth_success = true; + } view_dispatcher_send_custom_event( instance->view_dispatcher, NfcCustomEventDictAttackComplete); command = NfcCommandStop; From 9882c586aeff1b727acfdc299c032c9538f8ae6d Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 18 Aug 2025 02:13:51 -0400 Subject: [PATCH 012/192] Fix regression, read card. Don't try dict if user chose "Unlock with key". --- .../helpers/protocol_support/mf_ultralight/mf_ultralight.c | 5 +++-- lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c | 5 ++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index 3c041c077..656034f7a 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -183,8 +183,9 @@ bool nfc_scene_read_on_event_mf_ultralight(NfcApp* instance, SceneManagerEvent e } else if(event.event == NfcCustomEventPollerIncomplete) { const MfUltralightData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight); - if(data->type == MfUltralightTypeMfulC) { - // Start dict attack for MFUL C cards + if(data->type == MfUltralightTypeMfulC && + instance->mf_ul_auth->type == MfUltralightAuthTypeNone) { + // Start dict attack for MFUL C cards only if no specific auth was attempted scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCDictAttack); } else { notification_message(instance->notifications, &sequence_success); diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index 0c24dadd6..7639abaa9 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -516,9 +516,8 @@ static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPol &instance->data->page[44], instance->auth_context.tdes_key.data, MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE); - instance->data->pages_read = instance->pages_total; - instance->pages_read = instance->pages_total; - instance->state = MfUltralightPollerStateReadSuccess; + // Continue to read pages after successful authentication + instance->state = MfUltralightPollerStateReadPages; } } } while(false); From 603e1bd978f0f7f2c620296bc89681ce8104a387 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 18 Aug 2025 03:16:36 -0400 Subject: [PATCH 013/192] Now reading an UL-C card gives the success tone only if the key is found --- .../protocol_support/mf_ultralight/mf_ultralight.c | 6 +++++- .../nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c | 9 +++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index 656034f7a..915c8799b 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -188,7 +188,11 @@ bool nfc_scene_read_on_event_mf_ultralight(NfcApp* instance, SceneManagerEvent e // Start dict attack for MFUL C cards only if no specific auth was attempted scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCDictAttack); } else { - notification_message(instance->notifications, &sequence_success); + if(data->pages_read == data->pages_total) { + notification_message(instance->notifications, &sequence_success); + } else { + notification_message(instance->notifications, &sequence_semi_success); + } scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); dolphin_deed(DolphinDeedNfcReadSuccess); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c index f0b2e5de6..cf31e0f2d 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c @@ -156,7 +156,12 @@ bool nfc_scene_mf_ultralight_c_dict_attack_on_event(void* context, SceneManagerE consumed = true; } } else { - notification_message(instance->notifications, &sequence_success); + // TODO: Could check if card is fully read here like MFC dict attack, but found key means fully read + if(instance->mf_ultralight_c_dict_context.auth_success) { + notification_message(instance->notifications, &sequence_success); + } else { + notification_message(instance->notifications, &sequence_semi_success); + } scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); dolphin_deed(DolphinDeedNfcReadSuccess); consumed = true; @@ -184,7 +189,7 @@ bool nfc_scene_mf_ultralight_c_dict_attack_on_event(void* context, SceneManagerE } else { nfc_poller_stop(instance->poller); nfc_poller_free(instance->poller); - notification_message(instance->notifications, &sequence_success); + notification_message(instance->notifications, &sequence_semi_success); scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); dolphin_deed(DolphinDeedNfcReadSuccess); } From 0ec7a10ba908c634a43ec2ec4a89c587892416c9 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 18 Aug 2025 04:00:20 -0400 Subject: [PATCH 014/192] UL-C dict attack scene is now visible --- .../main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c index cf31e0f2d..94e1e9d80 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c @@ -116,6 +116,8 @@ void nfc_scene_mf_ultralight_c_dict_attack_on_enter(void* context) { instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); nfc_poller_start(instance->poller, nfc_mf_ultralight_c_dict_attack_worker_callback, instance); + dict_attack_set_card_state(instance->dict_attack, true); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewDictAttack); nfc_blink_read_start(instance); } From 9499c489c2ec8d4a6b0ac4bbaf2a89f8d474e555 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 18 Aug 2025 04:13:46 -0400 Subject: [PATCH 015/192] Fix display of key index during dict attack scene --- .../main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c index 94e1e9d80..946fccf60 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c @@ -96,6 +96,8 @@ void nfc_scene_mf_ultralight_c_dict_attack_prepare_view(NfcApp* instance) { dict_attack_set_total_dict_keys( instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_total); instance->mf_ultralight_c_dict_context.dict_keys_current = 0; + dict_attack_set_current_dict_key( + instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_current); dict_attack_set_callback( instance->dict_attack, nfc_scene_mf_ultralight_c_dict_attack_dict_attack_result_callback, From afe4559dd39f9136565bfb7513eb04b107e7cb3b Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 18 Aug 2025 22:00:45 -0400 Subject: [PATCH 016/192] Fix buffer overflow --- applications/main/nfc/nfc_app_i.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 9eb26a847..f7af85ea8 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -65,7 +65,7 @@ #define NFC_NAME_SIZE 22 #define NFC_TEXT_STORE_SIZE 128 -#define NFC_BYTE_INPUT_STORE_SIZE 10 +#define NFC_BYTE_INPUT_STORE_SIZE 16 #define NFC_LOG_SIZE_MAX (1024) #define NFC_APP_FOLDER EXT_PATH("nfc") #define NFC_APP_EXTENSION ".nfc" From 6e05de476fb6169ec5a2c10331afc66fb33f81ba Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 18 Aug 2025 23:05:23 -0400 Subject: [PATCH 017/192] Skip working --- .../nfc_scene_mf_ultralight_c_dict_attack.c | 42 +++++++++---------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c index 946fccf60..562e6c1d8 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c @@ -175,30 +175,26 @@ bool nfc_scene_mf_ultralight_c_dict_attack_on_event(void* context, SceneManagerE instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_current); consumed = true; } else if(event.event == NfcCustomEventDictAttackSkip) { - if(instance->mf_ultralight_c_dict_context.is_card_present) { - if(state == DictAttackStateUserDictInProgress) { - nfc_poller_stop(instance->poller); - nfc_poller_free(instance->poller); - keys_dict_free(instance->mf_ultralight_c_dict_context.dict); - scene_manager_set_scene_state( - instance->scene_manager, - NfcSceneMfUltralightCDictAttack, - DictAttackStateSystemDictInProgress); - nfc_scene_mf_ultralight_c_dict_attack_prepare_view(instance); - instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); - nfc_poller_start( - instance->poller, - nfc_mf_ultralight_c_dict_attack_worker_callback, - instance); - } else { - nfc_poller_stop(instance->poller); - nfc_poller_free(instance->poller); - notification_message(instance->notifications, &sequence_semi_success); - scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); - dolphin_deed(DolphinDeedNfcReadSuccess); - } - consumed = true; + if(state == DictAttackStateUserDictInProgress) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfUltralightCDictAttack, + DictAttackStateSystemDictInProgress); + nfc_scene_mf_ultralight_c_dict_attack_prepare_view(instance); + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); + nfc_poller_start( + instance->poller, + nfc_mf_ultralight_c_dict_attack_worker_callback, + instance); + } else { + notification_message(instance->notifications, &sequence_semi_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); } + consumed = true; } } else if(event.type == SceneManagerEventTypeBack) { scene_manager_next_scene(instance->scene_manager, NfcSceneExitConfirm); From 8f279a682bd57acd45fecb49113bef2b9126b5f5 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 18 Aug 2025 23:54:00 -0400 Subject: [PATCH 018/192] Pretty UI --- .../nfc_scene_mf_ultralight_c_dict_attack.c | 20 +- applications/main/nfc/views/dict_attack.c | 324 ++++++++++++------ applications/main/nfc/views/dict_attack.h | 13 + 3 files changed, 246 insertions(+), 111 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c index 562e6c1d8..99066bef0 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c @@ -3,6 +3,8 @@ #define TAG "NfcMfUlCDictAttack" +// TODO: Support card_detected properly + enum { DictAttackStateUserDictInProgress, DictAttackStateSystemDictInProgress, @@ -41,9 +43,15 @@ NfcCommand nfc_mf_ultralight_c_dict_attack_worker_callback(NfcGenericEvent event instance->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(instance->poller)); // Check if this is a successful authentication by looking at the poller's auth context const MfUltralightData* data = nfc_poller_get_data(instance->poller); + + // Update page information + dict_attack_set_pages_read(instance->dict_attack, data->pages_read); + dict_attack_set_pages_total(instance->dict_attack, data->pages_total); + if(data->pages_read == data->pages_total) { // Full read indicates successful authentication in dict attack mode instance->mf_ultralight_c_dict_context.auth_success = true; + dict_attack_set_key_found(instance->dict_attack, true); } view_dispatcher_send_custom_event( instance->view_dispatcher, NfcCustomEventDictAttackComplete); @@ -65,6 +73,10 @@ void nfc_scene_mf_ultralight_c_dict_attack_dict_attack_result_callback( void nfc_scene_mf_ultralight_c_dict_attack_prepare_view(NfcApp* instance) { uint32_t state = scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightCDictAttack); + + // Set attack type to Ultralight C + dict_attack_set_type(instance->dict_attack, DictAttackTypeMfUltralightC); + if(state == DictAttackStateUserDictInProgress) { do { if(!keys_dict_check_presence(NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH)) { @@ -98,6 +110,12 @@ void nfc_scene_mf_ultralight_c_dict_attack_prepare_view(NfcApp* instance) { instance->mf_ultralight_c_dict_context.dict_keys_current = 0; dict_attack_set_current_dict_key( instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_current); + + // Set initial Ultralight C specific values + dict_attack_set_key_found(instance->dict_attack, false); + dict_attack_set_pages_total(instance->dict_attack, 48); // Ultralight C page count + dict_attack_set_pages_read(instance->dict_attack, 0); + dict_attack_set_callback( instance->dict_attack, nfc_scene_mf_ultralight_c_dict_attack_dict_attack_result_callback, @@ -160,7 +178,7 @@ bool nfc_scene_mf_ultralight_c_dict_attack_on_event(void* context, SceneManagerE consumed = true; } } else { - // TODO: Could check if card is fully read here like MFC dict attack, but found key means fully read + // Could check if card is fully read here like MFC dict attack, but found key means fully read if(instance->mf_ultralight_c_dict_context.auth_success) { notification_message(instance->notifications, &sequence_success); } else { diff --git a/applications/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index 726076972..5a873fcfb 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -13,12 +13,13 @@ struct DictAttack { typedef struct { FuriString* header; bool card_detected; + DictAttackType attack_type; + + // MIFARE Classic specific uint8_t sectors_total; uint8_t sectors_read; uint8_t current_sector; uint8_t keys_found; - size_t dict_keys_total; - size_t dict_keys_current; bool is_key_attack; uint8_t key_attack_current_sector; MfClassicNestedPhase nested_phase; @@ -26,8 +27,161 @@ typedef struct { MfClassicBackdoor backdoor; uint16_t nested_target_key; uint16_t msb_count; + + // Ultralight C specific + uint8_t pages_total; + uint8_t pages_read; + bool key_found; + + // Common + size_t dict_keys_total; + size_t dict_keys_current; } DictAttackViewModel; +static void dict_attack_draw_mf_classic(Canvas* canvas, DictAttackViewModel* m) { + char draw_str[32] = {}; + canvas_set_font(canvas, FontSecondary); + + switch(m->nested_phase) { + case MfClassicNestedPhaseAnalyzePRNG: + furi_string_set(m->header, "PRNG Analysis"); + break; + case MfClassicNestedPhaseDictAttack: + case MfClassicNestedPhaseDictAttackVerify: + case MfClassicNestedPhaseDictAttackResume: + furi_string_set(m->header, "Nested Dictionary"); + break; + case MfClassicNestedPhaseCalibrate: + case MfClassicNestedPhaseRecalibrate: + furi_string_set(m->header, "Calibration"); + break; + case MfClassicNestedPhaseCollectNtEnc: + furi_string_set(m->header, "Nonce Collection"); + break; + default: + break; + } + + if(m->prng_type == MfClassicPrngTypeHard) { + furi_string_cat(m->header, " (Hard)"); + } + + if(m->backdoor != MfClassicBackdoorNone && m->backdoor != MfClassicBackdoorUnknown) { + if(m->nested_phase != MfClassicNestedPhaseNone) { + furi_string_cat(m->header, " (Backdoor)"); + } else { + furi_string_set(m->header, "Backdoor Read"); + } + } + + canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(m->header)); + if(m->nested_phase == MfClassicNestedPhaseCollectNtEnc) { + uint8_t nonce_sector = + m->nested_target_key / (m->prng_type == MfClassicPrngTypeWeak ? 4 : 2); + snprintf(draw_str, sizeof(draw_str), "Collecting from sector: %d", nonce_sector); + canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); + } else if(m->is_key_attack) { + snprintf( + draw_str, + sizeof(draw_str), + "Reuse key check for sector: %d", + m->key_attack_current_sector); + } else { + snprintf(draw_str, sizeof(draw_str), "Unlocking sector: %d", m->current_sector); + } + canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); + float dict_progress = 0; + if(m->nested_phase == MfClassicNestedPhaseAnalyzePRNG || + m->nested_phase == MfClassicNestedPhaseDictAttack || + m->nested_phase == MfClassicNestedPhaseDictAttackVerify || + m->nested_phase == MfClassicNestedPhaseDictAttackResume) { + // Phase: Nested dictionary attack + uint8_t target_sector = + m->nested_target_key / (m->prng_type == MfClassicPrngTypeWeak ? 2 : 16); + dict_progress = (float)(target_sector) / (float)(m->sectors_total); + snprintf(draw_str, sizeof(draw_str), "%d/%d", target_sector, m->sectors_total); + } else if( + m->nested_phase == MfClassicNestedPhaseCalibrate || + m->nested_phase == MfClassicNestedPhaseRecalibrate || + m->nested_phase == MfClassicNestedPhaseCollectNtEnc) { + // Phase: Nonce collection + if(m->prng_type == MfClassicPrngTypeWeak) { + uint8_t target_sector = m->nested_target_key / 4; + dict_progress = (float)(target_sector) / (float)(m->sectors_total); + snprintf(draw_str, sizeof(draw_str), "%d/%d", target_sector, m->sectors_total); + } else { + uint16_t max_msb = UINT8_MAX + 1; + dict_progress = (float)(m->msb_count) / (float)(max_msb); + snprintf(draw_str, sizeof(draw_str), "%d/%d", m->msb_count, max_msb); + } + } else { + dict_progress = m->dict_keys_total == 0 ? + 0 : + (float)(m->dict_keys_current) / (float)(m->dict_keys_total); + if(m->dict_keys_current == 0) { + // Cause when people see 0 they think it's broken + snprintf(draw_str, sizeof(draw_str), "%d/%zu", 1, m->dict_keys_total); + } else { + snprintf( + draw_str, + sizeof(draw_str), + "%zu/%zu", + m->dict_keys_current, + m->dict_keys_total); + } + } + if(dict_progress > 1.0f) { + dict_progress = 1.0f; + } + elements_progress_bar_with_text(canvas, 0, 20, 128, dict_progress, draw_str); + canvas_set_font(canvas, FontSecondary); + snprintf( + draw_str, + sizeof(draw_str), + "Keys found: %d/%d", + m->keys_found, + m->sectors_total * NFC_CLASSIC_KEYS_PER_SECTOR); + canvas_draw_str_aligned(canvas, 0, 33, AlignLeft, AlignTop, draw_str); + snprintf( + draw_str, sizeof(draw_str), "Sectors Read: %d/%d", m->sectors_read, m->sectors_total); + canvas_draw_str_aligned(canvas, 0, 43, AlignLeft, AlignTop, draw_str); +} + +static void dict_attack_draw_mf_ultralight_c(Canvas* canvas, DictAttackViewModel* m) { + char draw_str[32] = {}; + canvas_set_font(canvas, FontSecondary); + + canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(m->header)); + + snprintf(draw_str, sizeof(draw_str), "Trying keys"); + canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); + + float dict_progress = m->dict_keys_total == 0 ? + 0 : + (float)(m->dict_keys_current) / (float)(m->dict_keys_total); + if(m->dict_keys_current == 0) { + snprintf(draw_str, sizeof(draw_str), "%d/%zu", 1, m->dict_keys_total); + } else { + snprintf( + draw_str, + sizeof(draw_str), + "%zu/%zu", + m->dict_keys_current, + m->dict_keys_total); + } + if(dict_progress > 1.0f) { + dict_progress = 1.0f; + } + elements_progress_bar_with_text(canvas, 0, 20, 128, dict_progress, draw_str); + + canvas_set_font(canvas, FontSecondary); + snprintf(draw_str, sizeof(draw_str), "Key found: %s", m->key_found ? "Yes" : "No"); + canvas_draw_str_aligned(canvas, 0, 33, AlignLeft, AlignTop, draw_str); + + snprintf(draw_str, sizeof(draw_str), "Pages read: %d/%d", m->pages_read, m->pages_total); + canvas_draw_str_aligned(canvas, 0, 43, AlignLeft, AlignTop, draw_str); +} + static void dict_attack_draw_callback(Canvas* canvas, void* model) { DictAttackViewModel* m = model; if(!m->card_detected) { @@ -37,113 +191,11 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { elements_multiline_text_aligned( canvas, 64, 23, AlignCenter, AlignTop, "Make sure the tag is\npositioned correctly."); } else { - char draw_str[32] = {}; - canvas_set_font(canvas, FontSecondary); - - switch(m->nested_phase) { - case MfClassicNestedPhaseAnalyzePRNG: - furi_string_set(m->header, "PRNG Analysis"); - break; - case MfClassicNestedPhaseDictAttack: - case MfClassicNestedPhaseDictAttackVerify: - case MfClassicNestedPhaseDictAttackResume: - furi_string_set(m->header, "Nested Dictionary"); - break; - case MfClassicNestedPhaseCalibrate: - case MfClassicNestedPhaseRecalibrate: - furi_string_set(m->header, "Calibration"); - break; - case MfClassicNestedPhaseCollectNtEnc: - furi_string_set(m->header, "Nonce Collection"); - break; - default: - break; + if(m->attack_type == DictAttackTypeMfClassic) { + dict_attack_draw_mf_classic(canvas, m); + } else if(m->attack_type == DictAttackTypeMfUltralightC) { + dict_attack_draw_mf_ultralight_c(canvas, m); } - - if(m->prng_type == MfClassicPrngTypeHard) { - furi_string_cat(m->header, " (Hard)"); - } - - if(m->backdoor != MfClassicBackdoorNone && m->backdoor != MfClassicBackdoorUnknown) { - if(m->nested_phase != MfClassicNestedPhaseNone) { - furi_string_cat(m->header, " (Backdoor)"); - } else { - furi_string_set(m->header, "Backdoor Read"); - } - } - - canvas_draw_str_aligned( - canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(m->header)); - if(m->nested_phase == MfClassicNestedPhaseCollectNtEnc) { - uint8_t nonce_sector = - m->nested_target_key / (m->prng_type == MfClassicPrngTypeWeak ? 4 : 2); - snprintf(draw_str, sizeof(draw_str), "Collecting from sector: %d", nonce_sector); - canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); - } else if(m->is_key_attack) { - snprintf( - draw_str, - sizeof(draw_str), - "Reuse key check for sector: %d", - m->key_attack_current_sector); - } else { - snprintf(draw_str, sizeof(draw_str), "Unlocking sector: %d", m->current_sector); - } - canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); - float dict_progress = 0; - if(m->nested_phase == MfClassicNestedPhaseAnalyzePRNG || - m->nested_phase == MfClassicNestedPhaseDictAttack || - m->nested_phase == MfClassicNestedPhaseDictAttackVerify || - m->nested_phase == MfClassicNestedPhaseDictAttackResume) { - // Phase: Nested dictionary attack - uint8_t target_sector = - m->nested_target_key / (m->prng_type == MfClassicPrngTypeWeak ? 2 : 16); - dict_progress = (float)(target_sector) / (float)(m->sectors_total); - snprintf(draw_str, sizeof(draw_str), "%d/%d", target_sector, m->sectors_total); - } else if( - m->nested_phase == MfClassicNestedPhaseCalibrate || - m->nested_phase == MfClassicNestedPhaseRecalibrate || - m->nested_phase == MfClassicNestedPhaseCollectNtEnc) { - // Phase: Nonce collection - if(m->prng_type == MfClassicPrngTypeWeak) { - uint8_t target_sector = m->nested_target_key / 4; - dict_progress = (float)(target_sector) / (float)(m->sectors_total); - snprintf(draw_str, sizeof(draw_str), "%d/%d", target_sector, m->sectors_total); - } else { - uint16_t max_msb = UINT8_MAX + 1; - dict_progress = (float)(m->msb_count) / (float)(max_msb); - snprintf(draw_str, sizeof(draw_str), "%d/%d", m->msb_count, max_msb); - } - } else { - dict_progress = m->dict_keys_total == 0 ? - 0 : - (float)(m->dict_keys_current) / (float)(m->dict_keys_total); - if(m->dict_keys_current == 0) { - // Cause when people see 0 they think it's broken - snprintf(draw_str, sizeof(draw_str), "%d/%zu", 1, m->dict_keys_total); - } else { - snprintf( - draw_str, - sizeof(draw_str), - "%zu/%zu", - m->dict_keys_current, - m->dict_keys_total); - } - } - if(dict_progress > 1.0f) { - dict_progress = 1.0f; - } - elements_progress_bar_with_text(canvas, 0, 20, 128, dict_progress, draw_str); - canvas_set_font(canvas, FontSecondary); - snprintf( - draw_str, - sizeof(draw_str), - "Keys found: %d/%d", - m->keys_found, - m->sectors_total * NFC_CLASSIC_KEYS_PER_SECTOR); - canvas_draw_str_aligned(canvas, 0, 33, AlignLeft, AlignTop, draw_str); - snprintf( - draw_str, sizeof(draw_str), "Sectors Read: %d/%d", m->sectors_read, m->sectors_total); - canvas_draw_str_aligned(canvas, 0, 43, AlignLeft, AlignTop, draw_str); } elements_button_center(canvas, "Skip"); } @@ -195,18 +247,28 @@ void dict_attack_reset(DictAttack* instance) { instance->view, DictAttackViewModel * model, { + model->attack_type = DictAttackTypeMfClassic; + + // MIFARE Classic fields model->sectors_total = 0; model->sectors_read = 0; model->current_sector = 0; model->keys_found = 0; - model->dict_keys_total = 0; - model->dict_keys_current = 0; model->is_key_attack = false; model->nested_phase = MfClassicNestedPhaseNone; model->prng_type = MfClassicPrngTypeUnknown; model->backdoor = MfClassicBackdoorUnknown; model->nested_target_key = 0; model->msb_count = 0; + + // Ultralight C fields + model->pages_total = 0; + model->pages_read = 0; + model->key_found = false; + + // Common fields + model->dict_keys_total = 0; + model->dict_keys_current = 0; furi_string_reset(model->header); }, false); @@ -355,3 +417,45 @@ void dict_attack_set_msb_count(DictAttack* instance, uint16_t msb_count) { with_view_model( instance->view, DictAttackViewModel * model, { model->msb_count = msb_count; }, true); } + +void dict_attack_set_type(DictAttack* instance, DictAttackType type) { + furi_assert(instance); + + with_view_model( + instance->view, + DictAttackViewModel * model, + { + model->attack_type = type; + }, + true); +} + +void dict_attack_set_pages_total(DictAttack* instance, uint8_t pages_total) { + furi_assert(instance); + + with_view_model( + instance->view, + DictAttackViewModel * model, + { model->pages_total = pages_total; }, + true); +} + +void dict_attack_set_pages_read(DictAttack* instance, uint8_t pages_read) { + furi_assert(instance); + + with_view_model( + instance->view, + DictAttackViewModel * model, + { model->pages_read = pages_read; }, + true); +} + +void dict_attack_set_key_found(DictAttack* instance, bool key_found) { + furi_assert(instance); + + with_view_model( + instance->view, + DictAttackViewModel * model, + { model->key_found = key_found; }, + true); +} diff --git a/applications/main/nfc/views/dict_attack.h b/applications/main/nfc/views/dict_attack.h index b6c6fdbdc..70709f86e 100644 --- a/applications/main/nfc/views/dict_attack.h +++ b/applications/main/nfc/views/dict_attack.h @@ -8,6 +8,11 @@ extern "C" { #endif +typedef enum { + DictAttackTypeMfClassic, + DictAttackTypeMfUltralightC, +} DictAttackType; + typedef struct DictAttack DictAttack; typedef enum { @@ -56,6 +61,14 @@ void dict_attack_set_nested_target_key(DictAttack* instance, uint16_t target_key void dict_attack_set_msb_count(DictAttack* instance, uint16_t msb_count); +void dict_attack_set_type(DictAttack* instance, DictAttackType type); + +void dict_attack_set_pages_total(DictAttack* instance, uint8_t pages_total); + +void dict_attack_set_pages_read(DictAttack* instance, uint8_t pages_read); + +void dict_attack_set_key_found(DictAttack* instance, bool key_found); + #ifdef __cplusplus } #endif From f9507132984487f587eba935d3f60252832bc2da Mon Sep 17 00:00:00 2001 From: noproto Date: Sat, 23 Aug 2025 11:11:36 -0400 Subject: [PATCH 019/192] Bump Picopass to latest --- applications/system/picopass | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/system/picopass b/applications/system/picopass index 00f8a221f..9889c7119 160000 --- a/applications/system/picopass +++ b/applications/system/picopass @@ -1 +1 @@ -Subproject commit 00f8a221fa020ab0fcc22d77e43ae1ab6ac51c6b +Subproject commit 9889c71198c73f61400f718ed50cb033c0facc8b From 2cfe6f61301f994cb887ad5c6485fdefcb402b25 Mon Sep 17 00:00:00 2001 From: noproto Date: Sat, 23 Aug 2025 15:36:34 -0400 Subject: [PATCH 020/192] Initial commit: MFKey 3.1 --- applications/system/mfkey/.catalog/README.md | 2 +- .../system/mfkey/.catalog/changelog.md | 2 + applications/system/mfkey/application.fam | 2 +- applications/system/mfkey/crypto1.h | 405 ++-- applications/system/mfkey/mfkey.c | 1923 ++++++++++------- applications/system/mfkey/mfkey.h | 170 +- 6 files changed, 1431 insertions(+), 1073 deletions(-) diff --git a/applications/system/mfkey/.catalog/README.md b/applications/system/mfkey/.catalog/README.md index 7f130b41f..22ff43aa6 100644 --- a/applications/system/mfkey/.catalog/README.md +++ b/applications/system/mfkey/.catalog/README.md @@ -8,5 +8,5 @@ After collecting nonces using the Extract MF Keys option, press the Start button ## Credits -Developers: noproto, AG, Flipper Devices, WillyJL +Developers: noproto, AG, Flipper Devices, WillyJL, CavallUwU Thanks: AloneLiberty, Foxushka, bettse, Equip diff --git a/applications/system/mfkey/.catalog/changelog.md b/applications/system/mfkey/.catalog/changelog.md index 2b05351e0..c93c6e8f8 100644 --- a/applications/system/mfkey/.catalog/changelog.md +++ b/applications/system/mfkey/.catalog/changelog.md @@ -1,3 +1,5 @@ +## 3.1 + - Key recovery is 20% faster, added write buffering of Static Encrypted Nested key candidates ## 3.0 - Added Static Encrypted Nested key recovery, added NFC app support, dropped FlipperNested support ## 2.7 diff --git a/applications/system/mfkey/application.fam b/applications/system/mfkey/application.fam index dfd513847..0d81c1050 100644 --- a/applications/system/mfkey/application.fam +++ b/applications/system/mfkey/application.fam @@ -15,7 +15,7 @@ App( fap_icon_assets="images", fap_weburl="https://github.com/noproto/FlipperMfkey", fap_description="MIFARE Classic key recovery tool", - fap_version="3.0", + fap_version="3.1", ) App( diff --git a/applications/system/mfkey/crypto1.h b/applications/system/mfkey/crypto1.h index 9caf5b35e..b9f7c7725 100644 --- a/applications/system/mfkey/crypto1.h +++ b/applications/system/mfkey/crypto1.h @@ -6,251 +6,260 @@ #include #include -#define LF_POLY_ODD (0x29CE5C) +#define LF_POLY_ODD (0x29CE5C) #define LF_POLY_EVEN (0x870804) -#define BIT(x, n) ((x) >> (n) & 1) -#define BEBIT(x, n) BIT(x, (n) ^ 24) +#define BIT(x, n) ((x) >> (n) & 1) +#define BEBIT(x, n) BIT(x, (n) ^ 24) #define SWAPENDIAN(x) \ - ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) + ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) static inline uint32_t prng_successor(uint32_t x, uint32_t n); static inline int filter(uint32_t const x); static inline uint8_t evenparity32(uint32_t x); static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2); -void crypto1_get_lfsr(struct Crypto1State* state, MfClassicKey* lfsr); -static inline uint32_t crypt_word(struct Crypto1State* s); -static inline void crypt_word_noret(struct Crypto1State* s, uint32_t in, int x); -static inline uint32_t crypt_word_ret(struct Crypto1State* s, uint32_t in, int x); +void crypto1_get_lfsr(struct Crypto1State *state, MfClassicKey *lfsr); +static inline uint32_t crypt_word(struct Crypto1State *s); +static inline void crypt_word_noret(struct Crypto1State *s, uint32_t in, int x); +static inline uint32_t crypt_word_ret(struct Crypto1State *s, uint32_t in, int x); static uint32_t crypt_word_par( - struct Crypto1State* s, - uint32_t in, - int is_encrypted, - uint32_t nt_plain, - uint8_t* parity_keystream_bits); -static inline void rollback_word_noret(struct Crypto1State* s, uint32_t in, int x); -static inline uint8_t napi_lfsr_rollback_bit(struct Crypto1State* s, uint32_t in, int fb); -static inline uint32_t napi_lfsr_rollback_word(struct Crypto1State* s, uint32_t in, int fb); + struct Crypto1State *s, + uint32_t in, + int is_encrypted, + uint32_t nt_plain, + uint8_t *parity_keystream_bits); +static inline void rollback_word_noret(struct Crypto1State *s, uint32_t in, int x); +static inline uint8_t napi_lfsr_rollback_bit(struct Crypto1State *s, uint32_t in, int fb); +static inline uint32_t napi_lfsr_rollback_word(struct Crypto1State *s, uint32_t in, int fb); static const uint8_t lookup1[256] = { - 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, - 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, - 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24}; + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, + 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24}; static const uint8_t lookup2[256] = { - 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, - 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, - 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, - 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, - 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, - 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, - 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, - 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, - 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6}; + 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, + 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, + 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, + 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, + 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, + 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, + 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, + 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, + 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6}; -static inline int filter(uint32_t const x) { - uint32_t f; - f = lookup1[x & 0xff] | lookup2[(x >> 8) & 0xff]; - f |= 0x0d938 >> (x >> 16 & 0xf) & 1; - return BIT(0xEC57E80A, f); +static inline int filter(uint32_t const x) +{ + uint32_t f; + f = lookup1[x & 0xff] | lookup2[(x >> 8) & 0xff]; + f |= 0x0d938 >> (x >> 16 & 0xf) & 1; + return BIT(0xEC57E80A, f); } -#ifndef __ARM_ARCH_7EM__ -static inline uint8_t evenparity32(uint32_t x) { - return __builtin_parity(x); -} -#endif - #ifdef __ARM_ARCH_7EM__ -static inline uint8_t evenparity32(uint32_t x) { - uint32_t result; - __asm__ volatile("eor r1, %[x], %[x], lsr #16 \n\t" // r1 = x ^ (x >> 16) - "eor r1, r1, r1, lsr #8 \n\t" // r1 = r1 ^ (r1 >> 8) - "eor r1, r1, r1, lsr #4 \n\t" // r1 = r1 ^ (r1 >> 4) - "eor r1, r1, r1, lsr #2 \n\t" // r1 = r1 ^ (r1 >> 2) - "eor r1, r1, r1, lsr #1 \n\t" // r1 = r1 ^ (r1 >> 1) - "and %[result], r1, #1 \n\t" // result = r1 & 1 - : [result] "=r"(result) - : [x] "r"(x) - : "r1"); - return result; +static inline uint8_t evenparity32(uint32_t x) +{ + // fold 32 bits -> 16 -> 8 -> 4 + x ^= x >> 16; + x ^= x >> 8; + x ^= x >> 4; + // magic 0x6996: bit i tells you parity of i (0 ≤ i < 16) + return (uint8_t)((0x6996u >> (x & 0xF)) & 1); } #endif -static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2) { - int p = data[item] >> 25; - p = p << 1 | evenparity32(data[item] & mask1); - p = p << 1 | evenparity32(data[item] & mask2); - data[item] = p << 24 | (data[item] & 0xffffff); +static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2) +{ + int p = data[item] >> 25; + p = p << 1 | evenparity32(data[item] & mask1); + p = p << 1 | evenparity32(data[item] & mask2); + data[item] = p << 24 | (data[item] & 0xffffff); } -static inline uint32_t crypt_word(struct Crypto1State* s) { - // "in" and "x" are always 0 (last iteration) - uint32_t res_ret = 0; - uint32_t feedin, t; - for(int i = 0; i <= 31; i++) { - res_ret |= (filter(s->odd) << (24 ^ i)); //-V629 - feedin = LF_POLY_EVEN & s->even; - feedin ^= LF_POLY_ODD & s->odd; - s->even = s->even << 1 | (evenparity32(feedin)); - t = s->odd, s->odd = s->even, s->even = t; - } - return res_ret; +static inline uint32_t crypt_word(struct Crypto1State *s) +{ + // "in" and "x" are always 0 (last iteration) + uint32_t res_ret = 0; + uint32_t feedin, t; + for (int i = 0; i <= 31; i++) + { + res_ret |= (filter(s->odd) << (24 ^ i)); //-V629 + feedin = LF_POLY_EVEN & s->even; + feedin ^= LF_POLY_ODD & s->odd; + s->even = s->even << 1 | (evenparity32(feedin)); + t = s->odd, s->odd = s->even, s->even = t; + } + return res_ret; } -static inline void crypt_word_noret(struct Crypto1State* s, uint32_t in, int x) { - uint8_t ret; - uint32_t feedin, t, next_in; - for(int i = 0; i <= 31; i++) { - next_in = BEBIT(in, i); - ret = filter(s->odd); - feedin = ret & (!!x); - feedin ^= LF_POLY_EVEN & s->even; - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= !!next_in; - s->even = s->even << 1 | (evenparity32(feedin)); - t = s->odd, s->odd = s->even, s->even = t; - } - return; +static inline void crypt_word_noret(struct Crypto1State *s, uint32_t in, int x) +{ + uint8_t ret; + uint32_t feedin, t, next_in; + for (int i = 0; i <= 31; i++) + { + next_in = BEBIT(in, i); + ret = filter(s->odd); + feedin = ret & (!!x); + feedin ^= LF_POLY_EVEN & s->even; + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even = s->even << 1 | (evenparity32(feedin)); + t = s->odd, s->odd = s->even, s->even = t; + } + return; } -static inline uint32_t crypt_word_ret(struct Crypto1State* s, uint32_t in, int x) { - uint32_t ret = 0; - uint32_t feedin, t, next_in; - uint8_t next_ret; - for(int i = 0; i <= 31; i++) { - next_in = BEBIT(in, i); - next_ret = filter(s->odd); - feedin = next_ret & (!!x); - feedin ^= LF_POLY_EVEN & s->even; - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= !!next_in; - s->even = s->even << 1 | (evenparity32(feedin)); - t = s->odd, s->odd = s->even, s->even = t; - ret |= next_ret << (24 ^ i); - } - return ret; +static inline uint32_t crypt_word_ret(struct Crypto1State *s, uint32_t in, int x) +{ + uint32_t ret = 0; + uint32_t feedin, t, next_in; + uint8_t next_ret; + for (int i = 0; i <= 31; i++) + { + next_in = BEBIT(in, i); + next_ret = filter(s->odd); + feedin = next_ret & (!!x); + feedin ^= LF_POLY_EVEN & s->even; + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even = s->even << 1 | (evenparity32(feedin)); + t = s->odd, s->odd = s->even, s->even = t; + ret |= next_ret << (24 ^ i); + } + return ret; } -static uint8_t get_nth_byte(uint32_t value, int n) { - if(n < 0 || n > 3) { - // Handle invalid input - return 0; - } - return (value >> (8 * (3 - n))) & 0xFF; +static uint8_t get_nth_byte(uint32_t value, int n) +{ + if (n < 0 || n > 3) + { + // Handle invalid input + return 0; + } + return (value >> (8 * (3 - n))) & 0xFF; } -static uint8_t crypt_bit(struct Crypto1State* s, uint8_t in, int is_encrypted) { - uint32_t feedin, t; - uint8_t ret = filter(s->odd); - feedin = ret & !!is_encrypted; - feedin ^= !!in; - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= LF_POLY_EVEN & s->even; - s->even = s->even << 1 | evenparity32(feedin); - t = s->odd, s->odd = s->even, s->even = t; - return ret; +static uint8_t crypt_bit(struct Crypto1State *s, uint8_t in, int is_encrypted) +{ + uint32_t feedin, t; + uint8_t ret = filter(s->odd); + feedin = ret & !!is_encrypted; + feedin ^= !!in; + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= LF_POLY_EVEN & s->even; + s->even = s->even << 1 | evenparity32(feedin); + t = s->odd, s->odd = s->even, s->even = t; + return ret; } static inline uint32_t crypt_word_par( - struct Crypto1State* s, - uint32_t in, - int is_encrypted, - uint32_t nt_plain, - uint8_t* parity_keystream_bits) { - uint32_t ret = 0; - *parity_keystream_bits = 0; // Reset parity keystream bits + struct Crypto1State *s, + uint32_t in, + int is_encrypted, + uint32_t nt_plain, + uint8_t *parity_keystream_bits) +{ + uint32_t ret = 0; + *parity_keystream_bits = 0; // Reset parity keystream bits - for(int i = 0; i < 32; i++) { - uint8_t bit = crypt_bit(s, BEBIT(in, i), is_encrypted); - ret |= bit << (24 ^ i); - // Save keystream parity bit - if((i + 1) % 8 == 0) { - *parity_keystream_bits |= - (filter(s->odd) ^ nfc_util_even_parity8(get_nth_byte(nt_plain, i / 8))) - << (3 - (i / 8)); - } - } - return ret; + for (int i = 0; i < 32; i++) + { + uint8_t bit = crypt_bit(s, BEBIT(in, i), is_encrypted); + ret |= bit << (24 ^ i); + // Save keystream parity bit + if ((i + 1) % 8 == 0) + { + *parity_keystream_bits |= + (filter(s->odd) ^ nfc_util_even_parity8(get_nth_byte(nt_plain, i / 8))) + << (3 - (i / 8)); + } + } + return ret; } -static inline void rollback_word_noret(struct Crypto1State* s, uint32_t in, int x) { - uint8_t ret; - uint32_t feedin, t, next_in; - for(int i = 31; i >= 0; i--) { - next_in = BEBIT(in, i); - s->odd &= 0xffffff; - t = s->odd, s->odd = s->even, s->even = t; - ret = filter(s->odd); - feedin = ret & (!!x); - feedin ^= s->even & 1; - feedin ^= LF_POLY_EVEN & (s->even >>= 1); - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= !!next_in; - s->even |= (evenparity32(feedin)) << 23; - } - return; +static inline void rollback_word_noret(struct Crypto1State *s, uint32_t in, int x) +{ + uint8_t ret; + uint32_t feedin, t, next_in; + for (int i = 31; i >= 0; i--) + { + next_in = BEBIT(in, i); + s->odd &= 0xffffff; + t = s->odd, s->odd = s->even, s->even = t; + ret = filter(s->odd); + feedin = ret & (!!x); + feedin ^= s->even & 1; + feedin ^= LF_POLY_EVEN & (s->even >>= 1); + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even |= (evenparity32(feedin)) << 23; + } + return; } // TODO: /* uint32_t rollback_word(struct Crypto1State *s, uint32_t in, int x) { - uint32_t res_ret = 0; - uint8_t ret; - uint32_t feedin, t, next_in; - for (int i = 31; i >= 0; i--) { - next_in = BEBIT(in, i); - s->odd &= 0xffffff; - t = s->odd, s->odd = s->even, s->even = t; - ret = filter(s->odd); - feedin = ret & (!!x); - feedin ^= s->even & 1; - feedin ^= LF_POLY_EVEN & (s->even >>= 1); - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= !!next_in; - s->even |= (evenparity32(feedin)) << 23; - res_ret |= (ret << (24 ^ i)); - } - return res_ret; + uint32_t res_ret = 0; + uint8_t ret; + uint32_t feedin, t, next_in; + for (int i = 31; i >= 0; i--) { + next_in = BEBIT(in, i); + s->odd &= 0xffffff; + t = s->odd, s->odd = s->even, s->even = t; + ret = filter(s->odd); + feedin = ret & (!!x); + feedin ^= s->even & 1; + feedin ^= LF_POLY_EVEN & (s->even >>= 1); + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even |= (evenparity32(feedin)) << 23; + res_ret |= (ret << (24 ^ i)); + } + return res_ret; } */ -uint8_t napi_lfsr_rollback_bit(struct Crypto1State* s, uint32_t in, int fb) { - int out; - uint8_t ret; - uint32_t t; - s->odd &= 0xffffff; - t = s->odd, s->odd = s->even, s->even = t; +uint8_t napi_lfsr_rollback_bit(struct Crypto1State *s, uint32_t in, int fb) +{ + int out; + uint8_t ret; + uint32_t t; + s->odd &= 0xffffff; + t = s->odd, s->odd = s->even, s->even = t; - out = s->even & 1; - out ^= LF_POLY_EVEN & (s->even >>= 1); - out ^= LF_POLY_ODD & s->odd; - out ^= !!in; - out ^= (ret = filter(s->odd)) & !!fb; + out = s->even & 1; + out ^= LF_POLY_EVEN & (s->even >>= 1); + out ^= LF_POLY_ODD & s->odd; + out ^= !!in; + out ^= (ret = filter(s->odd)) & !!fb; - s->even |= evenparity32(out) << 23; - return ret; + s->even |= evenparity32(out) << 23; + return ret; } -uint32_t napi_lfsr_rollback_word(struct Crypto1State* s, uint32_t in, int fb) { - int i; - uint32_t ret = 0; - for(i = 31; i >= 0; --i) - ret |= napi_lfsr_rollback_bit(s, BEBIT(in, i), fb) << (i ^ 24); - return ret; +uint32_t napi_lfsr_rollback_word(struct Crypto1State *s, uint32_t in, int fb) +{ + int i; + uint32_t ret = 0; + for (i = 31; i >= 0; --i) + ret |= napi_lfsr_rollback_bit(s, BEBIT(in, i), fb) << (i ^ 24); + return ret; } -static inline uint32_t prng_successor(uint32_t x, uint32_t n) { - SWAPENDIAN(x); - while(n--) - x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31; - return SWAPENDIAN(x); +static inline uint32_t prng_successor(uint32_t x, uint32_t n) +{ + SWAPENDIAN(x); + while (n--) + x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31; + return SWAPENDIAN(x); } -#endif // CRYPTO1_H +#endif // CRYPTO1_H \ No newline at end of file diff --git a/applications/system/mfkey/mfkey.c b/applications/system/mfkey/mfkey.c index ad39d186b..0b68e31e0 100644 --- a/applications/system/mfkey/mfkey.c +++ b/applications/system/mfkey/mfkey.c @@ -37,879 +37,1214 @@ // TODO: Remove defines that are not needed #define KEYS_DICT_SYSTEM_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc") -#define KEYS_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc") -#define MAX_NAME_LEN 32 -#define MAX_PATH_LEN 64 +#define KEYS_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc") +#define MAX_NAME_LEN 32 +#define MAX_PATH_LEN 64 -#define LF_POLY_ODD (0x29CE5C) +#define LF_POLY_ODD (0x29CE5C) #define LF_POLY_EVEN (0x870804) -#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1) -#define CONST_M2_1 (LF_POLY_ODD << 1) -#define CONST_M1_2 (LF_POLY_ODD) -#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1) -#define BIT(x, n) ((x) >> (n) & 1) -#define BEBIT(x, n) BIT(x, (n) ^ 24) +#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1) +#define CONST_M2_1 (LF_POLY_ODD << 1) +#define CONST_M1_2 (LF_POLY_ODD) +#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1) +#define BIT(x, n) ((x) >> (n) & 1) +#define BEBIT(x, n) BIT(x, (n) ^ 24) +#define SWAP(a, b) \ + do \ + { \ + unsigned int t = a; \ + a = b; \ + b = t; \ + } while (0) #define SWAPENDIAN(x) \ - ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) -//#define SIZEOF(arr) sizeof(arr) / sizeof(*arr) + ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) +// #define SIZEOF(arr) sizeof(arr) / sizeof(*arr) -static int eta_round_time = 44; -static int eta_total_time = 705; -// MSB_LIMIT: Chunk size (out of 256) -static int MSB_LIMIT = 16; +// Reduced to 16-bit as these values are small and don't need 32-bit +static int16_t eta_round_time = 44; +static int16_t eta_total_time = 705; +// MSB_LIMIT: Chunk size (out of 256) - can be 8-bit as it's a small value +static uint8_t MSB_LIMIT = 16; static inline int - check_state(struct Crypto1State* t, MfClassicNonce* n, ProgramState* program_state) { - if(!(t->odd | t->even)) return 0; - if(n->attack == mfkey32) { - uint32_t rb = (napi_lfsr_rollback_word(t, 0, 0) ^ n->p64); - if(rb != n->ar0_enc) { - return 0; - } - rollback_word_noret(t, n->nr0_enc, 1); - rollback_word_noret(t, n->uid_xor_nt0, 0); - struct Crypto1State temp = {t->odd, t->even}; - crypt_word_noret(t, n->uid_xor_nt1, 0); - crypt_word_noret(t, n->nr1_enc, 1); - if(n->ar1_enc == (crypt_word(t) ^ n->p64b)) { - crypto1_get_lfsr(&temp, &(n->key)); - return 1; - } - } else if(n->attack == static_nested) { - struct Crypto1State temp = {t->odd, t->even}; - rollback_word_noret(t, n->uid_xor_nt1, 0); - if(n->ks1_1_enc == crypt_word_ret(t, n->uid_xor_nt0, 0)) { - rollback_word_noret(&temp, n->uid_xor_nt1, 0); - crypto1_get_lfsr(&temp, &(n->key)); - return 1; - } - } else if(n->attack == static_encrypted) { - // TODO: Parity bits from rollback_word? - if(n->ks1_1_enc == napi_lfsr_rollback_word(t, n->uid_xor_nt0, 0)) { - // Reduce with parity - uint8_t local_parity_keystream_bits; - struct Crypto1State temp = {t->odd, t->even}; - if((crypt_word_par(&temp, n->uid_xor_nt0, 0, n->nt0, &local_parity_keystream_bits) == - n->ks1_1_enc) && - (local_parity_keystream_bits == n->par_1)) { - // Found key candidate - crypto1_get_lfsr(t, &(n->key)); - program_state->num_candidates++; - keys_dict_add_key(program_state->cuid_dict, n->key.data, sizeof(MfClassicKey)); - } - } - } - return 0; +check_state(struct Crypto1State *t, MfClassicNonce *n, ProgramState *program_state) +{ + if (!(t->odd | t->even)) + return 0; + if (n->attack == mfkey32) + { + uint32_t rb = (napi_lfsr_rollback_word(t, 0, 0) ^ n->p64); + if (rb != n->ar0_enc) + { + return 0; + } + rollback_word_noret(t, n->nr0_enc, 1); + rollback_word_noret(t, n->uid_xor_nt0, 0); + struct Crypto1State temp = {t->odd, t->even}; + crypt_word_noret(t, n->uid_xor_nt1, 0); + crypt_word_noret(t, n->nr1_enc, 1); + if (n->ar1_enc == (crypt_word(t) ^ n->p64b)) + { + crypto1_get_lfsr(&temp, &(n->key)); + return 1; + } + } + else if (n->attack == static_nested) + { + struct Crypto1State temp = {t->odd, t->even}; + rollback_word_noret(t, n->uid_xor_nt1, 0); + if (n->ks1_1_enc == crypt_word_ret(t, n->uid_xor_nt0, 0)) + { + rollback_word_noret(&temp, n->uid_xor_nt1, 0); + crypto1_get_lfsr(&temp, &(n->key)); + return 1; + } + } + else if (n->attack == static_encrypted) + { + // TODO: Parity bits from rollback_word? + if (n->ks1_1_enc == napi_lfsr_rollback_word(t, n->uid_xor_nt0, 0)) + { + // Reduce with parity + uint8_t local_parity_keystream_bits; + struct Crypto1State temp = {t->odd, t->even}; + if ((crypt_word_par(&temp, n->uid_xor_nt0, 0, n->nt0, &local_parity_keystream_bits) == + n->ks1_1_enc) && + (local_parity_keystream_bits == n->par_1)) + { + // Found key candidate + crypto1_get_lfsr(t, &(n->key)); + program_state->num_candidates++; + keys_dict_add_key(program_state->cuid_dict, n->key.data, sizeof(MfClassicKey)); + } + } + } + return 0; } -static inline int state_loop( - unsigned int* states_buffer, - int xks, - int m1, - int m2, - unsigned int in, - uint8_t and_val) { - int states_tail = 0; - int round = 0, s = 0, xks_bit = 0, round_in = 0; +static inline __attribute__((hot)) int state_loop( + unsigned int *states_buffer, + int xks, + int m1, + int m2, + unsigned int in, + int and_val) +{ + int states_tail = 0; + int xks_bit = 0, round_in = 0; - for(round = 1; round <= 12; round++) { - xks_bit = BIT(xks, round); - if(round > 4) { - round_in = ((in >> (2 * (round - 4))) & and_val) << 24; - } + // Unroll first 4 rounds (no round_in calculations needed) + // Hoist the filter() calls to just one iteration and reuse the results + // This avoids redundant calculations and improves performance and gives us 2000b of extra ram (11496b free on run) + // V.28/04. Aprox 3s speedup per round. Total 3 keys 7mins 17s!! + for (int round = 1; round <= 4; ++round) + { + xks_bit = BIT(xks, round); + for (int s = 0; s <= states_tail; ++s) + { + unsigned int v = states_buffer[s] << 1; + states_buffer[s] = v; + int f0 = filter(v); + int f1 = filter(v | 1); - for(s = 0; s <= states_tail; s++) { - states_buffer[s] <<= 1; + if (__builtin_expect((f0 ^ f1) != 0, 0)) + { + states_buffer[s] |= f0 ^ xks_bit; + } + else if (__builtin_expect(f0 == xks_bit, 1)) + { + states_buffer[++states_tail] = states_buffer[++s]; + states_buffer[s] = states_buffer[s - 1] | 1; + } + else + { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + } - if((filter(states_buffer[s]) ^ filter(states_buffer[s] | 1)) != 0) { - states_buffer[s] |= filter(states_buffer[s]) ^ xks_bit; - if(round > 4) { - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= round_in; - } - } else if(filter(states_buffer[s]) == xks_bit) { - // TODO: Refactor - if(round > 4) { - states_buffer[++states_tail] = states_buffer[s + 1]; - states_buffer[s + 1] = states_buffer[s] | 1; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s++] ^= round_in; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= round_in; - } else { - states_buffer[++states_tail] = states_buffer[++s]; - states_buffer[s] = states_buffer[s - 1] | 1; - } - } else { - states_buffer[s--] = states_buffer[states_tail--]; - } - } - } + // Round 5 (unrolled) + { + xks_bit = BIT(xks, 5); + int r5_in = ((in >> 2) & and_val) << 24; // 2*(5-4)=2 + for (int s = 0; s <= states_tail; ++s) + { + unsigned int v = states_buffer[s] << 1; + states_buffer[s] = v; + int f0 = filter(v), f1 = filter(v | 1); + if (__builtin_expect((f0 ^ f1) != 0, 0)) + { + states_buffer[s] |= f0 ^ xks_bit; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= r5_in; + } + else if (__builtin_expect(f0 == xks_bit, 1)) + { + states_buffer[++states_tail] = states_buffer[s + 1]; + states_buffer[s + 1] = v | 1; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s++] ^= r5_in; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= r5_in; + } + else + { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + } - return states_tail; + // Round 6 (unrolled) + { + xks_bit = BIT(xks, 6); + int r6_in = ((in >> 4) & and_val) << 24; // 2*(6-4)=4 + for (int s = 0; s <= states_tail; ++s) + { + unsigned int v = states_buffer[s] << 1; + states_buffer[s] = v; + int f0 = filter(v), f1 = filter(v | 1); + if (__builtin_expect((f0 ^ f1) != 0, 0)) + { + states_buffer[s] |= f0 ^ xks_bit; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= r6_in; + } + else if (__builtin_expect(f0 == xks_bit, 1)) + { + states_buffer[++states_tail] = states_buffer[s + 1]; + states_buffer[s + 1] = v | 1; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s++] ^= r6_in; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= r6_in; + } + else + { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + } + + // Loop rounds 7–12 + for (int round = 7; round <= 12; ++round) + { + xks_bit = BIT(xks, round); + round_in = ((in >> (2 * (round - 4))) & and_val) << 24; + for (int s = 0; s <= states_tail; ++s) + { + unsigned int v = states_buffer[s] << 1; + states_buffer[s] = v; + int f0 = filter(v), f1 = filter(v | 1); + if (__builtin_expect((f0 ^ f1) != 0, 0)) + { + states_buffer[s] |= f0 ^ xks_bit; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= round_in; + } + else if (__builtin_expect(f0 == xks_bit, 1)) + { + states_buffer[++states_tail] = states_buffer[s + 1]; + states_buffer[s + 1] = v | 1; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s++] ^= round_in; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= round_in; + } + else + { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + } + + return states_tail; } -int binsearch(unsigned int data[], int start, int stop) { - int mid, val = data[stop] & 0xff000000; - while(start != stop) { - mid = (stop - start) >> 1; - if((data[start + mid] ^ 0x80000000) > (val ^ 0x80000000)) - stop = start + mid; - else - start += mid + 1; - } - return start; +int binsearch(unsigned int data[], int start, int stop) +{ + int mid, val = data[stop] & 0xff000000; + while (start != stop) + { + mid = (stop - start) >> 1; + if ((data[start + mid] ^ 0x80000000) > (val ^ 0x80000000)) + stop = start + mid; + else + start += mid + 1; + } + return start; } -void quicksort(unsigned int array[], int low, int high) { - //if (SIZEOF(array) == 0) - // return; - if(low >= high) return; - int middle = low + (high - low) / 2; - unsigned int pivot = array[middle]; - int i = low, j = high; - while(i <= j) { - while(array[i] < pivot) { - i++; - } - while(array[j] > pivot) { - j--; - } - if(i <= j) { // swap - int temp = array[i]; - array[i] = array[j]; - array[j] = temp; - i++; - j--; - } - } - if(low < j) { - quicksort(array, low, j); - } - if(high > i) { - quicksort(array, i, high); - } + +void quicksort(unsigned int array[], int low, int high) +{ + // Use insertion sort for small arrays (threshold determined by testing) + if (high - low < 16) + { + // Insertion sort + for (int i = low + 1; i <= high; i++) + { + unsigned int key = array[i]; + int j = i - 1; + while (j >= low && array[j] > key) + { + array[j + 1] = array[j]; + j--; + } + array[j + 1] = key; + } + return; + } + + if (low >= high) + return; + + // Median-of-three pivot selection + int middle = low + (high - low) / 2; + if (array[middle] < array[low]) + SWAP(array[middle], array[low]); + if (array[high] < array[low]) + SWAP(array[high], array[low]); + if (array[high] < array[middle]) + SWAP(array[high], array[middle]); + + unsigned int pivot = array[middle]; + + // Rest of quicksort with improved partitioning + int i = low, j = high; + while (i <= j) + { + while (array[i] < pivot) + i++; + while (array[j] > pivot) + j--; + if (i <= j) + { + // swap + unsigned int temp = array[i]; + array[i] = array[j]; + array[j] = temp; + i++; + j--; + } + } + + if (low < j) + quicksort(array, low, j); + if (high > i) + quicksort(array, i, high); } -int extend_table(unsigned int data[], int tbl, int end, int bit, int m1, int m2, unsigned int in) { - in <<= 24; - for(data[tbl] <<= 1; tbl <= end; data[++tbl] <<= 1) { - if((filter(data[tbl]) ^ filter(data[tbl] | 1)) != 0) { - data[tbl] |= filter(data[tbl]) ^ bit; - update_contribution(data, tbl, m1, m2); - data[tbl] ^= in; - } else if(filter(data[tbl]) == bit) { - data[++end] = data[tbl + 1]; - data[tbl + 1] = data[tbl] | 1; - update_contribution(data, tbl, m1, m2); - data[tbl++] ^= in; - update_contribution(data, tbl, m1, m2); - data[tbl] ^= in; - } else { - data[tbl--] = data[end--]; - } - } - return end; + +int extend_table(unsigned int data[], int tbl, int end, int bit, int m1, int m2, unsigned int in) +{ + in <<= 24; + for (data[tbl] <<= 1; tbl <= end; data[++tbl] <<= 1) + { + if ((filter(data[tbl]) ^ filter(data[tbl] | 1)) != 0) + { + data[tbl] |= filter(data[tbl]) ^ bit; + update_contribution(data, tbl, m1, m2); + data[tbl] ^= in; + } + else if (filter(data[tbl]) == bit) + { + data[++end] = data[tbl + 1]; + data[tbl + 1] = data[tbl] | 1; + update_contribution(data, tbl, m1, m2); + data[tbl++] ^= in; + update_contribution(data, tbl, m1, m2); + data[tbl] ^= in; + } + else + { + data[tbl--] = data[end--]; + } + } + return end; } int old_recover( - unsigned int odd[], - int o_head, - int o_tail, - int oks, - unsigned int even[], - int e_head, - int e_tail, - int eks, - int rem, - int s, - MfClassicNonce* n, - unsigned int in, - int first_run, - ProgramState* program_state) { - int o, e, i; - if(rem == -1) { - for(e = e_head; e <= e_tail; ++e) { - even[e] = (even[e] << 1) ^ evenparity32(even[e] & LF_POLY_EVEN) ^ (!!(in & 4)); - for(o = o_head; o <= o_tail; ++o, ++s) { - struct Crypto1State temp = {0, 0}; - temp.even = odd[o]; - temp.odd = even[e] ^ evenparity32(odd[o] & LF_POLY_ODD); - if(check_state(&temp, n, program_state)) { - return -1; - } - } - } - return s; - } - if(first_run == 0) { - for(i = 0; (i < 4) && (rem-- != 0); i++) { - oks >>= 1; - eks >>= 1; - in >>= 2; - o_tail = extend_table( - odd, o_head, o_tail, oks & 1, LF_POLY_EVEN << 1 | 1, LF_POLY_ODD << 1, 0); - if(o_head > o_tail) return s; - e_tail = extend_table( - even, e_head, e_tail, eks & 1, LF_POLY_ODD, LF_POLY_EVEN << 1 | 1, in & 3); - if(e_head > e_tail) return s; - } - } - first_run = 0; - quicksort(odd, o_head, o_tail); - quicksort(even, e_head, e_tail); - while(o_tail >= o_head && e_tail >= e_head) { - if(((odd[o_tail] ^ even[e_tail]) >> 24) == 0) { - o_tail = binsearch(odd, o_head, o = o_tail); - e_tail = binsearch(even, e_head, e = e_tail); - s = old_recover( - odd, - o_tail--, - o, - oks, - even, - e_tail--, - e, - eks, - rem, - s, - n, - in, - first_run, - program_state); - if(s == -1) { - break; - } - } else if((odd[o_tail] ^ 0x80000000) > (even[e_tail] ^ 0x80000000)) { - o_tail = binsearch(odd, o_head, o_tail) - 1; - } else { - e_tail = binsearch(even, e_head, e_tail) - 1; - } - } - return s; + unsigned int odd[], + int o_head, + int o_tail, + int oks, + unsigned int even[], + int e_head, + int e_tail, + int eks, + int rem, + int s, + MfClassicNonce *n, + unsigned int in, + int first_run, + ProgramState *program_state) +{ + int o, e, i; + if (rem == -1) + { + for (e = e_head; e <= e_tail; ++e) + { + even[e] = (even[e] << 1) ^ evenparity32(even[e] & LF_POLY_EVEN) ^ (!!(in & 4)); + for (o = o_head; o <= o_tail; ++o, ++s) + { + struct Crypto1State temp = {0, 0}; + temp.even = odd[o]; + temp.odd = even[e] ^ evenparity32(odd[o] & LF_POLY_ODD); + if (check_state(&temp, n, program_state)) + { + return -1; + } + } + } + return s; + } + if (first_run == 0) + { + for (i = 0; (i < 4) && (rem-- != 0); i++) + { + oks >>= 1; + eks >>= 1; + in >>= 2; + o_tail = extend_table( + odd, o_head, o_tail, oks & 1, LF_POLY_EVEN << 1 | 1, LF_POLY_ODD << 1, 0); + if (o_head > o_tail) + return s; + e_tail = extend_table( + even, e_head, e_tail, eks & 1, LF_POLY_ODD, LF_POLY_EVEN << 1 | 1, in & 3); + if (e_head > e_tail) + return s; + } + } + first_run = 0; + quicksort(odd, o_head, o_tail); + quicksort(even, e_head, e_tail); + while (o_tail >= o_head && e_tail >= e_head) + { + if (((odd[o_tail] ^ even[e_tail]) >> 24) == 0) + { + o_tail = binsearch(odd, o_head, o = o_tail); + e_tail = binsearch(even, e_head, e = e_tail); + s = old_recover( + odd, + o_tail--, + o, + oks, + even, + e_tail--, + e, + eks, + rem, + s, + n, + in, + first_run, + program_state); + if (s == -1) + { + break; + } + } + else if ((odd[o_tail] ^ 0x80000000) > (even[e_tail] ^ 0x80000000)) + { + o_tail = binsearch(odd, o_head, o_tail) - 1; + } + else + { + e_tail = binsearch(even, e_head, e_tail) - 1; + } + } + return s; } -static inline int sync_state(ProgramState* program_state) { - int ts = furi_hal_rtc_get_timestamp(); - int elapsed_time = ts - program_state->eta_timestamp; - if(elapsed_time < program_state->eta_round) { - program_state->eta_round -= elapsed_time; - } else { - program_state->eta_round = 0; - } - if(elapsed_time < program_state->eta_total) { - program_state->eta_total -= elapsed_time; - } else { - program_state->eta_total = 0; - } - program_state->eta_timestamp = ts; - if(program_state->close_thread_please) { - return 1; - } - return 0; +static inline int sync_state(ProgramState *program_state) +{ + int ts = furi_hal_rtc_get_timestamp(); + int elapsed_time = ts - program_state->eta_timestamp; + if (elapsed_time < program_state->eta_round) + { + program_state->eta_round -= elapsed_time; + } + else + { + program_state->eta_round = 0; + } + if (elapsed_time < program_state->eta_total) + { + program_state->eta_total -= elapsed_time; + } + else + { + program_state->eta_total = 0; + } + program_state->eta_timestamp = ts; + if (program_state->close_thread_please) + { + return 1; + } + return 0; } int calculate_msb_tables( - int oks, - int eks, - int msb_round, - MfClassicNonce* n, - unsigned int* states_buffer, - struct Msb* odd_msbs, - struct Msb* even_msbs, - unsigned int* temp_states_odd, - unsigned int* temp_states_even, - unsigned int in, - ProgramState* program_state) { - //FURI_LOG_I(TAG, "MSB GO %i", msb_iter); // DEBUG - unsigned int msb_head = (MSB_LIMIT * msb_round); // msb_iter ranges from 0 to (256/MSB_LIMIT)-1 - unsigned int msb_tail = (MSB_LIMIT * (msb_round + 1)); - int states_tail = 0, tail = 0; - int i = 0, j = 0, semi_state = 0, found = 0; - unsigned int msb = 0; - in = ((in >> 16 & 0xff) | (in << 16) | (in & 0xff00)) << 1; - // TODO: Why is this necessary? - memset(odd_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); - memset(even_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); + int oks, + int eks, + int msb_round, + MfClassicNonce *n, + unsigned int *states_buffer, + struct Msb *odd_msbs, + struct Msb *even_msbs, + unsigned int *temp_states_odd, + unsigned int *temp_states_even, + unsigned int in, + ProgramState *program_state) +{ + unsigned int msb_head = (MSB_LIMIT * msb_round); + unsigned int msb_tail = (MSB_LIMIT * (msb_round + 1)); + int states_tail = 0; + int semi_state = 0; + unsigned int msb = 0; - for(semi_state = 1 << 20; semi_state >= 0; semi_state--) { - if(semi_state % 32768 == 0) { - if(sync_state(program_state) == 1) { - return 0; - } - } + // Preprocessed in value + in = ((in >> 16 & 0xff) | (in << 16) | (in & 0xff00)) << 1; - if(filter(semi_state) == (oks & 1)) { //-V547 - states_buffer[0] = semi_state; - states_tail = state_loop(states_buffer, oks, CONST_M1_1, CONST_M2_1, 0, 0); + // Clear MSB arrays once before loop instead of inside loop + memset(odd_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); + memset(even_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); - for(i = states_tail; i >= 0; i--) { - msb = states_buffer[i] >> 24; - if((msb >= msb_head) && (msb < msb_tail)) { - found = 0; - for(j = 0; j < odd_msbs[msb - msb_head].tail - 1; j++) { - if(odd_msbs[msb - msb_head].states[j] == states_buffer[i]) { - found = 1; - break; - } - } + // Bit values to check - calculate once outside the loop + int oks_bit = oks & 1; + int eks_bit = eks & 1; - if(!found) { - tail = odd_msbs[msb - msb_head].tail++; - odd_msbs[msb - msb_head].states[tail] = states_buffer[i]; - } - } - } - } + // Check for stop request less frequently + int sync_check_interval = 32768 * 2; // Doubled the interval - if(filter(semi_state) == (eks & 1)) { //-V547 - states_buffer[0] = semi_state; - states_tail = state_loop(states_buffer, eks, CONST_M1_2, CONST_M2_2, in, 3); + for (semi_state = 1 << 20; semi_state >= 0; semi_state--) + { + if (semi_state % sync_check_interval == 0) + { + if (sync_state(program_state) == 1) + { + return 0; + } + } - for(i = 0; i <= states_tail; i++) { - msb = states_buffer[i] >> 24; - if((msb >= msb_head) && (msb < msb_tail)) { - found = 0; + // Process both filter conditions in one pass when possible + int filter_semi_state = filter(semi_state); - for(j = 0; j < even_msbs[msb - msb_head].tail; j++) { - if(even_msbs[msb - msb_head].states[j] == states_buffer[i]) { - found = 1; - break; - } - } + // Check oks condition + if (filter_semi_state == oks_bit) + { + states_buffer[0] = semi_state; + states_tail = state_loop(states_buffer, oks, CONST_M1_1, CONST_M2_1, 0, 0); - if(!found) { - tail = even_msbs[msb - msb_head].tail++; - even_msbs[msb - msb_head].states[tail] = states_buffer[i]; - } - } - } - } - } + for (int i = states_tail; i >= 0; i--) + { + msb = states_buffer[i] >> 24; + if ((msb >= msb_head) && (msb < msb_tail)) + { + // Calculate index once + int msb_idx = msb - msb_head; - oks >>= 12; - eks >>= 12; + // Avoid sequential scan by using a direct flag + int found = 0; + for (int j = 0; j < odd_msbs[msb_idx].tail; j++) + { + if (odd_msbs[msb_idx].states[j] == states_buffer[i]) + { + found = 1; + break; + } + } - for(i = 0; i < MSB_LIMIT; i++) { - if(sync_state(program_state) == 1) { - return 0; - } - // TODO: Why is this necessary? - memset(temp_states_even, 0, sizeof(unsigned int) * (1280)); - memset(temp_states_odd, 0, sizeof(unsigned int) * (1280)); - memcpy(temp_states_odd, odd_msbs[i].states, odd_msbs[i].tail * sizeof(unsigned int)); - memcpy(temp_states_even, even_msbs[i].states, even_msbs[i].tail * sizeof(unsigned int)); - int res = old_recover( - temp_states_odd, - 0, - odd_msbs[i].tail, - oks, - temp_states_even, - 0, - even_msbs[i].tail, - eks, - 3, - 0, - n, - in >> 16, - 1, - program_state); - if(res == -1) { - return 1; - } - //odd_msbs[i].tail = 0; - //even_msbs[i].tail = 0; - } + if (!found) + { + int tail = odd_msbs[msb_idx].tail++; + odd_msbs[msb_idx].states[tail] = states_buffer[i]; + } + } + } + } - return 0; + // Check eks condition + if (filter_semi_state == eks_bit) + { + states_buffer[0] = semi_state; + states_tail = state_loop(states_buffer, eks, CONST_M1_2, CONST_M2_2, in, 3); + + for (int i = 0; i <= states_tail; i++) + { + msb = states_buffer[i] >> 24; + if ((msb >= msb_head) && (msb < msb_tail)) + { + // Calculate index once + int msb_idx = msb - msb_head; + + // Avoid sequential scan + int found = 0; + for (int j = 0; j < even_msbs[msb_idx].tail; j++) + { + if (even_msbs[msb_idx].states[j] == states_buffer[i]) + { + found = 1; + break; + } + } + + if (!found) + { + int tail = even_msbs[msb_idx].tail++; + even_msbs[msb_idx].states[tail] = states_buffer[i]; + } + } + } + } + } + + // Shift once outside the loop + oks >>= 12; + eks >>= 12; + + // Process results + for (int i = 0; i < MSB_LIMIT; i++) + { + if ((i % 4) == 0 && sync_state(program_state) == 1) + { + return 0; + } + + // Only clear buffers if they're going to be used + if (odd_msbs[i].tail > 0 || even_msbs[i].tail > 0) + { + memset(temp_states_even, 0, sizeof(unsigned int) * (1280)); + memset(temp_states_odd, 0, sizeof(unsigned int) * (1280)); + memcpy(temp_states_odd, odd_msbs[i].states, odd_msbs[i].tail * sizeof(unsigned int)); + memcpy(temp_states_even, even_msbs[i].states, even_msbs[i].tail * sizeof(unsigned int)); + + int res = old_recover( + temp_states_odd, + 0, + odd_msbs[i].tail, + oks, + temp_states_even, + 0, + even_msbs[i].tail, + eks, + 3, + 0, + n, + in >> 16, + 1, + program_state); + + if (res == -1) + { + return 1; + } + } + } + + return 0; } -void** allocate_blocks(const size_t* block_sizes, int num_blocks) { - void** block_pointers = malloc(num_blocks * sizeof(void*)); +void **allocate_blocks(const size_t *block_sizes, int num_blocks) +{ + void **block_pointers = malloc(num_blocks * sizeof(void *)); + if (!block_pointers) + { + return NULL; + } - for(int i = 0; i < num_blocks; i++) { - if(memmgr_heap_get_max_free_block() < block_sizes[i]) { - // Not enough memory, free previously allocated blocks - for(int j = 0; j < i; j++) { - free(block_pointers[j]); - } - free(block_pointers); - return NULL; - } + for (int i = 0; i < num_blocks; i++) + { + if (memmgr_heap_get_max_free_block() < block_sizes[i]) + { + // Not enough memory, free previously allocated blocks + for (int j = 0; j < i; j++) + { + free(block_pointers[j]); + } + free(block_pointers); + return NULL; + } - block_pointers[i] = malloc(block_sizes[i]); - } + block_pointers[i] = malloc(block_sizes[i]); + if (!block_pointers[i]) + { + // Allocation failed + for (int j = 0; j < i; j++) + { + free(block_pointers[j]); + } + free(block_pointers); + return NULL; + } + } - return block_pointers; + return block_pointers; } -bool is_full_speed() { - return MSB_LIMIT == 16; +// Inline function for checking if we're at full speed +static inline bool is_full_speed() +{ + return MSB_LIMIT == 16; } -bool recover(MfClassicNonce* n, int ks2, unsigned int in, ProgramState* program_state) { - bool found = false; - const size_t block_sizes[] = {49216, 49216, 5120, 5120, 4096}; - const size_t reduced_block_sizes[] = {24608, 24608, 5120, 5120, 4096}; - const int num_blocks = sizeof(block_sizes) / sizeof(block_sizes[0]); - void** block_pointers = allocate_blocks(block_sizes, num_blocks); - if(block_pointers == NULL) { - // System has less than the guaranteed amount of RAM (140 KB) - adjust some parameters to run anyway at half speed - if(is_full_speed()) { - //eta_round_time *= 2; - eta_total_time *= 2; - MSB_LIMIT /= 2; - } - block_pointers = allocate_blocks(reduced_block_sizes, num_blocks); - if(block_pointers == NULL) { - // System has less than 70 KB of RAM - should never happen so we don't reduce speed further - program_state->err = InsufficientRAM; - program_state->mfkey_state = Error; - return false; - } - } - // Adjust estimates for static encrypted attacks - if(n->attack == static_encrypted) { - eta_round_time *= 4; - eta_total_time *= 4; - if(is_full_speed()) { - eta_round_time *= 4; - eta_total_time *= 4; - } - } - struct Msb* odd_msbs = block_pointers[0]; - struct Msb* even_msbs = block_pointers[1]; - unsigned int* temp_states_odd = block_pointers[2]; - unsigned int* temp_states_even = block_pointers[3]; - unsigned int* states_buffer = block_pointers[4]; - int oks = 0, eks = 0; - int i = 0, msb = 0; - for(i = 31; i >= 0; i -= 2) { - oks = oks << 1 | BEBIT(ks2, i); - } - for(i = 30; i >= 0; i -= 2) { - eks = eks << 1 | BEBIT(ks2, i); - } - int bench_start = furi_hal_rtc_get_timestamp(); - program_state->eta_total = eta_total_time; - program_state->eta_timestamp = bench_start; - for(msb = 0; msb <= ((256 / MSB_LIMIT) - 1); msb++) { - program_state->search = msb; - program_state->eta_round = eta_round_time; - program_state->eta_total = eta_total_time - (eta_round_time * msb); - if(calculate_msb_tables( - oks, - eks, - msb, - n, - states_buffer, - odd_msbs, - even_msbs, - temp_states_odd, - temp_states_even, - in, - program_state)) { - //int bench_stop = furi_hal_rtc_get_timestamp(); - //FURI_LOG_I(TAG, "Cracked in %i seconds", bench_stop - bench_start); - found = true; - break; - } - if(program_state->close_thread_please) { - break; - } - } - // Free the allocated blocks - for(int i = 0; i < num_blocks; i++) { - free(block_pointers[i]); - } - free(block_pointers); - return found; +bool recover(MfClassicNonce *n, int ks2, unsigned int in, ProgramState *program_state) +{ + bool found = false; + const size_t block_sizes[] = {49216, 49216, 5120, 5120, 4096}; + const size_t reduced_block_sizes[] = {24608, 24608, 5120, 5120, 4096}; + const int num_blocks = sizeof(block_sizes) / sizeof(block_sizes[0]); + void **block_pointers = allocate_blocks(block_sizes, num_blocks); + if (block_pointers == NULL) + { + // System has less than the guaranteed amount of RAM (140 KB) - adjust some parameters to run anyway at half speed + if (is_full_speed()) + { + // eta_round_time *= 2; + eta_total_time *= 2; + MSB_LIMIT /= 2; + } + block_pointers = allocate_blocks(reduced_block_sizes, num_blocks); + if (block_pointers == NULL) + { + // System has less than 70 KB of RAM - should never happen so we don't reduce speed further + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } + // Adjust estimates for static encrypted attacks + if (n->attack == static_encrypted) + { + eta_round_time *= 4; + eta_total_time *= 4; + if (is_full_speed()) + { + eta_round_time *= 4; + eta_total_time *= 4; + } + } + struct Msb *odd_msbs = block_pointers[0]; + struct Msb *even_msbs = block_pointers[1]; + unsigned int *temp_states_odd = block_pointers[2]; + unsigned int *temp_states_even = block_pointers[3]; + unsigned int *states_buffer = block_pointers[4]; + int oks = 0, eks = 0; + int i = 0, msb = 0; + for (i = 31; i >= 0; i -= 2) + { + oks = oks << 1 | BEBIT(ks2, i); + } + for (i = 30; i >= 0; i -= 2) + { + eks = eks << 1 | BEBIT(ks2, i); + } + int bench_start = furi_hal_rtc_get_timestamp(); + program_state->eta_total = eta_total_time; + program_state->eta_timestamp = bench_start; + for (msb = 0; msb <= ((256 / MSB_LIMIT) - 1); msb++) + { + program_state->search = msb; + program_state->eta_round = eta_round_time; + program_state->eta_total = eta_total_time - (eta_round_time * msb); + if (calculate_msb_tables( + oks, + eks, + msb, + n, + states_buffer, + odd_msbs, + even_msbs, + temp_states_odd, + temp_states_even, + in, + program_state)) + { + // int bench_stop = furi_hal_rtc_get_timestamp(); + // FURI_LOG_I(TAG, "Cracked in %i seconds", bench_stop - bench_start); + found = true; + break; + } + if (program_state->close_thread_please) + { + break; + } + } + // Free the allocated blocks + for (int i = 0; i < num_blocks; i++) + { + free(block_pointers[i]); + } + free(block_pointers); + return found; } bool key_already_found_for_nonce_in_solved( - MfClassicKey* keyarray, - int keyarray_size, - MfClassicNonce* nonce) { - for(int k = 0; k < keyarray_size; k++) { - uint64_t key_as_int = bit_lib_bytes_to_num_be(keyarray[k].data, sizeof(MfClassicKey)); - struct Crypto1State temp = {0, 0}; - for(int i = 0; i < 24; i++) { - (&temp)->odd |= (BIT(key_as_int, 2 * i + 1) << (i ^ 3)); - (&temp)->even |= (BIT(key_as_int, 2 * i) << (i ^ 3)); - } - if(nonce->attack == mfkey32) { - crypt_word_noret(&temp, nonce->uid_xor_nt1, 0); - crypt_word_noret(&temp, nonce->nr1_enc, 1); - if(nonce->ar1_enc == (crypt_word(&temp) ^ nonce->p64b)) { - return true; - } - } else if(nonce->attack == static_nested) { - uint32_t expected_ks1 = crypt_word_ret(&temp, nonce->uid_xor_nt0, 0); - if(nonce->ks1_1_enc == expected_ks1) { - return true; - } - } - } - return false; + MfClassicKey *keyarray, + int keyarray_size, + MfClassicNonce *nonce) +{ + for (int k = 0; k < keyarray_size; k++) + { + uint64_t key_as_int = bit_lib_bytes_to_num_be(keyarray[k].data, sizeof(MfClassicKey)); + struct Crypto1State temp = {0, 0}; + for (int i = 0; i < 24; i++) + { + (&temp)->odd |= (BIT(key_as_int, 2 * i + 1) << (i ^ 3)); + (&temp)->even |= (BIT(key_as_int, 2 * i) << (i ^ 3)); + } + if (nonce->attack == mfkey32) + { + crypt_word_noret(&temp, nonce->uid_xor_nt1, 0); + crypt_word_noret(&temp, nonce->nr1_enc, 1); + if (nonce->ar1_enc == (crypt_word(&temp) ^ nonce->p64b)) + { + return true; + } + } + else if (nonce->attack == static_nested) + { + uint32_t expected_ks1 = crypt_word_ret(&temp, nonce->uid_xor_nt0, 0); + if (nonce->ks1_1_enc == expected_ks1) + { + return true; + } + } + } + return false; } #pragma GCC push_options #pragma GCC optimize("Os") -static void finished_beep() { - // Beep to indicate completion - NotificationApp* notification = furi_record_open("notification"); - notification_message(notification, &sequence_audiovisual_alert); - notification_message(notification, &sequence_display_backlight_on); - furi_record_close("notification"); +static void finished_beep() +{ + // Beep to indicate completion + NotificationApp *notification = furi_record_open("notification"); + notification_message(notification, &sequence_audiovisual_alert); + notification_message(notification, &sequence_display_backlight_on); + furi_record_close("notification"); } -void mfkey(ProgramState* program_state) { - uint32_t ks_enc = 0, nt_xor_uid = 0; - MfClassicKey found_key; // Recovered key - size_t keyarray_size = 0; - MfClassicKey* keyarray = malloc(sizeof(MfClassicKey) * 1); - uint32_t i = 0, j = 0; - //FURI_LOG_I(TAG, "Free heap before alloc(): %zub", memmgr_get_free_heap()); - Storage* storage = furi_record_open(RECORD_STORAGE); - FlipperApplication* app = flipper_application_alloc(storage, firmware_api_interface); - flipper_application_preload(app, APP_ASSETS_PATH("plugins/mfkey_init_plugin.fal")); - flipper_application_map_to_memory(app); - const FlipperAppPluginDescriptor* app_descriptor = - flipper_application_plugin_get_descriptor(app); - const MfkeyPlugin* init_plugin = app_descriptor->entry_point; - // Check for nonces - program_state->mfkey32_present = init_plugin->napi_mf_classic_mfkey32_nonces_check_presence(); - program_state->nested_present = init_plugin->napi_mf_classic_nested_nonces_check_presence(); - if(!(program_state->mfkey32_present) && !(program_state->nested_present)) { - program_state->err = MissingNonces; - program_state->mfkey_state = Error; - flipper_application_free(app); - furi_record_close(RECORD_STORAGE); - free(keyarray); - return; - } - // Read dictionaries (optional) - KeysDict* system_dict = {0}; - bool system_dict_exists = keys_dict_check_presence(KEYS_DICT_SYSTEM_PATH); - KeysDict* user_dict = {0}; - bool user_dict_exists = keys_dict_check_presence(KEYS_DICT_USER_PATH); - uint32_t total_dict_keys = 0; - if(system_dict_exists) { - system_dict = - keys_dict_alloc(KEYS_DICT_SYSTEM_PATH, KeysDictModeOpenExisting, sizeof(MfClassicKey)); - total_dict_keys += keys_dict_get_total_keys(system_dict); - } - user_dict = keys_dict_alloc(KEYS_DICT_USER_PATH, KeysDictModeOpenAlways, sizeof(MfClassicKey)); - if(user_dict_exists) { - total_dict_keys += keys_dict_get_total_keys(user_dict); - } - user_dict_exists = true; - program_state->dict_count = total_dict_keys; - program_state->mfkey_state = DictionaryAttack; - // Read nonces - MfClassicNonceArray* nonce_arr; - nonce_arr = init_plugin->napi_mf_classic_nonce_array_alloc( - system_dict, system_dict_exists, user_dict, program_state); - if(system_dict_exists) { - keys_dict_free(system_dict); - } - if(nonce_arr->total_nonces == 0) { - // Nothing to crack - program_state->err = ZeroNonces; - program_state->mfkey_state = Error; - init_plugin->napi_mf_classic_nonce_array_free(nonce_arr); - flipper_application_free(app); - furi_record_close(RECORD_STORAGE); - keys_dict_free(user_dict); - free(keyarray); - return; - } - flipper_application_free(app); - furi_record_close(RECORD_STORAGE); - // TODO: Track free state at the time this is called to ensure double free does not happen - furi_assert(nonce_arr); - furi_assert(nonce_arr->stream); - // TODO: Already closed? - buffered_file_stream_close(nonce_arr->stream); - stream_free(nonce_arr->stream); - //FURI_LOG_I(TAG, "Free heap after free(): %zub", memmgr_get_free_heap()); - program_state->mfkey_state = MFKeyAttack; - // TODO: Work backwards on this array and free memory - for(i = 0; i < nonce_arr->total_nonces; i++) { - MfClassicNonce next_nonce = nonce_arr->remaining_nonce_array[i]; - if(key_already_found_for_nonce_in_solved(keyarray, keyarray_size, &next_nonce)) { - nonce_arr->remaining_nonces--; - (program_state->cracked)++; - (program_state->num_completed)++; - continue; - } - //FURI_LOG_I(TAG, "Beginning recovery for %8lx", next_nonce.uid); - FuriString* cuid_dict_path; - switch(next_nonce.attack) { - case mfkey32: - ks_enc = next_nonce.ar0_enc ^ next_nonce.p64; - nt_xor_uid = 0; - break; - case static_nested: - ks_enc = next_nonce.ks1_2_enc; - nt_xor_uid = next_nonce.uid_xor_nt1; - break; - case static_encrypted: - ks_enc = next_nonce.ks1_1_enc; - nt_xor_uid = next_nonce.uid_xor_nt0; - cuid_dict_path = furi_string_alloc_printf( - "%s/mf_classic_dict_%08lx.nfc", EXT_PATH("nfc/assets"), next_nonce.uid); - // May need RECORD_STORAGE? - program_state->cuid_dict = keys_dict_alloc( - furi_string_get_cstr(cuid_dict_path), - KeysDictModeOpenAlways, - sizeof(MfClassicKey)); - break; - } +void mfkey(ProgramState *program_state) +{ + uint32_t ks_enc = 0, nt_xor_uid = 0; + MfClassicKey found_key; // Recovered key + size_t keyarray_size = 0; + MfClassicKey *keyarray = malloc(sizeof(MfClassicKey) * 1); + if (!keyarray) + { + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return; + } - if(!recover(&next_nonce, ks_enc, nt_xor_uid, program_state)) { - if((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) { - keys_dict_free(program_state->cuid_dict); - } - if(program_state->close_thread_please) { - break; - } - // No key found in recover() or static encrypted - (program_state->num_completed)++; - continue; - } - (program_state->cracked)++; - (program_state->num_completed)++; - found_key = next_nonce.key; - bool already_found = false; - for(j = 0; j < keyarray_size; j++) { - if(memcmp(keyarray[j].data, found_key.data, MF_CLASSIC_KEY_SIZE) == 0) { - already_found = true; - break; - } - } - if(already_found == false) { - // New key - keyarray = realloc(keyarray, sizeof(MfClassicKey) * (keyarray_size + 1)); //-V701 - keyarray_size += 1; - keyarray[keyarray_size - 1] = found_key; - (program_state->unique_cracked)++; - } - } - // TODO: Update display to show all keys were found - // TODO: Prepend found key(s) to user dictionary file - //FURI_LOG_I(TAG, "Unique keys found:"); - for(i = 0; i < keyarray_size; i++) { - //FURI_LOG_I(TAG, "%012" PRIx64, keyarray[i]); - keys_dict_add_key(user_dict, keyarray[i].data, sizeof(MfClassicKey)); - } - if(keyarray_size > 0) { - dolphin_deed(DolphinDeedNfcKeyAdd); - } - free(nonce_arr); - keys_dict_free(user_dict); - free(keyarray); - if(program_state->mfkey_state == Error) { - return; - } - //FURI_LOG_I(TAG, "mfkey function completed normally"); // DEBUG - program_state->mfkey_state = Complete; - // No need to alert the user if they asked it to stop - if(!(program_state->close_thread_please)) { - finished_beep(); - } - return; + uint32_t i = 0, j = 0; + // FURI_LOG_I(TAG, "Free heap before alloc(): %zub", memmgr_get_free_heap()); + Storage *storage = furi_record_open(RECORD_STORAGE); + FlipperApplication *app = flipper_application_alloc(storage, firmware_api_interface); + flipper_application_preload(app, APP_ASSETS_PATH("plugins/mfkey_init_plugin.fal")); + flipper_application_map_to_memory(app); + const FlipperAppPluginDescriptor *app_descriptor = + flipper_application_plugin_get_descriptor(app); + const MfkeyPlugin *init_plugin = app_descriptor->entry_point; + // Check for nonces + program_state->mfkey32_present = init_plugin->napi_mf_classic_mfkey32_nonces_check_presence(); + program_state->nested_present = init_plugin->napi_mf_classic_nested_nonces_check_presence(); + if (!(program_state->mfkey32_present) && !(program_state->nested_present)) + { + program_state->err = MissingNonces; + program_state->mfkey_state = Error; + flipper_application_free(app); + furi_record_close(RECORD_STORAGE); + free(keyarray); + return; + } + // Read dictionaries (optional) + KeysDict *system_dict = {0}; + bool system_dict_exists = keys_dict_check_presence(KEYS_DICT_SYSTEM_PATH); + KeysDict *user_dict = {0}; + bool user_dict_exists = keys_dict_check_presence(KEYS_DICT_USER_PATH); + uint32_t total_dict_keys = 0; + if (system_dict_exists) + { + system_dict = + keys_dict_alloc(KEYS_DICT_SYSTEM_PATH, KeysDictModeOpenExisting, sizeof(MfClassicKey)); + total_dict_keys += keys_dict_get_total_keys(system_dict); + } + user_dict = keys_dict_alloc(KEYS_DICT_USER_PATH, KeysDictModeOpenAlways, sizeof(MfClassicKey)); + if (user_dict_exists) + { + total_dict_keys += keys_dict_get_total_keys(user_dict); + } + user_dict_exists = true; + program_state->dict_count = total_dict_keys; + program_state->mfkey_state = DictionaryAttack; + // Read nonces + MfClassicNonceArray *nonce_arr; + nonce_arr = init_plugin->napi_mf_classic_nonce_array_alloc( + system_dict, system_dict_exists, user_dict, program_state); + if (system_dict_exists) + { + keys_dict_free(system_dict); + } + if (nonce_arr->total_nonces == 0) + { + // Nothing to crack + program_state->err = ZeroNonces; + program_state->mfkey_state = Error; + init_plugin->napi_mf_classic_nonce_array_free(nonce_arr); + flipper_application_free(app); + furi_record_close(RECORD_STORAGE); + keys_dict_free(user_dict); + free(keyarray); + return; + } + flipper_application_free(app); + furi_record_close(RECORD_STORAGE); + // TODO: Track free state at the time this is called to ensure double free does not happen + furi_assert(nonce_arr); + furi_assert(nonce_arr->stream); + // TODO: Already closed? + buffered_file_stream_close(nonce_arr->stream); + stream_free(nonce_arr->stream); + // FURI_LOG_I(TAG, "Free heap after free(): %zub", memmgr_get_free_heap()); + program_state->mfkey_state = MFKeyAttack; + // TODO: Work backwards on this array and free memory + for (i = 0; i < nonce_arr->total_nonces; i++) + { + MfClassicNonce next_nonce = nonce_arr->remaining_nonce_array[i]; + if (key_already_found_for_nonce_in_solved(keyarray, keyarray_size, &next_nonce)) + { + nonce_arr->remaining_nonces--; + (program_state->cracked)++; + (program_state->num_completed)++; + continue; + } + // FURI_LOG_I(TAG, "Beginning recovery for %8lx", next_nonce.uid); + FuriString *cuid_dict_path; + switch (next_nonce.attack) + { + case mfkey32: + ks_enc = next_nonce.ar0_enc ^ next_nonce.p64; + nt_xor_uid = 0; + break; + case static_nested: + ks_enc = next_nonce.ks1_2_enc; + nt_xor_uid = next_nonce.uid_xor_nt1; + break; + case static_encrypted: + ks_enc = next_nonce.ks1_1_enc; + nt_xor_uid = next_nonce.uid_xor_nt0; + cuid_dict_path = furi_string_alloc_printf( + "%s/mf_classic_dict_%08lx.nfc", EXT_PATH("nfc/assets"), next_nonce.uid); + // May need RECORD_STORAGE? + program_state->cuid_dict = keys_dict_alloc( + furi_string_get_cstr(cuid_dict_path), + KeysDictModeOpenAlways, + sizeof(MfClassicKey)); + furi_string_free(cuid_dict_path); + break; + } + + if (!recover(&next_nonce, ks_enc, nt_xor_uid, program_state)) + { + if ((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) + { + keys_dict_free(program_state->cuid_dict); + } + if (program_state->close_thread_please) + { + break; + } + // No key found in recover() or static encrypted + (program_state->num_completed)++; + continue; + } + (program_state->cracked)++; + (program_state->num_completed)++; + found_key = next_nonce.key; + bool already_found = false; + for (j = 0; j < keyarray_size; j++) + { + if (memcmp(keyarray[j].data, found_key.data, MF_CLASSIC_KEY_SIZE) == 0) + { + already_found = true; + break; + } + } + if (already_found == false) + { + // New key + MfClassicKey *new_keyarray = realloc(keyarray, sizeof(MfClassicKey) * (keyarray_size + 1)); + if (!new_keyarray) + { + // Realloc failed - continue with existing keyarray + FURI_LOG_E(TAG, "Failed to realloc keyarray"); + } + else + { + keyarray = new_keyarray; + keyarray_size += 1; + keyarray[keyarray_size - 1] = found_key; + (program_state->unique_cracked)++; + } + } + } + // TODO: Update display to show all keys were found + // TODO: Prepend found key(s) to user dictionary file + // FURI_LOG_I(TAG, "Unique keys found:"); + for (i = 0; i < keyarray_size; i++) + { + // FURI_LOG_I(TAG, "%012" PRIx64, keyarray[i]); + keys_dict_add_key(user_dict, keyarray[i].data, sizeof(MfClassicKey)); + } + if (keyarray_size > 0) + { + dolphin_deed(DolphinDeedNfcKeyAdd); + } + free(nonce_arr); + keys_dict_free(user_dict); + free(keyarray); + if (program_state->mfkey_state == Error) + { + return; + } + // FURI_LOG_I(TAG, "mfkey function completed normally"); // DEBUG + program_state->mfkey_state = Complete; + // No need to alert the user if they asked it to stop + if (!(program_state->close_thread_please)) + { + finished_beep(); + } + return; } // Screen is 128x64 px -static void render_callback(Canvas* const canvas, void* ctx) { - furi_assert(ctx); - ProgramState* program_state = ctx; - furi_mutex_acquire(program_state->mutex, FuriWaitForever); - char draw_str[44] = {}; +static void render_callback(Canvas *const canvas, void *ctx) +{ + furi_assert(ctx); + ProgramState *program_state = ctx; + furi_mutex_acquire(program_state->mutex, FuriWaitForever); + char draw_str[44] = {}; - canvas_draw_frame(canvas, 0, 0, 128, 64); - canvas_draw_frame(canvas, 0, 15, 128, 64); + canvas_draw_frame(canvas, 0, 0, 128, 64); + canvas_draw_frame(canvas, 0, 15, 128, 64); - // FontSecondary by default, title is drawn at the end - snprintf(draw_str, sizeof(draw_str), "RAM: %zub", memmgr_get_free_heap()); - canvas_draw_str_aligned(canvas, 48, 5, AlignLeft, AlignTop, draw_str); - canvas_draw_icon(canvas, 114, 4, &I_mfkey); - if(program_state->mfkey_state == MFKeyAttack) { - float eta_round = (float)1 - ((float)program_state->eta_round / (float)eta_round_time); - float eta_total = (float)1 - ((float)program_state->eta_total / (float)eta_total_time); - float progress = (float)program_state->num_completed / (float)program_state->total; - if(eta_round < 0 || eta_round > 1) { - // Round ETA miscalculated - eta_round = 1; - program_state->eta_round = 0; - } - if(eta_total < 0 || eta_round > 1) { - // Total ETA miscalculated - eta_total = 1; - program_state->eta_total = 0; - } - snprintf( - draw_str, - sizeof(draw_str), - "Cracking: %d/%d - in prog.", - program_state->num_completed, - program_state->total); - elements_progress_bar_with_text(canvas, 5, 18, 118, progress, draw_str); - snprintf( - draw_str, - sizeof(draw_str), - "Round: %d/%d - ETA %02d Sec", - (program_state->search) + 1, // Zero indexed - 256 / MSB_LIMIT, - program_state->eta_round); - elements_progress_bar_with_text(canvas, 5, 31, 118, eta_round, draw_str); - snprintf(draw_str, sizeof(draw_str), "Total ETA %03d Sec", program_state->eta_total); - elements_progress_bar_with_text(canvas, 5, 44, 118, eta_total, draw_str); - } else if(program_state->mfkey_state == DictionaryAttack) { - snprintf( - draw_str, sizeof(draw_str), "Dict solves: %d (in progress)", program_state->cracked); - canvas_draw_str_aligned(canvas, 10, 18, AlignLeft, AlignTop, draw_str); - snprintf(draw_str, sizeof(draw_str), "Keys in dict: %d", program_state->dict_count); - canvas_draw_str_aligned(canvas, 26, 28, AlignLeft, AlignTop, draw_str); - } else if(program_state->mfkey_state == Complete) { - // TODO: Scrollable list view to see cracked keys if user presses down - elements_progress_bar(canvas, 5, 18, 118, 1); - canvas_draw_str_aligned(canvas, 64, 31, AlignCenter, AlignTop, "Complete"); - snprintf( - draw_str, - sizeof(draw_str), - "Keys added to user dict: %d", - program_state->unique_cracked); - canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignTop, draw_str); - if(program_state->num_candidates > 0) { - snprintf( - draw_str, - sizeof(draw_str), - "SEN key candidates: %d", - program_state->num_candidates); - canvas_draw_str_aligned(canvas, 64, 51, AlignCenter, AlignTop, draw_str); - } - } else if(program_state->mfkey_state == Ready) { - canvas_draw_str_aligned(canvas, 50, 30, AlignLeft, AlignTop, "Ready"); - elements_button_center(canvas, "Start"); - elements_button_right(canvas, "Help"); - } else if(program_state->mfkey_state == Help) { - canvas_draw_str_aligned(canvas, 7, 20, AlignLeft, AlignTop, "Collect nonces by reading"); - canvas_draw_str_aligned(canvas, 7, 30, AlignLeft, AlignTop, "tag or reader in NFC app:"); - canvas_draw_str_aligned(canvas, 7, 40, AlignLeft, AlignTop, "https://docs.flipper.net/"); - canvas_draw_str_aligned(canvas, 7, 50, AlignLeft, AlignTop, "nfc/mfkey32"); - } else if(program_state->mfkey_state == Error) { - canvas_draw_str_aligned(canvas, 50, 25, AlignLeft, AlignTop, "Error"); - if(program_state->err == MissingNonces) { - canvas_draw_str_aligned(canvas, 25, 36, AlignLeft, AlignTop, "No nonces found"); - } else if(program_state->err == ZeroNonces) { - canvas_draw_str_aligned(canvas, 15, 36, AlignLeft, AlignTop, "Nonces already cracked"); - } else if(program_state->err == InsufficientRAM) { - canvas_draw_str_aligned(canvas, 35, 36, AlignLeft, AlignTop, "No free RAM"); - } else { - // Unhandled error - } - } else { - // Unhandled program state - } - // Title - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 5, 4, AlignLeft, AlignTop, "MFKey"); - furi_mutex_release(program_state->mutex); + // FontSecondary by default, title is drawn at the end + snprintf(draw_str, sizeof(draw_str), "RAM: %zub", memmgr_get_free_heap()); + canvas_draw_str_aligned(canvas, 48, 5, AlignLeft, AlignTop, draw_str); + canvas_draw_icon(canvas, 114, 4, &I_mfkey); + if (program_state->mfkey_state == MFKeyAttack) + { + float eta_round = (float)1 - ((float)program_state->eta_round / (float)eta_round_time); + float eta_total = (float)1 - ((float)program_state->eta_total / (float)eta_total_time); + float progress = (float)program_state->num_completed / (float)program_state->total; + if (eta_round < 0 || eta_round > 1) + { + // Round ETA miscalculated + eta_round = 1; + program_state->eta_round = 0; + } + if (eta_total < 0 || eta_round > 1) + { + // Total ETA miscalculated + eta_total = 1; + program_state->eta_total = 0; + } + snprintf( + draw_str, + sizeof(draw_str), + "Cracking: %d/%d - in prog.", + program_state->num_completed, + program_state->total); + elements_progress_bar_with_text(canvas, 5, 18, 118, progress, draw_str); + snprintf( + draw_str, + sizeof(draw_str), + "Round: %d/%d - ETA %02d Sec", + (program_state->search) + 1, // Zero indexed + 256 / MSB_LIMIT, + program_state->eta_round); + elements_progress_bar_with_text(canvas, 5, 31, 118, eta_round, draw_str); + snprintf(draw_str, sizeof(draw_str), "Total ETA %03d Sec", program_state->eta_total); + elements_progress_bar_with_text(canvas, 5, 44, 118, eta_total, draw_str); + } + else if (program_state->mfkey_state == DictionaryAttack) + { + snprintf( + draw_str, sizeof(draw_str), "Dict solves: %d (in progress)", program_state->cracked); + canvas_draw_str_aligned(canvas, 10, 18, AlignLeft, AlignTop, draw_str); + snprintf(draw_str, sizeof(draw_str), "Keys in dict: %d", program_state->dict_count); + canvas_draw_str_aligned(canvas, 26, 28, AlignLeft, AlignTop, draw_str); + } + else if (program_state->mfkey_state == Complete) + { + // TODO: Scrollable list view to see cracked keys if user presses down + elements_progress_bar(canvas, 5, 18, 118, 1); + canvas_draw_str_aligned(canvas, 64, 31, AlignCenter, AlignTop, "Complete"); + snprintf( + draw_str, + sizeof(draw_str), + "Keys added to user dict: %d", + program_state->unique_cracked); + canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignTop, draw_str); + if (program_state->num_candidates > 0) + { + snprintf( + draw_str, + sizeof(draw_str), + "SEN key candidates: %d", + program_state->num_candidates); + canvas_draw_str_aligned(canvas, 64, 51, AlignCenter, AlignTop, draw_str); + } + } + else if (program_state->mfkey_state == Ready) + { + canvas_draw_str_aligned(canvas, 50, 30, AlignLeft, AlignTop, "Ready"); + elements_button_center(canvas, "Start"); + elements_button_right(canvas, "Help"); + } + else if (program_state->mfkey_state == Help) + { + canvas_draw_str_aligned(canvas, 7, 20, AlignLeft, AlignTop, "Collect nonces by reading"); + canvas_draw_str_aligned(canvas, 7, 30, AlignLeft, AlignTop, "tag or reader in NFC app:"); + canvas_draw_str_aligned(canvas, 7, 40, AlignLeft, AlignTop, "https://docs.flipper.net/"); + canvas_draw_str_aligned(canvas, 7, 50, AlignLeft, AlignTop, "nfc/mfkey32"); + } + else if (program_state->mfkey_state == Error) + { + canvas_draw_str_aligned(canvas, 50, 25, AlignLeft, AlignTop, "Error"); + if (program_state->err == MissingNonces) + { + canvas_draw_str_aligned(canvas, 25, 36, AlignLeft, AlignTop, "No nonces found"); + } + else if (program_state->err == ZeroNonces) + { + canvas_draw_str_aligned(canvas, 15, 36, AlignLeft, AlignTop, "Nonces already cracked"); + } + else if (program_state->err == InsufficientRAM) + { + canvas_draw_str_aligned(canvas, 35, 36, AlignLeft, AlignTop, "No free RAM"); + } + else + { + // Unhandled error + } + } + else + { + // Unhandled program state + } + // Title + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 5, 4, AlignLeft, AlignTop, "MFKey"); + furi_mutex_release(program_state->mutex); } -static void input_callback(InputEvent* input_event, void* event_queue) { - furi_assert(event_queue); - furi_message_queue_put((FuriMessageQueue*)event_queue, input_event, FuriWaitForever); +static void input_callback(InputEvent *input_event, void *event_queue) +{ + furi_assert(event_queue); + furi_message_queue_put((FuriMessageQueue *)event_queue, input_event, FuriWaitForever); } -static void mfkey_state_init(ProgramState* program_state) { - program_state->mfkey_state = Ready; - program_state->cracked = 0; - program_state->unique_cracked = 0; - program_state->num_completed = 0; - program_state->num_candidates = 0; - program_state->total = 0; - program_state->dict_count = 0; +static void mfkey_state_init(ProgramState *program_state) +{ + program_state->mfkey_state = Ready; + program_state->cracked = 0; + program_state->unique_cracked = 0; + program_state->num_completed = 0; + program_state->num_candidates = 0; + program_state->total = 0; + program_state->dict_count = 0; } // Entrypoint for worker thread -static int32_t mfkey_worker_thread(void* ctx) { - ProgramState* program_state = ctx; - program_state->mfkey_state = Initializing; - mfkey(program_state); - return 0; +static int32_t mfkey_worker_thread(void *ctx) +{ + ProgramState *program_state = ctx; + program_state->mfkey_state = Initializing; + mfkey(program_state); + return 0; } -int32_t mfkey_main() { - FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); +int32_t mfkey_main() +{ + FuriMessageQueue *event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); - ProgramState* program_state = malloc(sizeof(ProgramState)); + ProgramState *program_state = malloc(sizeof(ProgramState)); - mfkey_state_init(program_state); + mfkey_state_init(program_state); - program_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + program_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); - // Set system callbacks - ViewPort* view_port = view_port_alloc(); - view_port_draw_callback_set(view_port, render_callback, program_state); - view_port_input_callback_set(view_port, input_callback, event_queue); + // Set system callbacks + ViewPort *view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, render_callback, program_state); + view_port_input_callback_set(view_port, input_callback, event_queue); - // Open GUI and register view_port - Gui* gui = furi_record_open(RECORD_GUI); - gui_add_view_port(gui, view_port, GuiLayerFullscreen); + // Open GUI and register view_port + Gui *gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); - program_state->mfkeythread = - furi_thread_alloc_ex("MFKeyWorker", 2048, mfkey_worker_thread, program_state); + program_state->mfkeythread = + furi_thread_alloc_ex("MFKeyWorker", 2048, mfkey_worker_thread, program_state); - InputEvent input_event; - for(bool main_loop = true; main_loop;) { - FuriStatus event_status = furi_message_queue_get(event_queue, &input_event, 100); + InputEvent input_event; + for (bool main_loop = true; main_loop;) + { + FuriStatus event_status = furi_message_queue_get(event_queue, &input_event, 100); - furi_mutex_acquire(program_state->mutex, FuriWaitForever); + furi_mutex_acquire(program_state->mutex, FuriWaitForever); - if(event_status == FuriStatusOk) { - if(input_event.type == InputTypePress) { - switch(input_event.key) { - case InputKeyRight: - if(program_state->mfkey_state == Ready) { - program_state->mfkey_state = Help; - } - break; - case InputKeyOk: - if(program_state->mfkey_state == Ready) { - furi_thread_start(program_state->mfkeythread); - } - break; - case InputKeyBack: - if(program_state->mfkey_state == Help) { - program_state->mfkey_state = Ready; - } else { - program_state->close_thread_please = true; - // Wait until thread is finished - furi_thread_join(program_state->mfkeythread); - main_loop = false; - } - break; - default: - break; - } - } - } + if (event_status == FuriStatusOk) + { + if (input_event.type == InputTypePress) + { + switch (input_event.key) + { + case InputKeyRight: + if (program_state->mfkey_state == Ready) + { + program_state->mfkey_state = Help; + } + break; + case InputKeyOk: + if (program_state->mfkey_state == Ready) + { + furi_thread_start(program_state->mfkeythread); + } + break; + case InputKeyBack: + if (program_state->mfkey_state == Help) + { + program_state->mfkey_state = Ready; + } + else + { + program_state->close_thread_please = true; + // Wait until thread is finished + furi_thread_join(program_state->mfkeythread); + main_loop = false; + } + break; + default: + break; + } + } + } - furi_mutex_release(program_state->mutex); - view_port_update(view_port); - } + furi_mutex_release(program_state->mutex); + view_port_update(view_port); + } - // Thread joined in back event handler - furi_thread_free(program_state->mfkeythread); - view_port_enabled_set(view_port, false); - gui_remove_view_port(gui, view_port); - furi_record_close(RECORD_GUI); - view_port_free(view_port); - furi_message_queue_free(event_queue); - furi_mutex_free(program_state->mutex); - free(program_state); + // Thread joined in back event handler + furi_thread_free(program_state->mfkeythread); + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close(RECORD_GUI); + view_port_free(view_port); + furi_message_queue_free(event_queue); + furi_mutex_free(program_state->mutex); + free(program_state); - return 0; + return 0; } #pragma GCC pop_options diff --git a/applications/system/mfkey/mfkey.h b/applications/system/mfkey/mfkey.h index 4a7ab3423..74274edc8 100644 --- a/applications/system/mfkey/mfkey.h +++ b/applications/system/mfkey/mfkey.h @@ -9,100 +9,112 @@ #include #include -struct Crypto1State { - uint32_t odd, even; +struct Crypto1State +{ + uint32_t odd, even; }; -struct Msb { - int tail; - uint32_t states[768]; +struct Msb +{ + int tail; + uint32_t states[768]; }; -typedef enum { - MissingNonces, - ZeroNonces, - InsufficientRAM, +typedef enum +{ + MissingNonces, + ZeroNonces, + InsufficientRAM, } MFKeyError; -typedef enum { - Ready, - Initializing, - DictionaryAttack, - MFKeyAttack, - Complete, - Error, - Help, +typedef enum +{ + Ready, + Initializing, + DictionaryAttack, + MFKeyAttack, + Complete, + Error, + Help, } MFKeyState; // TODO: Can we eliminate any of the members of this struct? -typedef struct { - FuriMutex* mutex; - MFKeyError err; - MFKeyState mfkey_state; - int cracked; - int unique_cracked; - int num_completed; - int num_candidates; - int total; - int dict_count; - int search; - int eta_timestamp; - int eta_total; - int eta_round; - bool mfkey32_present; - bool nested_present; - bool close_thread_please; - FuriThread* mfkeythread; - KeysDict* cuid_dict; +typedef struct +{ + FuriMutex *mutex; + MFKeyError err; + MFKeyState mfkey_state; + int cracked; + int unique_cracked; + int num_completed; + int num_candidates; + int total; + int dict_count; + int search; + int eta_timestamp; + int eta_total; + int eta_round; + bool mfkey32_present; + bool nested_present; + bool close_thread_please; + FuriThread *mfkeythread; + KeysDict *cuid_dict; } ProgramState; -typedef enum { - mfkey32, - static_nested, - static_encrypted +typedef enum +{ + mfkey32, + static_nested, + static_encrypted } AttackType; -typedef struct { - AttackType attack; - MfClassicKey key; // key - uint32_t uid; // serial number - uint32_t nt0; // tag challenge first - uint32_t nt1; // tag challenge second - uint32_t uid_xor_nt0; // uid ^ nt0 - uint32_t uid_xor_nt1; // uid ^ nt1 - union { - // Mfkey32 - struct { - uint32_t p64; // 64th successor of nt0 - uint32_t p64b; // 64th successor of nt1 - uint32_t nr0_enc; // first encrypted reader challenge - uint32_t ar0_enc; // first encrypted reader response - uint32_t nr1_enc; // second encrypted reader challenge - uint32_t ar1_enc; // second encrypted reader response - }; - // Nested - struct { - uint32_t ks1_1_enc; // first encrypted keystream - uint32_t ks1_2_enc; // second encrypted keystream - char par_1_str[5]; // first parity bits (string representation) - char par_2_str[5]; // second parity bits (string representation) - uint8_t par_1; // first parity bits - uint8_t par_2; // second parity bits - }; - }; +typedef struct +{ + AttackType attack; + MfClassicKey key; // key + uint32_t uid; // serial number + uint32_t nt0; // tag challenge first + uint32_t nt1; // tag challenge second + uint32_t uid_xor_nt0; // uid ^ nt0 + uint32_t uid_xor_nt1; // uid ^ nt1 + union + { + // Mfkey32 + struct + { + uint32_t p64; // 64th successor of nt0 + uint32_t p64b; // 64th successor of nt1 + uint32_t nr0_enc; // first encrypted reader challenge + uint32_t ar0_enc; // first encrypted reader response + uint32_t nr1_enc; // second encrypted reader challenge + uint32_t ar1_enc; // second encrypted reader response + }; + // Nested + struct + { + uint32_t ks1_1_enc; // first encrypted keystream + uint32_t ks1_2_enc; // second encrypted keystream + char par_1_str[5]; // first parity bits (string representation) + char par_2_str[5]; // second parity bits (string representation) + uint8_t par_1; // first parity bits + uint8_t par_2; // second parity bits + }; + }; } MfClassicNonce; -typedef struct { - Stream* stream; - uint32_t total_nonces; - MfClassicNonce* remaining_nonce_array; - size_t remaining_nonces; +typedef struct +{ + Stream *stream; + uint32_t total_nonces; + MfClassicNonce *remaining_nonce_array; + size_t remaining_nonces; } MfClassicNonceArray; -struct KeysDict { - Stream* stream; - size_t key_size; - size_t key_size_symbols; - size_t total_keys; +struct KeysDict +{ + Stream *stream; + size_t key_size; + size_t key_size_symbols; + size_t total_keys; }; -#endif // MFKEY_H +#endif // MFKEY_H \ No newline at end of file From 2125e210c89120220d9cfebe261b8f1f21cde04a Mon Sep 17 00:00:00 2001 From: noproto Date: Sun, 24 Aug 2025 19:00:10 -0400 Subject: [PATCH 021/192] Static encrypted performance fixed --- applications/system/mfkey/.catalog/README.md | 2 +- .../system/mfkey/.catalog/changelog.md | 2 +- applications/system/mfkey/mfkey.c | 189 +++++++++++++++--- applications/system/mfkey/mfkey.h | 3 + 4 files changed, 164 insertions(+), 32 deletions(-) diff --git a/applications/system/mfkey/.catalog/README.md b/applications/system/mfkey/.catalog/README.md index 22ff43aa6..770e46880 100644 --- a/applications/system/mfkey/.catalog/README.md +++ b/applications/system/mfkey/.catalog/README.md @@ -8,5 +8,5 @@ After collecting nonces using the Extract MF Keys option, press the Start button ## Credits -Developers: noproto, AG, Flipper Devices, WillyJL, CavallUwU +Developers: noproto, AG, Flipper Devices, WillyJL, CavallUwU, Ivisayan Thanks: AloneLiberty, Foxushka, bettse, Equip diff --git a/applications/system/mfkey/.catalog/changelog.md b/applications/system/mfkey/.catalog/changelog.md index c93c6e8f8..7b5be62b2 100644 --- a/applications/system/mfkey/.catalog/changelog.md +++ b/applications/system/mfkey/.catalog/changelog.md @@ -1,5 +1,5 @@ ## 3.1 - - Key recovery is 20% faster, added write buffering of Static Encrypted Nested key candidates + - Key recovery is 20% faster, new write buffering of Static Encrypted Nested key candidates performs recovery 70x faster ## 3.0 - Added Static Encrypted Nested key recovery, added NFC app support, dropped FlipperNested support ## 2.7 diff --git a/applications/system/mfkey/mfkey.c b/applications/system/mfkey/mfkey.c index 0b68e31e0..e49b96263 100644 --- a/applications/system/mfkey/mfkey.c +++ b/applications/system/mfkey/mfkey.c @@ -11,8 +11,6 @@ // TODO: Find ~1 KB memory leak // TODO: Use seednt16 to reduce static encrypted key candidates: https://gist.github.com/noproto/8102f8f32546564cd674256e62ff76ea // https://eprint.iacr.org/2024/1275.pdf section X -// TODO: Static Encrypted: Minimum RAM for adding to keys dict (avoid crashes) -// TODO: Static Encrypted: Optimize KeysDict or buffer keys to write in chunks #include #include @@ -40,6 +38,7 @@ #define KEYS_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc") #define MAX_NAME_LEN 32 #define MAX_PATH_LEN 64 +#define STATIC_ENCRYPTED_RAM_THRESHOLD 4096 #define LF_POLY_ODD (0x29CE5C) #define LF_POLY_EVEN (0x870804) @@ -66,6 +65,50 @@ static int16_t eta_total_time = 705; // MSB_LIMIT: Chunk size (out of 256) - can be 8-bit as it's a small value static uint8_t MSB_LIMIT = 16; +static inline void flush_key_buffer(ProgramState *program_state) +{ + if (program_state->key_buffer && program_state->key_buffer_count > 0 && program_state->cuid_dict) + { + // Pre-allocate exact size needed: 12 hex chars + 1 newline per key + size_t total_size = program_state->key_buffer_count * 13; + //FURI_LOG_I(TAG, "Flushing key buffer: %d keys", program_state->key_buffer_count); + //FURI_LOG_I(TAG, "Total size: %d bytes", total_size); + char* batch_buffer = malloc(total_size + 1); // +1 for null terminator + + char* ptr = batch_buffer; + const char hex_chars[] = "0123456789ABCDEF"; + + for (size_t i = 0; i < program_state->key_buffer_count; i++) + { + // Convert key to hex string directly into buffer + for (size_t j = 0; j < sizeof(MfClassicKey); j++) + { + uint8_t byte = program_state->key_buffer[i].data[j]; + *ptr++ = hex_chars[byte >> 4]; + *ptr++ = hex_chars[byte & 0x0F]; + } + *ptr++ = '\n'; + } + *ptr = '\0'; + + // Write all keys at once by directly accessing the stream + Stream* stream = program_state->cuid_dict->stream; + uint32_t actual_pos = stream_tell(stream); + + if (stream_seek(stream, 0, StreamOffsetFromEnd) && + stream_write(stream, (uint8_t*)batch_buffer, total_size) == total_size) + { + // Update total key count + program_state->cuid_dict->total_keys += program_state->key_buffer_count; + } + + // May not be needed + stream_seek(stream, actual_pos, StreamOffsetFromStart); + free(batch_buffer); + program_state->key_buffer_count = 0; + } +} + static inline int check_state(struct Crypto1State *t, MfClassicNonce *n, ProgramState *program_state) { @@ -115,7 +158,16 @@ check_state(struct Crypto1State *t, MfClassicNonce *n, ProgramState *program_sta // Found key candidate crypto1_get_lfsr(t, &(n->key)); program_state->num_candidates++; - keys_dict_add_key(program_state->cuid_dict, n->key.data, sizeof(MfClassicKey)); + + // Use key buffer - buffer is guaranteed to be available for static_encrypted + program_state->key_buffer[program_state->key_buffer_count] = n->key; + program_state->key_buffer_count++; + + // Flush buffer when full + if (program_state->key_buffer_count >= program_state->key_buffer_size) + { + flush_key_buffer(program_state); + } } } } @@ -680,53 +732,102 @@ void **allocate_blocks(const size_t *block_sizes, int num_blocks) return block_pointers; } -// Inline function for checking if we're at full speed -static inline bool is_full_speed() -{ - return MSB_LIMIT == 16; -} - bool recover(MfClassicNonce *n, int ks2, unsigned int in, ProgramState *program_state) { bool found = false; const size_t block_sizes[] = {49216, 49216, 5120, 5120, 4096}; const size_t reduced_block_sizes[] = {24608, 24608, 5120, 5120, 4096}; const int num_blocks = sizeof(block_sizes) / sizeof(block_sizes[0]); - void **block_pointers = allocate_blocks(block_sizes, num_blocks); - if (block_pointers == NULL) + // Reset globals each nonce + eta_round_time = 44; + eta_total_time = 705; + MSB_LIMIT = 16; + + // Use half speed (reduced block sizes) for static encrypted nonces so we can buffer keys + bool use_half_speed = (n->attack == static_encrypted); + if (use_half_speed) { - // System has less than the guaranteed amount of RAM (140 KB) - adjust some parameters to run anyway at half speed - if (is_full_speed()) + //eta_round_time *= 2; + eta_total_time *= 2; + MSB_LIMIT /= 2; + } + + void **block_pointers = allocate_blocks(use_half_speed ? reduced_block_sizes : block_sizes, num_blocks); + if (block_pointers == NULL) { + if (n->attack != static_encrypted) { + // System has less than the guaranteed amount of RAM (140 KB) - adjust some parameters to run anyway at half speed // eta_round_time *= 2; eta_total_time *= 2; MSB_LIMIT /= 2; - } - block_pointers = allocate_blocks(reduced_block_sizes, num_blocks); - if (block_pointers == NULL) + block_pointers = allocate_blocks(reduced_block_sizes, num_blocks); + if (block_pointers == NULL) + { + // System has less than 70 KB of RAM - should never happen so we don't reduce speed further + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } else { - // System has less than 70 KB of RAM - should never happen so we don't reduce speed further program_state->err = InsufficientRAM; program_state->mfkey_state = Error; return false; } } - // Adjust estimates for static encrypted attacks - if (n->attack == static_encrypted) - { - eta_round_time *= 4; - eta_total_time *= 4; - if (is_full_speed()) - { - eta_round_time *= 4; - eta_total_time *= 4; - } - } struct Msb *odd_msbs = block_pointers[0]; struct Msb *even_msbs = block_pointers[1]; unsigned int *temp_states_odd = block_pointers[2]; unsigned int *temp_states_even = block_pointers[3]; unsigned int *states_buffer = block_pointers[4]; + + // Allocate key buffer for static encrypted nonces + if (n->attack == static_encrypted) + { + size_t available_ram = memmgr_heap_get_max_free_block(); + // Each key becomes 12 hex chars + 1 newline = 13 bytes in the batch string + // Plus original 6 bytes in buffer = 19 bytes total per key + // Add extra safety margin for string overhead and other allocations + const size_t safety_threshold = STATIC_ENCRYPTED_RAM_THRESHOLD; + const size_t bytes_per_key = sizeof(MfClassicKey) + 13; // buffer + string representation + if (available_ram > safety_threshold) + { + program_state->key_buffer_size = (available_ram - safety_threshold) / bytes_per_key; + program_state->key_buffer = malloc(program_state->key_buffer_size * sizeof(MfClassicKey)); + program_state->key_buffer_count = 0; + if (!program_state->key_buffer) + { + // Free the allocated blocks before returning + for (int i = 0; i < num_blocks; i++) + { + free(block_pointers[i]); + } + free(block_pointers); + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } + else + { + // Free the allocated blocks before returning + for (int i = 0; i < num_blocks; i++) + { + free(block_pointers[i]); + } + free(block_pointers); + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } + else + { + program_state->key_buffer = NULL; + program_state->key_buffer_size = 0; + program_state->key_buffer_count = 0; + } + int oks = 0, eks = 0; int i = 0, msb = 0; for (i = 31; i >= 0; i -= 2) @@ -768,6 +869,17 @@ bool recover(MfClassicNonce *n, int ks2, unsigned int in, ProgramState *program_ break; } } + + // Final flush and cleanup for key buffer + if (n->attack == static_encrypted && program_state->key_buffer) + { + flush_key_buffer(program_state); + free(program_state->key_buffer); + program_state->key_buffer = NULL; + program_state->key_buffer_size = 0; + program_state->key_buffer_count = 0; + } + // Free the allocated blocks for (int i = 0; i < num_blocks; i++) { @@ -946,16 +1058,33 @@ void mfkey(ProgramState *program_state) if (!recover(&next_nonce, ks_enc, nt_xor_uid, program_state)) { - if ((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) + // Check for non-recoverable errors and break the loop + if (program_state->mfkey_state == Error) { - keys_dict_free(program_state->cuid_dict); + if ((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) + { + keys_dict_free(program_state->cuid_dict); + program_state->cuid_dict = NULL; + } + break; } if (program_state->close_thread_please) { + if ((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) + { + keys_dict_free(program_state->cuid_dict); + program_state->cuid_dict = NULL; + } break; } // No key found in recover() or static encrypted (program_state->num_completed)++; + // Free CUID dictionary after each static_encrypted nonce processing + if ((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) + { + keys_dict_free(program_state->cuid_dict); + program_state->cuid_dict = NULL; + } continue; } (program_state->cracked)++; diff --git a/applications/system/mfkey/mfkey.h b/applications/system/mfkey/mfkey.h index 74274edc8..7b5be5c5a 100644 --- a/applications/system/mfkey/mfkey.h +++ b/applications/system/mfkey/mfkey.h @@ -58,6 +58,9 @@ typedef struct bool close_thread_please; FuriThread *mfkeythread; KeysDict *cuid_dict; + MfClassicKey *key_buffer; + size_t key_buffer_size; + size_t key_buffer_count; } ProgramState; typedef enum From 37d8ed1d6fe1c29b797f5061f564e12e4588e2af Mon Sep 17 00:00:00 2001 From: noproto Date: Sun, 24 Aug 2025 22:50:35 -0400 Subject: [PATCH 022/192] Update README.md --- ReadMe.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 074c73150..c30c3bb22 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -32,12 +32,12 @@ Coming from other custom firmware, you'll get: - *MIFARE Classic Accelerated dictionary attack*: dictionary attacks reduced to several seconds - checks ~3500 keys per second - *MIFARE Classic Nested attack support*: collects nested nonces to be cracked by MFKey - *MIFARE Classic Static encrypted backdoor support*: collects static encrypted nonces to be cracked by MFKey using NXP/Fudan backdoor -- **MFKey 3.0**: Mfkey32, Static Nested, and Static Encrypted attacks all on your Flipper Zero -- **MIFARE Ultralight C Dictionary attack** (coming soon!) -- **MIFARE Ultralight C Emulation** (coming soon!) +- **MFKey 3.1**: Mfkey32, Static Nested, and Static Encrypted attacks all on your Flipper Zero +- **MIFARE Ultralight C Dictionary attack**: Ultralight C key management and bruteforce attacks, feature parity with MIFARE Classic +- **MIFARE Ultralight C Key recovery** (NXP disclosure in progress!) - **NFC app memory improvements** (coming soon!) - **Minimal theme** (coming soon!) -- *Future*: MIFARE DESFire Dictionary attack, Tesla credential enrollment +- *Future*: Tesla credential enrollment, MIFARE Classic EV1 support (signature sector Nested attacks), fully locked Ultralight C support, faster static encrypted attacks # Contributing From 828a73bbd1a92a697310280b4bce729d54b39516 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Tue, 26 Aug 2025 01:59:47 +0200 Subject: [PATCH 023/192] Sub-GHz: Only fix Linear inverted bits in user interface, keep old remotes compatible --- lib/subghz/protocols/linear.c | 43 +++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/lib/subghz/protocols/linear.c b/lib/subghz/protocols/linear.c index 7d9f12d42..520d1d34e 100644 --- a/lib/subghz/protocols/linear.c +++ b/lib/subghz/protocols/linear.c @@ -115,33 +115,33 @@ static bool subghz_protocol_encoder_linear_get_upload(SubGhzProtocolEncoderLinea for(uint8_t i = instance->generic.data_count_bit; i > 1; i--) { if(bit_read(instance->generic.data, i - 1)) { //send bit 1 - instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_short); - instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)subghz_protocol_linear_const.te_long); - } else { - //send bit 0 instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_long); instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)subghz_protocol_linear_const.te_short); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_linear_const.te_long); } } //Send end bit if(bit_read(instance->generic.data, 0)) { //send bit 1 - instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_short); - //Send gap - instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)subghz_protocol_linear_const.te_short * 44); - } else { - //send bit 0 instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_long); //Send gap instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)subghz_protocol_linear_const.te_short * 42); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_short); + //Send gap + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_linear_const.te_short * 44); } return true; @@ -249,11 +249,11 @@ void subghz_protocol_decoder_linear_feed(void* context, bool level, uint32_t dur } if(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_linear_const.te_short) < subghz_protocol_linear_const.te_delta) { - subghz_protocol_blocks_add_bit(&instance->decoder, 1); + subghz_protocol_blocks_add_bit(&instance->decoder, 0); } else if( DURATION_DIFF(instance->decoder.te_last, subghz_protocol_linear_const.te_long) < subghz_protocol_linear_const.te_delta) { - subghz_protocol_blocks_add_bit(&instance->decoder, 0); + subghz_protocol_blocks_add_bit(&instance->decoder, 1); } if(instance->decoder.decode_count_bit == subghz_protocol_linear_const.min_count_bit_for_found) { @@ -273,14 +273,14 @@ void subghz_protocol_decoder_linear_feed(void* context, bool level, uint32_t dur subghz_protocol_linear_const.te_delta) && (DURATION_DIFF(duration, subghz_protocol_linear_const.te_long) < subghz_protocol_linear_const.te_delta)) { - subghz_protocol_blocks_add_bit(&instance->decoder, 1); + subghz_protocol_blocks_add_bit(&instance->decoder, 0); instance->decoder.parser_step = LinearDecoderStepSaveDuration; } else if( (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_linear_const.te_long) < subghz_protocol_linear_const.te_delta) && (DURATION_DIFF(duration, subghz_protocol_linear_const.te_short) < subghz_protocol_linear_const.te_delta)) { - subghz_protocol_blocks_add_bit(&instance->decoder, 0); + subghz_protocol_blocks_add_bit(&instance->decoder, 1); instance->decoder.parser_step = LinearDecoderStepSaveDuration; } else { instance->decoder.parser_step = LinearDecoderStepReset; @@ -321,12 +321,15 @@ void subghz_protocol_decoder_linear_get_string(void* context, FuriString* output furi_assert(context); SubGhzProtocolDecoderLinear* instance = context; - uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + // Protocol is actually implemented wrong way around, bits are inverted. + // Instead of fixing it and breaking old saved remotes, + // only the display here is inverted to show correct values. + uint32_t code_found_reverse_lo = instance->generic.data & 0x00000000ffffffff; - uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + uint64_t code_found = subghz_protocol_blocks_reverse_key( instance->generic.data, instance->generic.data_count_bit); - uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; + uint32_t code_found_lo = code_found & 0x00000000ffffffff; furi_string_cat_printf( output, From db4a613f6cc89c800f5a1334192944a40038c03b Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 27 Aug 2025 02:59:00 +0300 Subject: [PATCH 024/192] upd changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e65eaab3e..5875b26ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## Main changes - Current API: 86.0 -* SubGHz: Tune Linear (add better EZCode support), Dickert MAHS decoders +* SubGHz: Tune Linear (edited by @WillyJL in PR #919) (add better EZCode support), Dickert MAHS decoders * SubGHz: Add ZKTeco 430.5 MHz add manually support * SubGHz: Add variant of 'Add Manually' menu with manual editing for each value (PR #909 #911 #914 | by @MrLego8-9) * OFW PR 4251: CLI: Fix long delay with quick connect/disconnect (by @WillyJL) From 5f7aaf2a04722ef3f9e8078fc46ae580effea96a Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 27 Aug 2025 03:09:52 +0300 Subject: [PATCH 025/192] NFC: Fix read crash with unexpectedly large MFC AUTH(0) response by WillyJL https://github.com/flipperdevices/flipperzero-firmware/pull/4265/files --- lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.c b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.c index 2cf66edba..5795c1bfa 100644 --- a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.c +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.c @@ -42,7 +42,13 @@ static Iso14443_3aError iso14443_3a_poller_standard_frame_exchange( break; } + if(bit_buffer_get_capacity_bytes(rx_buffer) < + bit_buffer_get_size_bytes(instance->rx_buffer)) { + ret = Iso14443_3aErrorBufferOverflow; + break; + } bit_buffer_copy(rx_buffer, instance->rx_buffer); + if(!iso14443_crc_check(Iso14443CrcTypeA, instance->rx_buffer)) { ret = Iso14443_3aErrorWrongCrc; break; From 8e650f06881d0566dbc4a84726dee61778be7599 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 27 Aug 2025 03:11:01 +0300 Subject: [PATCH 026/192] upd changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5875b26ce..06a3728aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ * SubGHz: Tune Linear (edited by @WillyJL in PR #919) (add better EZCode support), Dickert MAHS decoders * SubGHz: Add ZKTeco 430.5 MHz add manually support * SubGHz: Add variant of 'Add Manually' menu with manual editing for each value (PR #909 #911 #914 | by @MrLego8-9) +* OFW PR 4265: NFC: Fix read crash with unexpectedly large MFC AUTH(0) response (by @WillyJL) * OFW PR 4251: CLI: Fix long delay with quick connect/disconnect (by @WillyJL) * LFRFID: Add additional procotols supported by EM4305 chipset (by @jamisonderek) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) From c091a58486b574b06852e09e2ce82805451e02d2 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 27 Aug 2025 03:42:01 +0300 Subject: [PATCH 027/192] fix linear again --- lib/subghz/protocols/linear.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/subghz/protocols/linear.c b/lib/subghz/protocols/linear.c index 520d1d34e..4b6114b98 100644 --- a/lib/subghz/protocols/linear.c +++ b/lib/subghz/protocols/linear.c @@ -10,10 +10,10 @@ #define DIP_PATTERN "%c%c%c%c%c%c%c%c%c%c" #define DATA_TO_DIP(dip) \ - (dip & 0x0200 ? '1' : '0'), (dip & 0x0100 ? '1' : '0'), (dip & 0x0080 ? '1' : '0'), \ - (dip & 0x0040 ? '1' : '0'), (dip & 0x0020 ? '1' : '0'), (dip & 0x0010 ? '1' : '0'), \ - (dip & 0x0008 ? '1' : '0'), (dip & 0x0004 ? '1' : '0'), (dip & 0x0002 ? '1' : '0'), \ - (dip & 0x0001 ? '1' : '0') + (dip & 0x0200 ? '0' : '1'), (dip & 0x0100 ? '0' : '1'), (dip & 0x0080 ? '0' : '1'), \ + (dip & 0x0040 ? '0' : '1'), (dip & 0x0020 ? '0' : '1'), (dip & 0x0010 ? '0' : '1'), \ + (dip & 0x0008 ? '0' : '1'), (dip & 0x0004 ? '0' : '1'), (dip & 0x0002 ? '0' : '1'), \ + (dip & 0x0001 ? '0' : '1') static const SubGhzBlockConst subghz_protocol_linear_const = { .te_short = 500, @@ -341,5 +341,5 @@ void subghz_protocol_decoder_linear_get_string(void* context, FuriString* output instance->generic.data_count_bit, code_found_lo, code_found_reverse_lo, - DATA_TO_DIP(code_found_lo)); + DATA_TO_DIP(code_found_reverse_lo)); } From 9913ae0f41c6ea90ef566ba042274a096c965e2d Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sat, 6 Sep 2025 02:33:51 +0200 Subject: [PATCH 028/192] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31c18257b..4e0999bd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ - Metroflip: Fix unsupported card crash, RENFE Suma 10 support, GEG Connect AID added, Top Up log parsing and animations, 16 new rail lines, support for parsing area codes, saving function for Suica/Japan Rail IC, bugfixes (by @luu176) - NFC Maker: Support making empty/blank NDEF payloads (by @WillyJL) - NFC Playlist: Refactor playlist worker, new settings layout, loop setting, controls to move between items (by @acegoal07) + - NMEA GPS: Moved to GPIO/GPS subfolder (by @WillyJL) - Passy: Misc memory management bugfixes, misc UI improvements (by @qistoph) - Seader: Fix ATS handling (by @NVX), reset SAM on error (by @bettse) - Sentry Safe: New interface, settings & help page (by @H4ckd4ddy) From d97155eb2d1169301d8807dc60deaed1dfa41c9c Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sat, 6 Sep 2025 03:35:15 +0200 Subject: [PATCH 029/192] Format --- .../mf_ultralight/mf_ultralight.c | 6 +- .../nfc_scene_mf_ultralight_c_dict_attack.c | 20 +++--- applications/main/nfc/views/dict_attack.c | 63 ++++++------------- .../mf_ultralight/mf_ultralight_poller.c | 3 +- 4 files changed, 34 insertions(+), 58 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index 4860c91ee..aa3cc2e75 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -150,9 +150,9 @@ static NfcCommand } if(!mf_ultralight_event->data->auth_context.skip_auth) { mf_ultralight_event->data->auth_context.password = instance->mf_ul_auth->password; - + // Only set tdes_key for Manual/Reader auth types, not for dictionary attacks - if(instance->mf_ul_auth->type == MfUltralightAuthTypeManual || + if(instance->mf_ul_auth->type == MfUltralightAuthTypeManual || instance->mf_ul_auth->type == MfUltralightAuthTypeReader) { mf_ultralight_event->data->key_request_data.key = instance->mf_ul_auth->tdes_key; mf_ultralight_event->data->key_request_data.key_provided = true; @@ -182,7 +182,7 @@ bool nfc_scene_read_on_event_mf_ultralight(NfcApp* instance, SceneManagerEvent e } else if(event.event == NfcCustomEventPollerIncomplete) { const MfUltralightData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight); - if(data->type == MfUltralightTypeMfulC && + if(data->type == MfUltralightTypeMfulC && instance->mf_ul_auth->type == MfUltralightAuthTypeNone) { // Start dict attack for MFUL C cards only if no specific auth was attempted scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCDictAttack); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c index 99066bef0..b1e24def2 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c @@ -43,11 +43,11 @@ NfcCommand nfc_mf_ultralight_c_dict_attack_worker_callback(NfcGenericEvent event instance->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(instance->poller)); // Check if this is a successful authentication by looking at the poller's auth context const MfUltralightData* data = nfc_poller_get_data(instance->poller); - + // Update page information dict_attack_set_pages_read(instance->dict_attack, data->pages_read); dict_attack_set_pages_total(instance->dict_attack, data->pages_total); - + if(data->pages_read == data->pages_total) { // Full read indicates successful authentication in dict attack mode instance->mf_ultralight_c_dict_context.auth_success = true; @@ -73,10 +73,10 @@ void nfc_scene_mf_ultralight_c_dict_attack_dict_attack_result_callback( void nfc_scene_mf_ultralight_c_dict_attack_prepare_view(NfcApp* instance) { uint32_t state = scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightCDictAttack); - + // Set attack type to Ultralight C dict_attack_set_type(instance->dict_attack, DictAttackTypeMfUltralightC); - + if(state == DictAttackStateUserDictInProgress) { do { if(!keys_dict_check_presence(NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH)) { @@ -110,12 +110,12 @@ void nfc_scene_mf_ultralight_c_dict_attack_prepare_view(NfcApp* instance) { instance->mf_ultralight_c_dict_context.dict_keys_current = 0; dict_attack_set_current_dict_key( instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_current); - + // Set initial Ultralight C specific values dict_attack_set_key_found(instance->dict_attack, false); dict_attack_set_pages_total(instance->dict_attack, 48); // Ultralight C page count dict_attack_set_pages_read(instance->dict_attack, 0); - + dict_attack_set_callback( instance->dict_attack, nfc_scene_mf_ultralight_c_dict_attack_dict_attack_result_callback, @@ -174,7 +174,9 @@ bool nfc_scene_mf_ultralight_c_dict_attack_on_event(void* context, SceneManagerE nfc_scene_mf_ultralight_c_dict_attack_prepare_view(instance); instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); nfc_poller_start( - instance->poller, nfc_mf_ultralight_c_dict_attack_worker_callback, instance); + instance->poller, + nfc_mf_ultralight_c_dict_attack_worker_callback, + instance); consumed = true; } } else { @@ -204,9 +206,7 @@ bool nfc_scene_mf_ultralight_c_dict_attack_on_event(void* context, SceneManagerE nfc_scene_mf_ultralight_c_dict_attack_prepare_view(instance); instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); nfc_poller_start( - instance->poller, - nfc_mf_ultralight_c_dict_attack_worker_callback, - instance); + instance->poller, nfc_mf_ultralight_c_dict_attack_worker_callback, instance); } else { notification_message(instance->notifications, &sequence_semi_success); scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); diff --git a/applications/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index 5a873fcfb..a71e466f8 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -14,7 +14,7 @@ typedef struct { FuriString* header; bool card_detected; DictAttackType attack_type; - + // MIFARE Classic specific uint8_t sectors_total; uint8_t sectors_read; @@ -27,12 +27,12 @@ typedef struct { MfClassicBackdoor backdoor; uint16_t nested_target_key; uint16_t msb_count; - + // Ultralight C specific uint8_t pages_total; uint8_t pages_read; bool key_found; - + // Common size_t dict_keys_total; size_t dict_keys_current; @@ -123,11 +123,7 @@ static void dict_attack_draw_mf_classic(Canvas* canvas, DictAttackViewModel* m) snprintf(draw_str, sizeof(draw_str), "%d/%zu", 1, m->dict_keys_total); } else { snprintf( - draw_str, - sizeof(draw_str), - "%zu/%zu", - m->dict_keys_current, - m->dict_keys_total); + draw_str, sizeof(draw_str), "%zu/%zu", m->dict_keys_current, m->dict_keys_total); } } if(dict_progress > 1.0f) { @@ -142,8 +138,7 @@ static void dict_attack_draw_mf_classic(Canvas* canvas, DictAttackViewModel* m) m->keys_found, m->sectors_total * NFC_CLASSIC_KEYS_PER_SECTOR); canvas_draw_str_aligned(canvas, 0, 33, AlignLeft, AlignTop, draw_str); - snprintf( - draw_str, sizeof(draw_str), "Sectors Read: %d/%d", m->sectors_read, m->sectors_total); + snprintf(draw_str, sizeof(draw_str), "Sectors Read: %d/%d", m->sectors_read, m->sectors_total); canvas_draw_str_aligned(canvas, 0, 43, AlignLeft, AlignTop, draw_str); } @@ -152,32 +147,26 @@ static void dict_attack_draw_mf_ultralight_c(Canvas* canvas, DictAttackViewModel canvas_set_font(canvas, FontSecondary); canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(m->header)); - + snprintf(draw_str, sizeof(draw_str), "Trying keys"); canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); - - float dict_progress = m->dict_keys_total == 0 ? - 0 : - (float)(m->dict_keys_current) / (float)(m->dict_keys_total); + + float dict_progress = + m->dict_keys_total == 0 ? 0 : (float)(m->dict_keys_current) / (float)(m->dict_keys_total); if(m->dict_keys_current == 0) { snprintf(draw_str, sizeof(draw_str), "%d/%zu", 1, m->dict_keys_total); } else { - snprintf( - draw_str, - sizeof(draw_str), - "%zu/%zu", - m->dict_keys_current, - m->dict_keys_total); + snprintf(draw_str, sizeof(draw_str), "%zu/%zu", m->dict_keys_current, m->dict_keys_total); } if(dict_progress > 1.0f) { dict_progress = 1.0f; } elements_progress_bar_with_text(canvas, 0, 20, 128, dict_progress, draw_str); - + canvas_set_font(canvas, FontSecondary); snprintf(draw_str, sizeof(draw_str), "Key found: %s", m->key_found ? "Yes" : "No"); canvas_draw_str_aligned(canvas, 0, 33, AlignLeft, AlignTop, draw_str); - + snprintf(draw_str, sizeof(draw_str), "Pages read: %d/%d", m->pages_read, m->pages_total); canvas_draw_str_aligned(canvas, 0, 43, AlignLeft, AlignTop, draw_str); } @@ -248,7 +237,7 @@ void dict_attack_reset(DictAttack* instance) { DictAttackViewModel * model, { model->attack_type = DictAttackTypeMfClassic; - + // MIFARE Classic fields model->sectors_total = 0; model->sectors_read = 0; @@ -260,12 +249,12 @@ void dict_attack_reset(DictAttack* instance) { model->backdoor = MfClassicBackdoorUnknown; model->nested_target_key = 0; model->msb_count = 0; - + // Ultralight C fields model->pages_total = 0; model->pages_read = 0; model->key_found = false; - + // Common fields model->dict_keys_total = 0; model->dict_keys_current = 0; @@ -422,40 +411,26 @@ void dict_attack_set_type(DictAttack* instance, DictAttackType type) { furi_assert(instance); with_view_model( - instance->view, - DictAttackViewModel * model, - { - model->attack_type = type; - }, - true); + instance->view, DictAttackViewModel * model, { model->attack_type = type; }, true); } void dict_attack_set_pages_total(DictAttack* instance, uint8_t pages_total) { furi_assert(instance); with_view_model( - instance->view, - DictAttackViewModel * model, - { model->pages_total = pages_total; }, - true); + instance->view, DictAttackViewModel * model, { model->pages_total = pages_total; }, true); } void dict_attack_set_pages_read(DictAttack* instance, uint8_t pages_read) { furi_assert(instance); with_view_model( - instance->view, - DictAttackViewModel * model, - { model->pages_read = pages_read; }, - true); + instance->view, DictAttackViewModel * model, { model->pages_read = pages_read; }, true); } void dict_attack_set_key_found(DictAttack* instance, bool key_found) { furi_assert(instance); with_view_model( - instance->view, - DictAttackViewModel * model, - { model->key_found = key_found; }, - true); + instance->view, DictAttackViewModel * model, { model->key_found = key_found; }, true); } diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index 7639abaa9..cf942172b 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -465,7 +465,8 @@ static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPol instance->state = MfUltralightPollerStateReadPages; return command; } else { - instance->auth_context.tdes_key = instance->mfu_event.data->key_request_data.key; + instance->auth_context.tdes_key = + instance->mfu_event.data->key_request_data.key; } } else { FURI_LOG_D(TAG, "No key provided, skipping auth"); From 5a4a8c71d85aad53624a126bc02cb4aa827fbad4 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sat, 6 Sep 2025 03:35:18 +0200 Subject: [PATCH 030/192] Revert "Fix ByteInput to properly zero bytes" This reverts commit e583c105b98215e8192dce3a73e8723d20679294. --- applications/services/gui/modules/byte_input.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/applications/services/gui/modules/byte_input.c b/applications/services/gui/modules/byte_input.c index 06bd76d79..c9f689662 100644 --- a/applications/services/gui/modules/byte_input.c +++ b/applications/services/gui/modules/byte_input.c @@ -839,10 +839,6 @@ void byte_input_set_result_callback( model->callback_context = callback_context; model->bytes = bytes; model->bytes_count = bytes_count; - // Zero out the bytes - for(uint8_t i = 0; i < bytes_count; i++) { - model->bytes[i] = 0; - } }, true); } From 54352f1e800317900dc6eb22b0faca10c911df67 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sat, 6 Sep 2025 03:53:49 +0200 Subject: [PATCH 031/192] Apps: Update MFKey --- applications/external | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/external b/applications/external index 049d6992f..eac9fab55 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit 049d6992f34234d51cc9f04cb29d36530e8e1540 +Subproject commit eac9fab55eef9b960291e0cff669d7b7a6cf5eec From 2a8dfd13aebff8324c30b429d26e9a4ebb4a488f Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sat, 6 Sep 2025 03:53:59 +0200 Subject: [PATCH 032/192] Fix api symbols --- targets/f7/api_symbols.csv | 1 - 1 file changed, 1 deletion(-) diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index da1683348..8f135a0eb 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -2792,7 +2792,6 @@ Function,+,mf_ultralight_is_equal,_Bool,"const MfUltralightData*, const MfUltral Function,+,mf_ultralight_is_page_pwd_or_pack,_Bool,"MfUltralightType, uint16_t" Function,+,mf_ultralight_load,_Bool,"MfUltralightData*, FlipperFormat*, uint32_t" Function,+,mf_ultralight_poller_auth_pwd,MfUltralightError,"MfUltralightPoller*, MfUltralightPollerAuthContext*" -Function,+,mf_ultralight_poller_auth_tdes,MfUltralightError,"MfUltralightPoller*, MfUltralightPollerAuthContext*" Function,+,mf_ultralight_poller_authenticate_end,MfUltralightError,"MfUltralightPoller*, const uint8_t*, const uint8_t*, uint8_t*" Function,+,mf_ultralight_poller_authenticate_start,MfUltralightError,"MfUltralightPoller*, const uint8_t*, uint8_t*" Function,+,mf_ultralight_poller_read_counter,MfUltralightError,"MfUltralightPoller*, uint8_t, MfUltralightCounter*" From aafc497c8f949cdb6ffe700acb8ac6affd5b058c Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sat, 6 Sep 2025 03:56:01 +0200 Subject: [PATCH 033/192] Update changelog --nobuild --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e0999bd5..bb6074b1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,11 @@ - USB: - LEGO Dimensions Toy Pad (by @SegerEnd) - USB-MIDI (by @kribesk, original by @DrZlo13) +- NFC: + - XERO: MIFARE Ultralight C feature parity with MIFARE Classic in native NFC app (by @noproto) + - Dictionary attack: Uses system and user dictionaries stored under /nfc/assets/ to unlock Ultralight C tags + - Key management: Extra Actions → MIFARE Ultralight C Keys in the NFC app allows you to add, list, and remove Ultralight C keys from your Flipper + - UI: Dictionary attack scene and menu options - SubGHz: - UL: Roger (static 28 bit) with add manually support (by @xMasterX & @mishamyte) - UL: V2 Phoenix full support (button switch, add manually, counter decrypt/encrypt) (by @xMasterX & @RocketGod-git, original code by @Skorpionm) @@ -66,6 +71,8 @@ ### Updated: - Apps: + - XERO: MFKey: Key recovery is 20% faster, new write buffering of Static Encrypted Nested key candidates performs recovery 70x faster (by @noproto) + - UL: Sub-GHz Remote: Add possibility to use custom buttons (by @MrLego8-9) - Asteroids: Bugfixes, title screen, Drone Buddy power-up (by @SimplyMinimal) - Combo Cracker: Allow press and hold to change values, add tutorial (by @TAxelAnderson), support alphabetic combination locks (by @henrygab) - ESP Flasher: Bump Marauder 1.8.4 (by @justcallmekoko), add C5 support (by @Play2BReal) @@ -86,7 +93,6 @@ - Weather Station: Added support for solight TE44 (by @fersingb) - Weebo: Prevent 0x88 in UID[3] (by @bettse) - WiFi Marauder: Support for ESP32Marauder 1.8.4 (by @justcallmekoko) - - UL: Sub-GHz Remote: Add possibility to use custom buttons (by @MrLego8-9) - Sub-GHz: - UL: Add 868.46 MHz to default subghz freqs list (by @xMasterX) - UL: Reduce less popular freqs in default hopper preset, make it faster (by @xMasterX) From d06d3684e56732a33c68e519d2dea2bfe4f87fdf Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sat, 6 Sep 2025 04:41:15 +0200 Subject: [PATCH 034/192] Sub-GHz: Actually fix Linear display for real now --- lib/subghz/protocols/linear.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/subghz/protocols/linear.c b/lib/subghz/protocols/linear.c index 4b6114b98..7671dc428 100644 --- a/lib/subghz/protocols/linear.c +++ b/lib/subghz/protocols/linear.c @@ -10,10 +10,10 @@ #define DIP_PATTERN "%c%c%c%c%c%c%c%c%c%c" #define DATA_TO_DIP(dip) \ - (dip & 0x0200 ? '0' : '1'), (dip & 0x0100 ? '0' : '1'), (dip & 0x0080 ? '0' : '1'), \ - (dip & 0x0040 ? '0' : '1'), (dip & 0x0020 ? '0' : '1'), (dip & 0x0010 ? '0' : '1'), \ - (dip & 0x0008 ? '0' : '1'), (dip & 0x0004 ? '0' : '1'), (dip & 0x0002 ? '0' : '1'), \ - (dip & 0x0001 ? '0' : '1') + (dip & 0x0200 ? '1' : '0'), (dip & 0x0100 ? '1' : '0'), (dip & 0x0080 ? '1' : '0'), \ + (dip & 0x0040 ? '1' : '0'), (dip & 0x0020 ? '1' : '0'), (dip & 0x0010 ? '1' : '0'), \ + (dip & 0x0008 ? '1' : '0'), (dip & 0x0004 ? '1' : '0'), (dip & 0x0002 ? '1' : '0'), \ + (dip & 0x0001 ? '1' : '0') static const SubGhzBlockConst subghz_protocol_linear_const = { .te_short = 500, @@ -323,13 +323,13 @@ void subghz_protocol_decoder_linear_get_string(void* context, FuriString* output // Protocol is actually implemented wrong way around, bits are inverted. // Instead of fixing it and breaking old saved remotes, - // only the display here is inverted to show correct values. - uint32_t code_found_reverse_lo = instance->generic.data & 0x00000000ffffffff; + // only the display here is inverted (~) to show correct values. + uint32_t code_found_lo = ~instance->generic.data & 0x00000000000003ff; - uint64_t code_found = subghz_protocol_blocks_reverse_key( - instance->generic.data, instance->generic.data_count_bit); + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + ~instance->generic.data, instance->generic.data_count_bit); - uint32_t code_found_lo = code_found & 0x00000000ffffffff; + uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000000003ff; furi_string_cat_printf( output, @@ -341,5 +341,5 @@ void subghz_protocol_decoder_linear_get_string(void* context, FuriString* output instance->generic.data_count_bit, code_found_lo, code_found_reverse_lo, - DATA_TO_DIP(code_found_reverse_lo)); + DATA_TO_DIP(code_found_lo)); } From af67a99da9c741a283c0491b4c8645fb92d99258 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sat, 6 Sep 2025 04:41:15 +0200 Subject: [PATCH 035/192] Sub-GHz: Actually fix Linear display for real now --- lib/subghz/protocols/linear.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/subghz/protocols/linear.c b/lib/subghz/protocols/linear.c index 0bbfa4e0e..5ecff4f5a 100644 --- a/lib/subghz/protocols/linear.c +++ b/lib/subghz/protocols/linear.c @@ -10,10 +10,10 @@ #define DIP_PATTERN "%c%c%c%c%c%c%c%c%c%c" #define DATA_TO_DIP(dip) \ - (dip & 0x0200 ? '0' : '1'), (dip & 0x0100 ? '0' : '1'), (dip & 0x0080 ? '0' : '1'), \ - (dip & 0x0040 ? '0' : '1'), (dip & 0x0020 ? '0' : '1'), (dip & 0x0010 ? '0' : '1'), \ - (dip & 0x0008 ? '0' : '1'), (dip & 0x0004 ? '0' : '1'), (dip & 0x0002 ? '0' : '1'), \ - (dip & 0x0001 ? '0' : '1') + (dip & 0x0200 ? '1' : '0'), (dip & 0x0100 ? '1' : '0'), (dip & 0x0080 ? '1' : '0'), \ + (dip & 0x0040 ? '1' : '0'), (dip & 0x0020 ? '1' : '0'), (dip & 0x0010 ? '1' : '0'), \ + (dip & 0x0008 ? '1' : '0'), (dip & 0x0004 ? '1' : '0'), (dip & 0x0002 ? '1' : '0'), \ + (dip & 0x0001 ? '1' : '0') static const SubGhzBlockConst subghz_protocol_linear_const = { .te_short = 500, @@ -325,13 +325,13 @@ void subghz_protocol_decoder_linear_get_string(void* context, FuriString* output // Protocol is actually implemented wrong way around, bits are inverted. // Instead of fixing it and breaking old saved remotes, - // only the display here is inverted to show correct values. - uint32_t code_found_reverse_lo = instance->generic.data & 0x00000000ffffffff; + // only the display here is inverted (~) to show correct values. + uint32_t code_found_lo = ~instance->generic.data & 0x00000000000003ff; - uint64_t code_found = subghz_protocol_blocks_reverse_key( - instance->generic.data, instance->generic.data_count_bit); + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + ~instance->generic.data, instance->generic.data_count_bit); - uint32_t code_found_lo = code_found & 0x00000000ffffffff; + uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000000003ff; furi_string_cat_printf( output, @@ -343,5 +343,5 @@ void subghz_protocol_decoder_linear_get_string(void* context, FuriString* output instance->generic.data_count_bit, code_found_lo, code_found_reverse_lo, - DATA_TO_DIP(code_found_reverse_lo)); + DATA_TO_DIP(code_found_lo)); } From bf106d5e3d2946db5f5f086b34933a4bb8db52f1 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sat, 6 Sep 2025 15:58:39 +0200 Subject: [PATCH 036/192] Sub-GHz Remote: Fix hang with RAW files, prevent it for the future --nobuild --- applications/external | 2 +- lib/subghz/protocols/raw.c | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/applications/external b/applications/external index eac9fab55..725321fa9 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit eac9fab55eef9b960291e0cff669d7b7a6cf5eec +Subproject commit 725321fa9f00c327061a572055b876c22c6e8ee5 diff --git a/lib/subghz/protocols/raw.c b/lib/subghz/protocols/raw.c index 5d88145e3..a6aaa3585 100644 --- a/lib/subghz/protocols/raw.c +++ b/lib/subghz/protocols/raw.c @@ -292,10 +292,12 @@ void* subghz_protocol_encoder_raw_alloc(SubGhzEnvironment* environment) { void subghz_protocol_encoder_raw_stop(void* context) { furi_check(context); SubGhzProtocolEncoderRAW* instance = context; + furi_check(instance->file_worker_encoder); instance->is_running = false; if(subghz_file_encoder_worker_is_running(instance->file_worker_encoder)) { subghz_file_encoder_worker_stop(instance->file_worker_encoder); subghz_file_encoder_worker_free(instance->file_worker_encoder); + instance->file_worker_encoder = NULL; } } @@ -320,6 +322,7 @@ void subghz_protocol_raw_file_encoder_worker_set_callback_end( static bool subghz_protocol_encoder_raw_worker_init(SubGhzProtocolEncoderRAW* instance) { furi_assert(instance); + furi_check(!instance->file_worker_encoder); instance->file_worker_encoder = subghz_file_encoder_worker_alloc(); if(subghz_file_encoder_worker_start( From e3f9af505215efd36b01092a2f9c76a5e40c77f0 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sat, 6 Sep 2025 16:43:31 +0200 Subject: [PATCH 037/192] Update apps --nobuild - NFC Playlist: Settings management (by acegoal07) - Weebo: Add more figures to the database (by bettse) --- CHANGELOG.md | 4 ++-- applications/external | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1ae9ee41..0dc6c93f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,7 +84,7 @@ - KeyCopier: Added Weiser WR3 key format (by @lightos), added Suzuki SUZ18 key format (by @RIcePatrol) - Metroflip: Fix unsupported card crash, RENFE Suma 10 support, GEG Connect AID added, Top Up log parsing and animations, 16 new rail lines, support for parsing area codes, saving function for Suica/Japan Rail IC, bugfixes (by @luu176) - NFC Maker: Support making empty/blank NDEF payloads (by @WillyJL) - - NFC Playlist: Refactor playlist worker, new settings layout, loop setting, controls to move between items (by @acegoal07) + - NFC Playlist: Refactor playlist worker, new settings layout and management, loop setting, controls to move between items (by @acegoal07) - NMEA GPS: Moved to GPIO/GPS subfolder (by @WillyJL) - Passy: Misc memory management bugfixes, misc UI improvements (by @qistoph) - Seader: Fix ATS handling (by @NVX), reset SAM on error (by @bettse) @@ -92,7 +92,7 @@ - Seos Compatible: Add keys v2 support with per-device encryption (by @bettse) - Sub-GHz Playlist: Fix crash on disallowed frequencies (by @WillyJL) - Weather Station: Added support for solight TE44 (by @fersingb) - - Weebo: Prevent 0x88 in UID[3] (by @bettse) + - Weebo: Prevent 0x88 in UID[3], add more figures to the database (by @bettse) - WiFi Marauder: Support for ESP32Marauder 1.8.4 (by @justcallmekoko) - Sub-GHz: - UL: Add 868.46 MHz to default subghz freqs list (by @xMasterX) diff --git a/applications/external b/applications/external index 725321fa9..f98b51129 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit 725321fa9f00c327061a572055b876c22c6e8ee5 +Subproject commit f98b5112901aa68a56c47a947a20a6052c544734 From 803209f02cf1696b5bdb4b2ed02f0594be8473eb Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sun, 7 Sep 2025 00:15:34 +0200 Subject: [PATCH 038/192] CI: Reindex updater and website after closing local PRs --nobuild --- .github/workflows/pr-cleanup.yml | 53 ++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .github/workflows/pr-cleanup.yml diff --git a/.github/workflows/pr-cleanup.yml b/.github/workflows/pr-cleanup.yml new file mode 100644 index 000000000..9ecd9b5cd --- /dev/null +++ b/.github/workflows/pr-cleanup.yml @@ -0,0 +1,53 @@ +name: "PR Cleanup" + +on: + pull_request: + types: + - closed + +jobs: + pr-cleanup: + runs-on: ubuntu-latest + steps: + - name: "Configure for Reindex" + run: | + if ${{ secrets.INDEXER_URL == '' }} ; then + exit + fi + if ${{ !!github.event.pull_request }} ; then + SHOULD_REINDEX=${{ github.event.pull_request.action == 'closed' }} + else + echo "What?" + exit 1 + fi + echo "SHOULD_REINDEX=$SHOULD_REINDEX" >> $GITHUB_ENV + + - name: "Trigger reindex" + if: ${{ env.SHOULD_REINDEX == 'true' }} + run: | + curl --fail -L -H "Token: ${{ secrets.INDEXER_TOKEN }}" \ + "${{ secrets.INDEXER_URL }}"/firmware/reindex + + - name: "Force deploy website to refresh branches" + if: ${{ env.SHOULD_REINDEX == 'true' }} + uses: Codex-/return-dispatch@v2 + id: force-deploy + with: + token: ${{ secrets.REPO_DISPATCH_TOKEN }} + ref: dev + repo: v2.momentum-fw.dev + owner: Next-Flip + workflow: force-deploy.yml + workflow_timeout_seconds: 300 + workflow_job_steps_retry_seconds: 5 + + - name: "Wait for deploy (Run ID ${{ steps.force-deploy.outputs.run_id }})" + if: ${{ env.SHOULD_REINDEX == 'true' }} + uses: Codex-/await-remote-run@v1 + with: + token: ${{ github.token }} + repo: v2.momentum-fw.dev + owner: Next-Flip + run_id: ${{ steps.force-deploy.outputs.run_id }} + run_timeout_seconds: 300 + poll_interval_ms: 5000 From f131facd8a29e634fa48864bd738632b7be24e86 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sun, 7 Sep 2025 04:22:57 +0200 Subject: [PATCH 039/192] Mass Storage: Add ability to spoof USB identity values (by xtruan) --- CHANGELOG.md | 1 + applications/external | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dc6c93f7..ae877426e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,6 +82,7 @@ - FlipWiFi: Minor bugfixes (by @jblanked) - Flipper Blackhat: Add Deauth Broadcast command (by @o7-machinehum) - KeyCopier: Added Weiser WR3 key format (by @lightos), added Suzuki SUZ18 key format (by @RIcePatrol) + - Mass Storage: Add ability to spoof USB identity values (by @xtruan) - Metroflip: Fix unsupported card crash, RENFE Suma 10 support, GEG Connect AID added, Top Up log parsing and animations, 16 new rail lines, support for parsing area codes, saving function for Suica/Japan Rail IC, bugfixes (by @luu176) - NFC Maker: Support making empty/blank NDEF payloads (by @WillyJL) - NFC Playlist: Refactor playlist worker, new settings layout and management, loop setting, controls to move between items (by @acegoal07) diff --git a/applications/external b/applications/external index f98b51129..48bc1876e 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit f98b5112901aa68a56c47a947a20a6052c544734 +Subproject commit 48bc1876e1981e123a378466a8958933220829b8 From f09942a4a3cb3bf5ba5c0ffcb03769282da2a325 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sun, 7 Sep 2025 14:15:48 +0200 Subject: [PATCH 040/192] Sub-GHz: Fix RAW files again --- lib/subghz/protocols/raw.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/subghz/protocols/raw.c b/lib/subghz/protocols/raw.c index a6aaa3585..71cb18b9a 100644 --- a/lib/subghz/protocols/raw.c +++ b/lib/subghz/protocols/raw.c @@ -292,9 +292,9 @@ void* subghz_protocol_encoder_raw_alloc(SubGhzEnvironment* environment) { void subghz_protocol_encoder_raw_stop(void* context) { furi_check(context); SubGhzProtocolEncoderRAW* instance = context; - furi_check(instance->file_worker_encoder); instance->is_running = false; - if(subghz_file_encoder_worker_is_running(instance->file_worker_encoder)) { + if(instance->file_worker_encoder && + subghz_file_encoder_worker_is_running(instance->file_worker_encoder)) { subghz_file_encoder_worker_stop(instance->file_worker_encoder); subghz_file_encoder_worker_free(instance->file_worker_encoder); instance->file_worker_encoder = NULL; From 65cd636f500d6a7205a7e9d6ccc109844dc30825 Mon Sep 17 00:00:00 2001 From: "Aaron Tulino (Aaronjamt)" Date: Mon, 8 Sep 2025 03:12:50 -0700 Subject: [PATCH 041/192] Update Date/Time Input GUI module Add an option to control which fields can be edited by the user --- .../services/gui/modules/date_time_input.c | 114 +++++++++++++++--- .../services/gui/modules/date_time_input.h | 19 +++ targets/f7/api_symbols.csv | 1 + 3 files changed, 114 insertions(+), 20 deletions(-) diff --git a/applications/services/gui/modules/date_time_input.c b/applications/services/gui/modules/date_time_input.c index 63f648edb..abcb16887 100644 --- a/applications/services/gui/modules/date_time_input.c +++ b/applications/services/gui/modules/date_time_input.c @@ -2,12 +2,12 @@ #include #include -#define get_state(m, r, c) \ - ((m)->row == (r) && (m)->column == (c) ? \ - ((m)->editing ? EditStateActiveEditing : EditStateActive) : \ - EditStateNone) - -#define ROW_0_Y (10) +#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) @@ -28,6 +28,15 @@ typedef struct { 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; @@ -37,6 +46,7 @@ typedef enum { EditStateNone, EditStateActive, EditStateActiveEditing, + EditStateDisabled } EditState; static inline void date_time_input_cleanup_date(DateTime* dt) { @@ -59,15 +69,17 @@ static inline void date_time_input_draw_block( furi_assert(text); canvas_set_color(canvas, ColorBlack); - 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); + 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_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); @@ -88,19 +100,19 @@ static void date_time_input_draw_time_callback(Canvas* canvas, DateTimeInputMode 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), buffer); + 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); 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), buffer); + 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), buffer); + 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) { @@ -113,14 +125,14 @@ static void date_time_input_draw_date_callback(Canvas* canvas, DateTimeInputMode 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), buffer); + 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), buffer); + 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), buffer); + 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); } @@ -131,17 +143,36 @@ static void date_time_input_view_draw_callback(Canvas* canvas, void* _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) { @@ -283,6 +314,13 @@ static void date_time_input_reset_model_input_data(DateTimeInputModel* model) { 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) { @@ -345,3 +383,39 @@ void date_time_input_set_header_text(DateTimeInput* date_time_input, const char* with_view_model( date_time_input->view, DateTimeInputModel * model, { model->header = text; }, 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 index 3efc6c7a1..2969994fd 100644 --- a/applications/services/gui/modules/date_time_input.h +++ b/applications/services/gui/modules/date_time_input.h @@ -65,6 +65,25 @@ void date_time_input_set_result_callback( */ void date_time_input_set_header_text(DateTimeInput* date_time_input, const char* text); +/** 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 1ea105e00..fcf8388f1 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -929,6 +929,7 @@ 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_header_text,void,"DateTimeInput*, const char*" Function,+,date_time_input_set_result_callback,void,"DateTimeInput*, DateTimeChangedCallback, DateTimeDoneCallback, void*, DateTime*" Function,+,datetime_datetime_to_timestamp,uint32_t,DateTime* From ffb8eb7cff4345826ac83ecc9dda816db693ae2d Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 9 Sep 2025 15:31:43 +0300 Subject: [PATCH 042/192] upd mfkey --- applications/system/mfkey/application.fam | 2 +- applications/system/mfkey/crypto1.h | 405 ++-- applications/system/mfkey/mfkey.c | 2058 +++++++++++++-------- applications/system/mfkey/mfkey.h | 173 +- 4 files changed, 1563 insertions(+), 1075 deletions(-) diff --git a/applications/system/mfkey/application.fam b/applications/system/mfkey/application.fam index dfd513847..0d81c1050 100644 --- a/applications/system/mfkey/application.fam +++ b/applications/system/mfkey/application.fam @@ -15,7 +15,7 @@ App( fap_icon_assets="images", fap_weburl="https://github.com/noproto/FlipperMfkey", fap_description="MIFARE Classic key recovery tool", - fap_version="3.0", + fap_version="3.1", ) App( diff --git a/applications/system/mfkey/crypto1.h b/applications/system/mfkey/crypto1.h index 9caf5b35e..b9f7c7725 100644 --- a/applications/system/mfkey/crypto1.h +++ b/applications/system/mfkey/crypto1.h @@ -6,251 +6,260 @@ #include #include -#define LF_POLY_ODD (0x29CE5C) +#define LF_POLY_ODD (0x29CE5C) #define LF_POLY_EVEN (0x870804) -#define BIT(x, n) ((x) >> (n) & 1) -#define BEBIT(x, n) BIT(x, (n) ^ 24) +#define BIT(x, n) ((x) >> (n) & 1) +#define BEBIT(x, n) BIT(x, (n) ^ 24) #define SWAPENDIAN(x) \ - ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) + ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) static inline uint32_t prng_successor(uint32_t x, uint32_t n); static inline int filter(uint32_t const x); static inline uint8_t evenparity32(uint32_t x); static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2); -void crypto1_get_lfsr(struct Crypto1State* state, MfClassicKey* lfsr); -static inline uint32_t crypt_word(struct Crypto1State* s); -static inline void crypt_word_noret(struct Crypto1State* s, uint32_t in, int x); -static inline uint32_t crypt_word_ret(struct Crypto1State* s, uint32_t in, int x); +void crypto1_get_lfsr(struct Crypto1State *state, MfClassicKey *lfsr); +static inline uint32_t crypt_word(struct Crypto1State *s); +static inline void crypt_word_noret(struct Crypto1State *s, uint32_t in, int x); +static inline uint32_t crypt_word_ret(struct Crypto1State *s, uint32_t in, int x); static uint32_t crypt_word_par( - struct Crypto1State* s, - uint32_t in, - int is_encrypted, - uint32_t nt_plain, - uint8_t* parity_keystream_bits); -static inline void rollback_word_noret(struct Crypto1State* s, uint32_t in, int x); -static inline uint8_t napi_lfsr_rollback_bit(struct Crypto1State* s, uint32_t in, int fb); -static inline uint32_t napi_lfsr_rollback_word(struct Crypto1State* s, uint32_t in, int fb); + struct Crypto1State *s, + uint32_t in, + int is_encrypted, + uint32_t nt_plain, + uint8_t *parity_keystream_bits); +static inline void rollback_word_noret(struct Crypto1State *s, uint32_t in, int x); +static inline uint8_t napi_lfsr_rollback_bit(struct Crypto1State *s, uint32_t in, int fb); +static inline uint32_t napi_lfsr_rollback_word(struct Crypto1State *s, uint32_t in, int fb); static const uint8_t lookup1[256] = { - 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, - 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, - 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24}; + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, + 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24}; static const uint8_t lookup2[256] = { - 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, - 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, - 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, - 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, - 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, - 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, - 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, - 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, - 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6}; + 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, + 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, + 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, + 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, + 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, + 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, + 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, + 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, + 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6}; -static inline int filter(uint32_t const x) { - uint32_t f; - f = lookup1[x & 0xff] | lookup2[(x >> 8) & 0xff]; - f |= 0x0d938 >> (x >> 16 & 0xf) & 1; - return BIT(0xEC57E80A, f); +static inline int filter(uint32_t const x) +{ + uint32_t f; + f = lookup1[x & 0xff] | lookup2[(x >> 8) & 0xff]; + f |= 0x0d938 >> (x >> 16 & 0xf) & 1; + return BIT(0xEC57E80A, f); } -#ifndef __ARM_ARCH_7EM__ -static inline uint8_t evenparity32(uint32_t x) { - return __builtin_parity(x); -} -#endif - #ifdef __ARM_ARCH_7EM__ -static inline uint8_t evenparity32(uint32_t x) { - uint32_t result; - __asm__ volatile("eor r1, %[x], %[x], lsr #16 \n\t" // r1 = x ^ (x >> 16) - "eor r1, r1, r1, lsr #8 \n\t" // r1 = r1 ^ (r1 >> 8) - "eor r1, r1, r1, lsr #4 \n\t" // r1 = r1 ^ (r1 >> 4) - "eor r1, r1, r1, lsr #2 \n\t" // r1 = r1 ^ (r1 >> 2) - "eor r1, r1, r1, lsr #1 \n\t" // r1 = r1 ^ (r1 >> 1) - "and %[result], r1, #1 \n\t" // result = r1 & 1 - : [result] "=r"(result) - : [x] "r"(x) - : "r1"); - return result; +static inline uint8_t evenparity32(uint32_t x) +{ + // fold 32 bits -> 16 -> 8 -> 4 + x ^= x >> 16; + x ^= x >> 8; + x ^= x >> 4; + // magic 0x6996: bit i tells you parity of i (0 ≤ i < 16) + return (uint8_t)((0x6996u >> (x & 0xF)) & 1); } #endif -static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2) { - int p = data[item] >> 25; - p = p << 1 | evenparity32(data[item] & mask1); - p = p << 1 | evenparity32(data[item] & mask2); - data[item] = p << 24 | (data[item] & 0xffffff); +static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2) +{ + int p = data[item] >> 25; + p = p << 1 | evenparity32(data[item] & mask1); + p = p << 1 | evenparity32(data[item] & mask2); + data[item] = p << 24 | (data[item] & 0xffffff); } -static inline uint32_t crypt_word(struct Crypto1State* s) { - // "in" and "x" are always 0 (last iteration) - uint32_t res_ret = 0; - uint32_t feedin, t; - for(int i = 0; i <= 31; i++) { - res_ret |= (filter(s->odd) << (24 ^ i)); //-V629 - feedin = LF_POLY_EVEN & s->even; - feedin ^= LF_POLY_ODD & s->odd; - s->even = s->even << 1 | (evenparity32(feedin)); - t = s->odd, s->odd = s->even, s->even = t; - } - return res_ret; +static inline uint32_t crypt_word(struct Crypto1State *s) +{ + // "in" and "x" are always 0 (last iteration) + uint32_t res_ret = 0; + uint32_t feedin, t; + for (int i = 0; i <= 31; i++) + { + res_ret |= (filter(s->odd) << (24 ^ i)); //-V629 + feedin = LF_POLY_EVEN & s->even; + feedin ^= LF_POLY_ODD & s->odd; + s->even = s->even << 1 | (evenparity32(feedin)); + t = s->odd, s->odd = s->even, s->even = t; + } + return res_ret; } -static inline void crypt_word_noret(struct Crypto1State* s, uint32_t in, int x) { - uint8_t ret; - uint32_t feedin, t, next_in; - for(int i = 0; i <= 31; i++) { - next_in = BEBIT(in, i); - ret = filter(s->odd); - feedin = ret & (!!x); - feedin ^= LF_POLY_EVEN & s->even; - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= !!next_in; - s->even = s->even << 1 | (evenparity32(feedin)); - t = s->odd, s->odd = s->even, s->even = t; - } - return; +static inline void crypt_word_noret(struct Crypto1State *s, uint32_t in, int x) +{ + uint8_t ret; + uint32_t feedin, t, next_in; + for (int i = 0; i <= 31; i++) + { + next_in = BEBIT(in, i); + ret = filter(s->odd); + feedin = ret & (!!x); + feedin ^= LF_POLY_EVEN & s->even; + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even = s->even << 1 | (evenparity32(feedin)); + t = s->odd, s->odd = s->even, s->even = t; + } + return; } -static inline uint32_t crypt_word_ret(struct Crypto1State* s, uint32_t in, int x) { - uint32_t ret = 0; - uint32_t feedin, t, next_in; - uint8_t next_ret; - for(int i = 0; i <= 31; i++) { - next_in = BEBIT(in, i); - next_ret = filter(s->odd); - feedin = next_ret & (!!x); - feedin ^= LF_POLY_EVEN & s->even; - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= !!next_in; - s->even = s->even << 1 | (evenparity32(feedin)); - t = s->odd, s->odd = s->even, s->even = t; - ret |= next_ret << (24 ^ i); - } - return ret; +static inline uint32_t crypt_word_ret(struct Crypto1State *s, uint32_t in, int x) +{ + uint32_t ret = 0; + uint32_t feedin, t, next_in; + uint8_t next_ret; + for (int i = 0; i <= 31; i++) + { + next_in = BEBIT(in, i); + next_ret = filter(s->odd); + feedin = next_ret & (!!x); + feedin ^= LF_POLY_EVEN & s->even; + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even = s->even << 1 | (evenparity32(feedin)); + t = s->odd, s->odd = s->even, s->even = t; + ret |= next_ret << (24 ^ i); + } + return ret; } -static uint8_t get_nth_byte(uint32_t value, int n) { - if(n < 0 || n > 3) { - // Handle invalid input - return 0; - } - return (value >> (8 * (3 - n))) & 0xFF; +static uint8_t get_nth_byte(uint32_t value, int n) +{ + if (n < 0 || n > 3) + { + // Handle invalid input + return 0; + } + return (value >> (8 * (3 - n))) & 0xFF; } -static uint8_t crypt_bit(struct Crypto1State* s, uint8_t in, int is_encrypted) { - uint32_t feedin, t; - uint8_t ret = filter(s->odd); - feedin = ret & !!is_encrypted; - feedin ^= !!in; - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= LF_POLY_EVEN & s->even; - s->even = s->even << 1 | evenparity32(feedin); - t = s->odd, s->odd = s->even, s->even = t; - return ret; +static uint8_t crypt_bit(struct Crypto1State *s, uint8_t in, int is_encrypted) +{ + uint32_t feedin, t; + uint8_t ret = filter(s->odd); + feedin = ret & !!is_encrypted; + feedin ^= !!in; + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= LF_POLY_EVEN & s->even; + s->even = s->even << 1 | evenparity32(feedin); + t = s->odd, s->odd = s->even, s->even = t; + return ret; } static inline uint32_t crypt_word_par( - struct Crypto1State* s, - uint32_t in, - int is_encrypted, - uint32_t nt_plain, - uint8_t* parity_keystream_bits) { - uint32_t ret = 0; - *parity_keystream_bits = 0; // Reset parity keystream bits + struct Crypto1State *s, + uint32_t in, + int is_encrypted, + uint32_t nt_plain, + uint8_t *parity_keystream_bits) +{ + uint32_t ret = 0; + *parity_keystream_bits = 0; // Reset parity keystream bits - for(int i = 0; i < 32; i++) { - uint8_t bit = crypt_bit(s, BEBIT(in, i), is_encrypted); - ret |= bit << (24 ^ i); - // Save keystream parity bit - if((i + 1) % 8 == 0) { - *parity_keystream_bits |= - (filter(s->odd) ^ nfc_util_even_parity8(get_nth_byte(nt_plain, i / 8))) - << (3 - (i / 8)); - } - } - return ret; + for (int i = 0; i < 32; i++) + { + uint8_t bit = crypt_bit(s, BEBIT(in, i), is_encrypted); + ret |= bit << (24 ^ i); + // Save keystream parity bit + if ((i + 1) % 8 == 0) + { + *parity_keystream_bits |= + (filter(s->odd) ^ nfc_util_even_parity8(get_nth_byte(nt_plain, i / 8))) + << (3 - (i / 8)); + } + } + return ret; } -static inline void rollback_word_noret(struct Crypto1State* s, uint32_t in, int x) { - uint8_t ret; - uint32_t feedin, t, next_in; - for(int i = 31; i >= 0; i--) { - next_in = BEBIT(in, i); - s->odd &= 0xffffff; - t = s->odd, s->odd = s->even, s->even = t; - ret = filter(s->odd); - feedin = ret & (!!x); - feedin ^= s->even & 1; - feedin ^= LF_POLY_EVEN & (s->even >>= 1); - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= !!next_in; - s->even |= (evenparity32(feedin)) << 23; - } - return; +static inline void rollback_word_noret(struct Crypto1State *s, uint32_t in, int x) +{ + uint8_t ret; + uint32_t feedin, t, next_in; + for (int i = 31; i >= 0; i--) + { + next_in = BEBIT(in, i); + s->odd &= 0xffffff; + t = s->odd, s->odd = s->even, s->even = t; + ret = filter(s->odd); + feedin = ret & (!!x); + feedin ^= s->even & 1; + feedin ^= LF_POLY_EVEN & (s->even >>= 1); + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even |= (evenparity32(feedin)) << 23; + } + return; } // TODO: /* uint32_t rollback_word(struct Crypto1State *s, uint32_t in, int x) { - uint32_t res_ret = 0; - uint8_t ret; - uint32_t feedin, t, next_in; - for (int i = 31; i >= 0; i--) { - next_in = BEBIT(in, i); - s->odd &= 0xffffff; - t = s->odd, s->odd = s->even, s->even = t; - ret = filter(s->odd); - feedin = ret & (!!x); - feedin ^= s->even & 1; - feedin ^= LF_POLY_EVEN & (s->even >>= 1); - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= !!next_in; - s->even |= (evenparity32(feedin)) << 23; - res_ret |= (ret << (24 ^ i)); - } - return res_ret; + uint32_t res_ret = 0; + uint8_t ret; + uint32_t feedin, t, next_in; + for (int i = 31; i >= 0; i--) { + next_in = BEBIT(in, i); + s->odd &= 0xffffff; + t = s->odd, s->odd = s->even, s->even = t; + ret = filter(s->odd); + feedin = ret & (!!x); + feedin ^= s->even & 1; + feedin ^= LF_POLY_EVEN & (s->even >>= 1); + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even |= (evenparity32(feedin)) << 23; + res_ret |= (ret << (24 ^ i)); + } + return res_ret; } */ -uint8_t napi_lfsr_rollback_bit(struct Crypto1State* s, uint32_t in, int fb) { - int out; - uint8_t ret; - uint32_t t; - s->odd &= 0xffffff; - t = s->odd, s->odd = s->even, s->even = t; +uint8_t napi_lfsr_rollback_bit(struct Crypto1State *s, uint32_t in, int fb) +{ + int out; + uint8_t ret; + uint32_t t; + s->odd &= 0xffffff; + t = s->odd, s->odd = s->even, s->even = t; - out = s->even & 1; - out ^= LF_POLY_EVEN & (s->even >>= 1); - out ^= LF_POLY_ODD & s->odd; - out ^= !!in; - out ^= (ret = filter(s->odd)) & !!fb; + out = s->even & 1; + out ^= LF_POLY_EVEN & (s->even >>= 1); + out ^= LF_POLY_ODD & s->odd; + out ^= !!in; + out ^= (ret = filter(s->odd)) & !!fb; - s->even |= evenparity32(out) << 23; - return ret; + s->even |= evenparity32(out) << 23; + return ret; } -uint32_t napi_lfsr_rollback_word(struct Crypto1State* s, uint32_t in, int fb) { - int i; - uint32_t ret = 0; - for(i = 31; i >= 0; --i) - ret |= napi_lfsr_rollback_bit(s, BEBIT(in, i), fb) << (i ^ 24); - return ret; +uint32_t napi_lfsr_rollback_word(struct Crypto1State *s, uint32_t in, int fb) +{ + int i; + uint32_t ret = 0; + for (i = 31; i >= 0; --i) + ret |= napi_lfsr_rollback_bit(s, BEBIT(in, i), fb) << (i ^ 24); + return ret; } -static inline uint32_t prng_successor(uint32_t x, uint32_t n) { - SWAPENDIAN(x); - while(n--) - x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31; - return SWAPENDIAN(x); +static inline uint32_t prng_successor(uint32_t x, uint32_t n) +{ + SWAPENDIAN(x); + while (n--) + x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31; + return SWAPENDIAN(x); } -#endif // CRYPTO1_H +#endif // CRYPTO1_H \ No newline at end of file diff --git a/applications/system/mfkey/mfkey.c b/applications/system/mfkey/mfkey.c index ae5cc90f2..e49b96263 100644 --- a/applications/system/mfkey/mfkey.c +++ b/applications/system/mfkey/mfkey.c @@ -11,8 +11,6 @@ // TODO: Find ~1 KB memory leak // TODO: Use seednt16 to reduce static encrypted key candidates: https://gist.github.com/noproto/8102f8f32546564cd674256e62ff76ea // https://eprint.iacr.org/2024/1275.pdf section X -// TODO: Static Encrypted: Minimum RAM for adding to keys dict (avoid crashes) -// TODO: Static Encrypted: Optimize KeysDict or buffer keys to write in chunks #include #include @@ -37,879 +35,1345 @@ // TODO: Remove defines that are not needed #define KEYS_DICT_SYSTEM_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc") -#define KEYS_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc") -#define MAX_NAME_LEN 32 -#define MAX_PATH_LEN 64 +#define KEYS_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc") +#define MAX_NAME_LEN 32 +#define MAX_PATH_LEN 64 +#define STATIC_ENCRYPTED_RAM_THRESHOLD 4096 -#define LF_POLY_ODD (0x29CE5C) +#define LF_POLY_ODD (0x29CE5C) #define LF_POLY_EVEN (0x870804) -#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1) -#define CONST_M2_1 (LF_POLY_ODD << 1) -#define CONST_M1_2 (LF_POLY_ODD) -#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1) -#define BIT(x, n) ((x) >> (n) & 1) -#define BEBIT(x, n) BIT(x, (n) ^ 24) +#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1) +#define CONST_M2_1 (LF_POLY_ODD << 1) +#define CONST_M1_2 (LF_POLY_ODD) +#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1) +#define BIT(x, n) ((x) >> (n) & 1) +#define BEBIT(x, n) BIT(x, (n) ^ 24) +#define SWAP(a, b) \ + do \ + { \ + unsigned int t = a; \ + a = b; \ + b = t; \ + } while (0) #define SWAPENDIAN(x) \ - ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) -//#define SIZEOF(arr) sizeof(arr) / sizeof(*arr) + ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) +// #define SIZEOF(arr) sizeof(arr) / sizeof(*arr) -static int eta_round_time = 44; -static int eta_total_time = 705; -// MSB_LIMIT: Chunk size (out of 256) -static int MSB_LIMIT = 16; +// Reduced to 16-bit as these values are small and don't need 32-bit +static int16_t eta_round_time = 44; +static int16_t eta_total_time = 705; +// MSB_LIMIT: Chunk size (out of 256) - can be 8-bit as it's a small value +static uint8_t MSB_LIMIT = 16; + +static inline void flush_key_buffer(ProgramState *program_state) +{ + if (program_state->key_buffer && program_state->key_buffer_count > 0 && program_state->cuid_dict) + { + // Pre-allocate exact size needed: 12 hex chars + 1 newline per key + size_t total_size = program_state->key_buffer_count * 13; + //FURI_LOG_I(TAG, "Flushing key buffer: %d keys", program_state->key_buffer_count); + //FURI_LOG_I(TAG, "Total size: %d bytes", total_size); + char* batch_buffer = malloc(total_size + 1); // +1 for null terminator + + char* ptr = batch_buffer; + const char hex_chars[] = "0123456789ABCDEF"; + + for (size_t i = 0; i < program_state->key_buffer_count; i++) + { + // Convert key to hex string directly into buffer + for (size_t j = 0; j < sizeof(MfClassicKey); j++) + { + uint8_t byte = program_state->key_buffer[i].data[j]; + *ptr++ = hex_chars[byte >> 4]; + *ptr++ = hex_chars[byte & 0x0F]; + } + *ptr++ = '\n'; + } + *ptr = '\0'; + + // Write all keys at once by directly accessing the stream + Stream* stream = program_state->cuid_dict->stream; + uint32_t actual_pos = stream_tell(stream); + + if (stream_seek(stream, 0, StreamOffsetFromEnd) && + stream_write(stream, (uint8_t*)batch_buffer, total_size) == total_size) + { + // Update total key count + program_state->cuid_dict->total_keys += program_state->key_buffer_count; + } + + // May not be needed + stream_seek(stream, actual_pos, StreamOffsetFromStart); + free(batch_buffer); + program_state->key_buffer_count = 0; + } +} static inline int - check_state(struct Crypto1State* t, MfClassicNonce* n, ProgramState* program_state) { - if(!(t->odd | t->even)) return 0; - if(n->attack == mfkey32) { - uint32_t rb = (napi_lfsr_rollback_word(t, 0, 0) ^ n->p64); - if(rb != n->ar0_enc) { - return 0; - } - rollback_word_noret(t, n->nr0_enc, 1); - rollback_word_noret(t, n->uid_xor_nt0, 0); - struct Crypto1State temp = {t->odd, t->even}; - crypt_word_noret(t, n->uid_xor_nt1, 0); - crypt_word_noret(t, n->nr1_enc, 1); - if(n->ar1_enc == (crypt_word(t) ^ n->p64b)) { - crypto1_get_lfsr(&temp, &(n->key)); - return 1; - } - } else if(n->attack == static_nested) { - struct Crypto1State temp = {t->odd, t->even}; - rollback_word_noret(t, n->uid_xor_nt1, 0); - if(n->ks1_1_enc == crypt_word_ret(t, n->uid_xor_nt0, 0)) { - rollback_word_noret(&temp, n->uid_xor_nt1, 0); - crypto1_get_lfsr(&temp, &(n->key)); - return 1; - } - } else if(n->attack == static_encrypted) { - // TODO: Parity bits from rollback_word? - if(n->ks1_1_enc == napi_lfsr_rollback_word(t, n->uid_xor_nt0, 0)) { - // Reduce with parity - uint8_t local_parity_keystream_bits; - struct Crypto1State temp = {t->odd, t->even}; - if((crypt_word_par(&temp, n->uid_xor_nt0, 0, n->nt0, &local_parity_keystream_bits) == - n->ks1_1_enc) && - (local_parity_keystream_bits == n->par_1)) { - // Found key candidate - crypto1_get_lfsr(t, &(n->key)); - program_state->num_candidates++; - keys_dict_add_key(program_state->cuid_dict, n->key.data, sizeof(MfClassicKey)); - } - } - } - return 0; +check_state(struct Crypto1State *t, MfClassicNonce *n, ProgramState *program_state) +{ + if (!(t->odd | t->even)) + return 0; + if (n->attack == mfkey32) + { + uint32_t rb = (napi_lfsr_rollback_word(t, 0, 0) ^ n->p64); + if (rb != n->ar0_enc) + { + return 0; + } + rollback_word_noret(t, n->nr0_enc, 1); + rollback_word_noret(t, n->uid_xor_nt0, 0); + struct Crypto1State temp = {t->odd, t->even}; + crypt_word_noret(t, n->uid_xor_nt1, 0); + crypt_word_noret(t, n->nr1_enc, 1); + if (n->ar1_enc == (crypt_word(t) ^ n->p64b)) + { + crypto1_get_lfsr(&temp, &(n->key)); + return 1; + } + } + else if (n->attack == static_nested) + { + struct Crypto1State temp = {t->odd, t->even}; + rollback_word_noret(t, n->uid_xor_nt1, 0); + if (n->ks1_1_enc == crypt_word_ret(t, n->uid_xor_nt0, 0)) + { + rollback_word_noret(&temp, n->uid_xor_nt1, 0); + crypto1_get_lfsr(&temp, &(n->key)); + return 1; + } + } + else if (n->attack == static_encrypted) + { + // TODO: Parity bits from rollback_word? + if (n->ks1_1_enc == napi_lfsr_rollback_word(t, n->uid_xor_nt0, 0)) + { + // Reduce with parity + uint8_t local_parity_keystream_bits; + struct Crypto1State temp = {t->odd, t->even}; + if ((crypt_word_par(&temp, n->uid_xor_nt0, 0, n->nt0, &local_parity_keystream_bits) == + n->ks1_1_enc) && + (local_parity_keystream_bits == n->par_1)) + { + // Found key candidate + crypto1_get_lfsr(t, &(n->key)); + program_state->num_candidates++; + + // Use key buffer - buffer is guaranteed to be available for static_encrypted + program_state->key_buffer[program_state->key_buffer_count] = n->key; + program_state->key_buffer_count++; + + // Flush buffer when full + if (program_state->key_buffer_count >= program_state->key_buffer_size) + { + flush_key_buffer(program_state); + } + } + } + } + return 0; } -static inline int state_loop( - unsigned int* states_buffer, - int xks, - int m1, - int m2, - unsigned int in, - uint8_t and_val) { - int states_tail = 0; - int round = 0, s = 0, xks_bit = 0, round_in = 0; +static inline __attribute__((hot)) int state_loop( + unsigned int *states_buffer, + int xks, + int m1, + int m2, + unsigned int in, + int and_val) +{ + int states_tail = 0; + int xks_bit = 0, round_in = 0; - for(round = 1; round <= 12; round++) { - xks_bit = BIT(xks, round); - if(round > 4) { - round_in = ((in >> (2 * (round - 4))) & and_val) << 24; - } + // Unroll first 4 rounds (no round_in calculations needed) + // Hoist the filter() calls to just one iteration and reuse the results + // This avoids redundant calculations and improves performance and gives us 2000b of extra ram (11496b free on run) + // V.28/04. Aprox 3s speedup per round. Total 3 keys 7mins 17s!! + for (int round = 1; round <= 4; ++round) + { + xks_bit = BIT(xks, round); + for (int s = 0; s <= states_tail; ++s) + { + unsigned int v = states_buffer[s] << 1; + states_buffer[s] = v; + int f0 = filter(v); + int f1 = filter(v | 1); - for(s = 0; s <= states_tail; s++) { - states_buffer[s] <<= 1; + if (__builtin_expect((f0 ^ f1) != 0, 0)) + { + states_buffer[s] |= f0 ^ xks_bit; + } + else if (__builtin_expect(f0 == xks_bit, 1)) + { + states_buffer[++states_tail] = states_buffer[++s]; + states_buffer[s] = states_buffer[s - 1] | 1; + } + else + { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + } - if((filter(states_buffer[s]) ^ filter(states_buffer[s] | 1)) != 0) { - states_buffer[s] |= filter(states_buffer[s]) ^ xks_bit; - if(round > 4) { - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= round_in; - } - } else if(filter(states_buffer[s]) == xks_bit) { - // TODO: Refactor - if(round > 4) { - states_buffer[++states_tail] = states_buffer[s + 1]; - states_buffer[s + 1] = states_buffer[s] | 1; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s++] ^= round_in; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= round_in; - } else { - states_buffer[++states_tail] = states_buffer[++s]; - states_buffer[s] = states_buffer[s - 1] | 1; - } - } else { - states_buffer[s--] = states_buffer[states_tail--]; - } - } - } + // Round 5 (unrolled) + { + xks_bit = BIT(xks, 5); + int r5_in = ((in >> 2) & and_val) << 24; // 2*(5-4)=2 + for (int s = 0; s <= states_tail; ++s) + { + unsigned int v = states_buffer[s] << 1; + states_buffer[s] = v; + int f0 = filter(v), f1 = filter(v | 1); + if (__builtin_expect((f0 ^ f1) != 0, 0)) + { + states_buffer[s] |= f0 ^ xks_bit; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= r5_in; + } + else if (__builtin_expect(f0 == xks_bit, 1)) + { + states_buffer[++states_tail] = states_buffer[s + 1]; + states_buffer[s + 1] = v | 1; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s++] ^= r5_in; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= r5_in; + } + else + { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + } - return states_tail; + // Round 6 (unrolled) + { + xks_bit = BIT(xks, 6); + int r6_in = ((in >> 4) & and_val) << 24; // 2*(6-4)=4 + for (int s = 0; s <= states_tail; ++s) + { + unsigned int v = states_buffer[s] << 1; + states_buffer[s] = v; + int f0 = filter(v), f1 = filter(v | 1); + if (__builtin_expect((f0 ^ f1) != 0, 0)) + { + states_buffer[s] |= f0 ^ xks_bit; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= r6_in; + } + else if (__builtin_expect(f0 == xks_bit, 1)) + { + states_buffer[++states_tail] = states_buffer[s + 1]; + states_buffer[s + 1] = v | 1; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s++] ^= r6_in; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= r6_in; + } + else + { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + } + + // Loop rounds 7–12 + for (int round = 7; round <= 12; ++round) + { + xks_bit = BIT(xks, round); + round_in = ((in >> (2 * (round - 4))) & and_val) << 24; + for (int s = 0; s <= states_tail; ++s) + { + unsigned int v = states_buffer[s] << 1; + states_buffer[s] = v; + int f0 = filter(v), f1 = filter(v | 1); + if (__builtin_expect((f0 ^ f1) != 0, 0)) + { + states_buffer[s] |= f0 ^ xks_bit; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= round_in; + } + else if (__builtin_expect(f0 == xks_bit, 1)) + { + states_buffer[++states_tail] = states_buffer[s + 1]; + states_buffer[s + 1] = v | 1; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s++] ^= round_in; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= round_in; + } + else + { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + } + + return states_tail; } -int binsearch(unsigned int data[], int start, int stop) { - int mid, val = data[stop] & 0xff000000; - while(start != stop) { - mid = (stop - start) >> 1; - if((data[start + mid] ^ 0x80000000) > (val ^ 0x80000000)) - stop = start + mid; - else - start += mid + 1; - } - return start; +int binsearch(unsigned int data[], int start, int stop) +{ + int mid, val = data[stop] & 0xff000000; + while (start != stop) + { + mid = (stop - start) >> 1; + if ((data[start + mid] ^ 0x80000000) > (val ^ 0x80000000)) + stop = start + mid; + else + start += mid + 1; + } + return start; } -void quicksort(unsigned int array[], int low, int high) { - //if (SIZEOF(array) == 0) - // return; - if(low >= high) return; - int middle = low + (high - low) / 2; - unsigned int pivot = array[middle]; - int i = low, j = high; - while(i <= j) { - while(array[i] < pivot) { - i++; - } - while(array[j] > pivot) { - j--; - } - if(i <= j) { // swap - int temp = array[i]; - array[i] = array[j]; - array[j] = temp; - i++; - j--; - } - } - if(low < j) { - quicksort(array, low, j); - } - if(high > i) { - quicksort(array, i, high); - } + +void quicksort(unsigned int array[], int low, int high) +{ + // Use insertion sort for small arrays (threshold determined by testing) + if (high - low < 16) + { + // Insertion sort + for (int i = low + 1; i <= high; i++) + { + unsigned int key = array[i]; + int j = i - 1; + while (j >= low && array[j] > key) + { + array[j + 1] = array[j]; + j--; + } + array[j + 1] = key; + } + return; + } + + if (low >= high) + return; + + // Median-of-three pivot selection + int middle = low + (high - low) / 2; + if (array[middle] < array[low]) + SWAP(array[middle], array[low]); + if (array[high] < array[low]) + SWAP(array[high], array[low]); + if (array[high] < array[middle]) + SWAP(array[high], array[middle]); + + unsigned int pivot = array[middle]; + + // Rest of quicksort with improved partitioning + int i = low, j = high; + while (i <= j) + { + while (array[i] < pivot) + i++; + while (array[j] > pivot) + j--; + if (i <= j) + { + // swap + unsigned int temp = array[i]; + array[i] = array[j]; + array[j] = temp; + i++; + j--; + } + } + + if (low < j) + quicksort(array, low, j); + if (high > i) + quicksort(array, i, high); } -int extend_table(unsigned int data[], int tbl, int end, int bit, int m1, int m2, unsigned int in) { - in <<= 24; - for(data[tbl] <<= 1; tbl <= end; data[++tbl] <<= 1) { - if((filter(data[tbl]) ^ filter(data[tbl] | 1)) != 0) { - data[tbl] |= filter(data[tbl]) ^ bit; - update_contribution(data, tbl, m1, m2); - data[tbl] ^= in; - } else if(filter(data[tbl]) == bit) { - data[++end] = data[tbl + 1]; - data[tbl + 1] = data[tbl] | 1; - update_contribution(data, tbl, m1, m2); - data[tbl++] ^= in; - update_contribution(data, tbl, m1, m2); - data[tbl] ^= in; - } else { - data[tbl--] = data[end--]; - } - } - return end; + +int extend_table(unsigned int data[], int tbl, int end, int bit, int m1, int m2, unsigned int in) +{ + in <<= 24; + for (data[tbl] <<= 1; tbl <= end; data[++tbl] <<= 1) + { + if ((filter(data[tbl]) ^ filter(data[tbl] | 1)) != 0) + { + data[tbl] |= filter(data[tbl]) ^ bit; + update_contribution(data, tbl, m1, m2); + data[tbl] ^= in; + } + else if (filter(data[tbl]) == bit) + { + data[++end] = data[tbl + 1]; + data[tbl + 1] = data[tbl] | 1; + update_contribution(data, tbl, m1, m2); + data[tbl++] ^= in; + update_contribution(data, tbl, m1, m2); + data[tbl] ^= in; + } + else + { + data[tbl--] = data[end--]; + } + } + return end; } int old_recover( - unsigned int odd[], - int o_head, - int o_tail, - int oks, - unsigned int even[], - int e_head, - int e_tail, - int eks, - int rem, - int s, - MfClassicNonce* n, - unsigned int in, - int first_run, - ProgramState* program_state) { - int o, e, i; - if(rem == -1) { - for(e = e_head; e <= e_tail; ++e) { - even[e] = (even[e] << 1) ^ evenparity32(even[e] & LF_POLY_EVEN) ^ (!!(in & 4)); - for(o = o_head; o <= o_tail; ++o, ++s) { - struct Crypto1State temp = {0, 0}; - temp.even = odd[o]; - temp.odd = even[e] ^ evenparity32(odd[o] & LF_POLY_ODD); - if(check_state(&temp, n, program_state)) { - return -1; - } - } - } - return s; - } - if(first_run == 0) { - for(i = 0; (i < 4) && (rem-- != 0); i++) { - oks >>= 1; - eks >>= 1; - in >>= 2; - o_tail = extend_table( - odd, o_head, o_tail, oks & 1, LF_POLY_EVEN << 1 | 1, LF_POLY_ODD << 1, 0); - if(o_head > o_tail) return s; - e_tail = extend_table( - even, e_head, e_tail, eks & 1, LF_POLY_ODD, LF_POLY_EVEN << 1 | 1, in & 3); - if(e_head > e_tail) return s; - } - } - first_run = 0; - quicksort(odd, o_head, o_tail); - quicksort(even, e_head, e_tail); - while(o_tail >= o_head && e_tail >= e_head) { - if(((odd[o_tail] ^ even[e_tail]) >> 24) == 0) { - o_tail = binsearch(odd, o_head, o = o_tail); - e_tail = binsearch(even, e_head, e = e_tail); - s = old_recover( - odd, - o_tail--, - o, - oks, - even, - e_tail--, - e, - eks, - rem, - s, - n, - in, - first_run, - program_state); - if(s == -1) { - break; - } - } else if((odd[o_tail] ^ 0x80000000) > (even[e_tail] ^ 0x80000000)) { - o_tail = binsearch(odd, o_head, o_tail) - 1; - } else { - e_tail = binsearch(even, e_head, e_tail) - 1; - } - } - return s; + unsigned int odd[], + int o_head, + int o_tail, + int oks, + unsigned int even[], + int e_head, + int e_tail, + int eks, + int rem, + int s, + MfClassicNonce *n, + unsigned int in, + int first_run, + ProgramState *program_state) +{ + int o, e, i; + if (rem == -1) + { + for (e = e_head; e <= e_tail; ++e) + { + even[e] = (even[e] << 1) ^ evenparity32(even[e] & LF_POLY_EVEN) ^ (!!(in & 4)); + for (o = o_head; o <= o_tail; ++o, ++s) + { + struct Crypto1State temp = {0, 0}; + temp.even = odd[o]; + temp.odd = even[e] ^ evenparity32(odd[o] & LF_POLY_ODD); + if (check_state(&temp, n, program_state)) + { + return -1; + } + } + } + return s; + } + if (first_run == 0) + { + for (i = 0; (i < 4) && (rem-- != 0); i++) + { + oks >>= 1; + eks >>= 1; + in >>= 2; + o_tail = extend_table( + odd, o_head, o_tail, oks & 1, LF_POLY_EVEN << 1 | 1, LF_POLY_ODD << 1, 0); + if (o_head > o_tail) + return s; + e_tail = extend_table( + even, e_head, e_tail, eks & 1, LF_POLY_ODD, LF_POLY_EVEN << 1 | 1, in & 3); + if (e_head > e_tail) + return s; + } + } + first_run = 0; + quicksort(odd, o_head, o_tail); + quicksort(even, e_head, e_tail); + while (o_tail >= o_head && e_tail >= e_head) + { + if (((odd[o_tail] ^ even[e_tail]) >> 24) == 0) + { + o_tail = binsearch(odd, o_head, o = o_tail); + e_tail = binsearch(even, e_head, e = e_tail); + s = old_recover( + odd, + o_tail--, + o, + oks, + even, + e_tail--, + e, + eks, + rem, + s, + n, + in, + first_run, + program_state); + if (s == -1) + { + break; + } + } + else if ((odd[o_tail] ^ 0x80000000) > (even[e_tail] ^ 0x80000000)) + { + o_tail = binsearch(odd, o_head, o_tail) - 1; + } + else + { + e_tail = binsearch(even, e_head, e_tail) - 1; + } + } + return s; } -static inline int sync_state(ProgramState* program_state) { - int ts = furi_hal_rtc_get_timestamp(); - int elapsed_time = ts - program_state->eta_timestamp; - if(elapsed_time < program_state->eta_round) { - program_state->eta_round -= elapsed_time; - } else { - program_state->eta_round = 0; - } - if(elapsed_time < program_state->eta_total) { - program_state->eta_total -= elapsed_time; - } else { - program_state->eta_total = 0; - } - program_state->eta_timestamp = ts; - if(program_state->close_thread_please) { - return 1; - } - return 0; +static inline int sync_state(ProgramState *program_state) +{ + int ts = furi_hal_rtc_get_timestamp(); + int elapsed_time = ts - program_state->eta_timestamp; + if (elapsed_time < program_state->eta_round) + { + program_state->eta_round -= elapsed_time; + } + else + { + program_state->eta_round = 0; + } + if (elapsed_time < program_state->eta_total) + { + program_state->eta_total -= elapsed_time; + } + else + { + program_state->eta_total = 0; + } + program_state->eta_timestamp = ts; + if (program_state->close_thread_please) + { + return 1; + } + return 0; } int calculate_msb_tables( - int oks, - int eks, - int msb_round, - MfClassicNonce* n, - unsigned int* states_buffer, - struct Msb* odd_msbs, - struct Msb* even_msbs, - unsigned int* temp_states_odd, - unsigned int* temp_states_even, - unsigned int in, - ProgramState* program_state) { - //FURI_LOG_I(TAG, "MSB GO %i", msb_iter); // DEBUG - unsigned int msb_head = (MSB_LIMIT * msb_round); // msb_iter ranges from 0 to (256/MSB_LIMIT)-1 - unsigned int msb_tail = (MSB_LIMIT * (msb_round + 1)); - int states_tail = 0, tail = 0; - int i = 0, j = 0, semi_state = 0, found = 0; - unsigned int msb = 0; - in = ((in >> 16 & 0xff) | (in << 16) | (in & 0xff00)) << 1; - // TODO: Why is this necessary? - memset(odd_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); - memset(even_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); + int oks, + int eks, + int msb_round, + MfClassicNonce *n, + unsigned int *states_buffer, + struct Msb *odd_msbs, + struct Msb *even_msbs, + unsigned int *temp_states_odd, + unsigned int *temp_states_even, + unsigned int in, + ProgramState *program_state) +{ + unsigned int msb_head = (MSB_LIMIT * msb_round); + unsigned int msb_tail = (MSB_LIMIT * (msb_round + 1)); + int states_tail = 0; + int semi_state = 0; + unsigned int msb = 0; - for(semi_state = 1 << 20; semi_state >= 0; semi_state--) { - if(semi_state % 32768 == 0) { - if(sync_state(program_state) == 1) { - return 0; - } - } + // Preprocessed in value + in = ((in >> 16 & 0xff) | (in << 16) | (in & 0xff00)) << 1; - if(filter(semi_state) == (oks & 1)) { //-V547 - states_buffer[0] = semi_state; - states_tail = state_loop(states_buffer, oks, CONST_M1_1, CONST_M2_1, 0, 0); + // Clear MSB arrays once before loop instead of inside loop + memset(odd_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); + memset(even_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); - for(i = states_tail; i >= 0; i--) { - msb = states_buffer[i] >> 24; - if((msb >= msb_head) && (msb < msb_tail)) { - found = 0; - for(j = 0; j < odd_msbs[msb - msb_head].tail - 1; j++) { - if(odd_msbs[msb - msb_head].states[j] == states_buffer[i]) { - found = 1; - break; - } - } + // Bit values to check - calculate once outside the loop + int oks_bit = oks & 1; + int eks_bit = eks & 1; - if(!found) { - tail = odd_msbs[msb - msb_head].tail++; - odd_msbs[msb - msb_head].states[tail] = states_buffer[i]; - } - } - } - } + // Check for stop request less frequently + int sync_check_interval = 32768 * 2; // Doubled the interval - if(filter(semi_state) == (eks & 1)) { //-V547 - states_buffer[0] = semi_state; - states_tail = state_loop(states_buffer, eks, CONST_M1_2, CONST_M2_2, in, 3); + for (semi_state = 1 << 20; semi_state >= 0; semi_state--) + { + if (semi_state % sync_check_interval == 0) + { + if (sync_state(program_state) == 1) + { + return 0; + } + } - for(i = 0; i <= states_tail; i++) { - msb = states_buffer[i] >> 24; - if((msb >= msb_head) && (msb < msb_tail)) { - found = 0; + // Process both filter conditions in one pass when possible + int filter_semi_state = filter(semi_state); - for(j = 0; j < even_msbs[msb - msb_head].tail; j++) { - if(even_msbs[msb - msb_head].states[j] == states_buffer[i]) { - found = 1; - break; - } - } + // Check oks condition + if (filter_semi_state == oks_bit) + { + states_buffer[0] = semi_state; + states_tail = state_loop(states_buffer, oks, CONST_M1_1, CONST_M2_1, 0, 0); - if(!found) { - tail = even_msbs[msb - msb_head].tail++; - even_msbs[msb - msb_head].states[tail] = states_buffer[i]; - } - } - } - } - } + for (int i = states_tail; i >= 0; i--) + { + msb = states_buffer[i] >> 24; + if ((msb >= msb_head) && (msb < msb_tail)) + { + // Calculate index once + int msb_idx = msb - msb_head; - oks >>= 12; - eks >>= 12; + // Avoid sequential scan by using a direct flag + int found = 0; + for (int j = 0; j < odd_msbs[msb_idx].tail; j++) + { + if (odd_msbs[msb_idx].states[j] == states_buffer[i]) + { + found = 1; + break; + } + } - for(i = 0; i < MSB_LIMIT; i++) { - if(sync_state(program_state) == 1) { - return 0; - } - // TODO: Why is this necessary? - memset(temp_states_even, 0, sizeof(unsigned int) * (1280)); - memset(temp_states_odd, 0, sizeof(unsigned int) * (1280)); - memcpy(temp_states_odd, odd_msbs[i].states, odd_msbs[i].tail * sizeof(unsigned int)); - memcpy(temp_states_even, even_msbs[i].states, even_msbs[i].tail * sizeof(unsigned int)); - int res = old_recover( - temp_states_odd, - 0, - odd_msbs[i].tail, - oks, - temp_states_even, - 0, - even_msbs[i].tail, - eks, - 3, - 0, - n, - in >> 16, - 1, - program_state); - if(res == -1) { - return 1; - } - //odd_msbs[i].tail = 0; - //even_msbs[i].tail = 0; - } + if (!found) + { + int tail = odd_msbs[msb_idx].tail++; + odd_msbs[msb_idx].states[tail] = states_buffer[i]; + } + } + } + } - return 0; + // Check eks condition + if (filter_semi_state == eks_bit) + { + states_buffer[0] = semi_state; + states_tail = state_loop(states_buffer, eks, CONST_M1_2, CONST_M2_2, in, 3); + + for (int i = 0; i <= states_tail; i++) + { + msb = states_buffer[i] >> 24; + if ((msb >= msb_head) && (msb < msb_tail)) + { + // Calculate index once + int msb_idx = msb - msb_head; + + // Avoid sequential scan + int found = 0; + for (int j = 0; j < even_msbs[msb_idx].tail; j++) + { + if (even_msbs[msb_idx].states[j] == states_buffer[i]) + { + found = 1; + break; + } + } + + if (!found) + { + int tail = even_msbs[msb_idx].tail++; + even_msbs[msb_idx].states[tail] = states_buffer[i]; + } + } + } + } + } + + // Shift once outside the loop + oks >>= 12; + eks >>= 12; + + // Process results + for (int i = 0; i < MSB_LIMIT; i++) + { + if ((i % 4) == 0 && sync_state(program_state) == 1) + { + return 0; + } + + // Only clear buffers if they're going to be used + if (odd_msbs[i].tail > 0 || even_msbs[i].tail > 0) + { + memset(temp_states_even, 0, sizeof(unsigned int) * (1280)); + memset(temp_states_odd, 0, sizeof(unsigned int) * (1280)); + memcpy(temp_states_odd, odd_msbs[i].states, odd_msbs[i].tail * sizeof(unsigned int)); + memcpy(temp_states_even, even_msbs[i].states, even_msbs[i].tail * sizeof(unsigned int)); + + int res = old_recover( + temp_states_odd, + 0, + odd_msbs[i].tail, + oks, + temp_states_even, + 0, + even_msbs[i].tail, + eks, + 3, + 0, + n, + in >> 16, + 1, + program_state); + + if (res == -1) + { + return 1; + } + } + } + + return 0; } -void** allocate_blocks(const size_t* block_sizes, int num_blocks) { - void** block_pointers = malloc(num_blocks * sizeof(void*)); +void **allocate_blocks(const size_t *block_sizes, int num_blocks) +{ + void **block_pointers = malloc(num_blocks * sizeof(void *)); + if (!block_pointers) + { + return NULL; + } - for(int i = 0; i < num_blocks; i++) { - if(memmgr_heap_get_max_free_block() < block_sizes[i]) { - // Not enough memory, free previously allocated blocks - for(int j = 0; j < i; j++) { - free(block_pointers[j]); - } - free(block_pointers); - return NULL; - } + for (int i = 0; i < num_blocks; i++) + { + if (memmgr_heap_get_max_free_block() < block_sizes[i]) + { + // Not enough memory, free previously allocated blocks + for (int j = 0; j < i; j++) + { + free(block_pointers[j]); + } + free(block_pointers); + return NULL; + } - block_pointers[i] = malloc(block_sizes[i]); - } + block_pointers[i] = malloc(block_sizes[i]); + if (!block_pointers[i]) + { + // Allocation failed + for (int j = 0; j < i; j++) + { + free(block_pointers[j]); + } + free(block_pointers); + return NULL; + } + } - return block_pointers; + return block_pointers; } -bool is_full_speed() { - return MSB_LIMIT == 16; -} +bool recover(MfClassicNonce *n, int ks2, unsigned int in, ProgramState *program_state) +{ + bool found = false; + const size_t block_sizes[] = {49216, 49216, 5120, 5120, 4096}; + const size_t reduced_block_sizes[] = {24608, 24608, 5120, 5120, 4096}; + const int num_blocks = sizeof(block_sizes) / sizeof(block_sizes[0]); + // Reset globals each nonce + eta_round_time = 44; + eta_total_time = 705; + MSB_LIMIT = 16; + + // Use half speed (reduced block sizes) for static encrypted nonces so we can buffer keys + bool use_half_speed = (n->attack == static_encrypted); + if (use_half_speed) + { + //eta_round_time *= 2; + eta_total_time *= 2; + MSB_LIMIT /= 2; + } -bool recover(MfClassicNonce* n, int ks2, unsigned int in, ProgramState* program_state) { - bool found = false; - const size_t block_sizes[] = {49216, 49216, 5120, 5120, 4096}; - const size_t reduced_block_sizes[] = {24608, 24608, 5120, 5120, 4096}; - const int num_blocks = sizeof(block_sizes) / sizeof(block_sizes[0]); - void** block_pointers = allocate_blocks(block_sizes, num_blocks); - if(block_pointers == NULL) { - // System has less than the guaranteed amount of RAM (140 KB) - adjust some parameters to run anyway at half speed - if(is_full_speed()) { - //eta_round_time *= 2; - eta_total_time *= 2; - MSB_LIMIT /= 2; - } - block_pointers = allocate_blocks(reduced_block_sizes, num_blocks); - if(block_pointers == NULL) { - // System has less than 70 KB of RAM - should never happen so we don't reduce speed further - program_state->err = InsufficientRAM; - program_state->mfkey_state = Error; - return false; - } - } - // Adjust estimates for static encrypted attacks - if(n->attack == static_encrypted) { - eta_round_time *= 4; - eta_total_time *= 4; - if(is_full_speed()) { - eta_round_time *= 4; - eta_total_time *= 4; - } - } - struct Msb* odd_msbs = block_pointers[0]; - struct Msb* even_msbs = block_pointers[1]; - unsigned int* temp_states_odd = block_pointers[2]; - unsigned int* temp_states_even = block_pointers[3]; - unsigned int* states_buffer = block_pointers[4]; - int oks = 0, eks = 0; - int i = 0, msb = 0; - for(i = 31; i >= 0; i -= 2) { - oks = oks << 1 | BEBIT(ks2, i); - } - for(i = 30; i >= 0; i -= 2) { - eks = eks << 1 | BEBIT(ks2, i); - } - int bench_start = furi_hal_rtc_get_timestamp(); - program_state->eta_total = eta_total_time; - program_state->eta_timestamp = bench_start; - for(msb = 0; msb <= ((256 / MSB_LIMIT) - 1); msb++) { - program_state->search = msb; - program_state->eta_round = eta_round_time; - program_state->eta_total = eta_total_time - (eta_round_time * msb); - if(calculate_msb_tables( - oks, - eks, - msb, - n, - states_buffer, - odd_msbs, - even_msbs, - temp_states_odd, - temp_states_even, - in, - program_state)) { - //int bench_stop = furi_hal_rtc_get_timestamp(); - //FURI_LOG_I(TAG, "Cracked in %i seconds", bench_stop - bench_start); - found = true; - break; - } - if(program_state->close_thread_please) { - break; - } - } - // Free the allocated blocks - for(int i = 0; i < num_blocks; i++) { - free(block_pointers[i]); - } - free(block_pointers); - return found; + void **block_pointers = allocate_blocks(use_half_speed ? reduced_block_sizes : block_sizes, num_blocks); + if (block_pointers == NULL) { + if (n->attack != static_encrypted) + { + // System has less than the guaranteed amount of RAM (140 KB) - adjust some parameters to run anyway at half speed + // eta_round_time *= 2; + eta_total_time *= 2; + MSB_LIMIT /= 2; + block_pointers = allocate_blocks(reduced_block_sizes, num_blocks); + if (block_pointers == NULL) + { + // System has less than 70 KB of RAM - should never happen so we don't reduce speed further + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } else + { + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } + struct Msb *odd_msbs = block_pointers[0]; + struct Msb *even_msbs = block_pointers[1]; + unsigned int *temp_states_odd = block_pointers[2]; + unsigned int *temp_states_even = block_pointers[3]; + unsigned int *states_buffer = block_pointers[4]; + + // Allocate key buffer for static encrypted nonces + if (n->attack == static_encrypted) + { + size_t available_ram = memmgr_heap_get_max_free_block(); + // Each key becomes 12 hex chars + 1 newline = 13 bytes in the batch string + // Plus original 6 bytes in buffer = 19 bytes total per key + // Add extra safety margin for string overhead and other allocations + const size_t safety_threshold = STATIC_ENCRYPTED_RAM_THRESHOLD; + const size_t bytes_per_key = sizeof(MfClassicKey) + 13; // buffer + string representation + if (available_ram > safety_threshold) + { + program_state->key_buffer_size = (available_ram - safety_threshold) / bytes_per_key; + program_state->key_buffer = malloc(program_state->key_buffer_size * sizeof(MfClassicKey)); + program_state->key_buffer_count = 0; + if (!program_state->key_buffer) + { + // Free the allocated blocks before returning + for (int i = 0; i < num_blocks; i++) + { + free(block_pointers[i]); + } + free(block_pointers); + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } + else + { + // Free the allocated blocks before returning + for (int i = 0; i < num_blocks; i++) + { + free(block_pointers[i]); + } + free(block_pointers); + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } + else + { + program_state->key_buffer = NULL; + program_state->key_buffer_size = 0; + program_state->key_buffer_count = 0; + } + + int oks = 0, eks = 0; + int i = 0, msb = 0; + for (i = 31; i >= 0; i -= 2) + { + oks = oks << 1 | BEBIT(ks2, i); + } + for (i = 30; i >= 0; i -= 2) + { + eks = eks << 1 | BEBIT(ks2, i); + } + int bench_start = furi_hal_rtc_get_timestamp(); + program_state->eta_total = eta_total_time; + program_state->eta_timestamp = bench_start; + for (msb = 0; msb <= ((256 / MSB_LIMIT) - 1); msb++) + { + program_state->search = msb; + program_state->eta_round = eta_round_time; + program_state->eta_total = eta_total_time - (eta_round_time * msb); + if (calculate_msb_tables( + oks, + eks, + msb, + n, + states_buffer, + odd_msbs, + even_msbs, + temp_states_odd, + temp_states_even, + in, + program_state)) + { + // int bench_stop = furi_hal_rtc_get_timestamp(); + // FURI_LOG_I(TAG, "Cracked in %i seconds", bench_stop - bench_start); + found = true; + break; + } + if (program_state->close_thread_please) + { + break; + } + } + + // Final flush and cleanup for key buffer + if (n->attack == static_encrypted && program_state->key_buffer) + { + flush_key_buffer(program_state); + free(program_state->key_buffer); + program_state->key_buffer = NULL; + program_state->key_buffer_size = 0; + program_state->key_buffer_count = 0; + } + + // Free the allocated blocks + for (int i = 0; i < num_blocks; i++) + { + free(block_pointers[i]); + } + free(block_pointers); + return found; } bool key_already_found_for_nonce_in_solved( - MfClassicKey* keyarray, - int keyarray_size, - MfClassicNonce* nonce) { - for(int k = 0; k < keyarray_size; k++) { - uint64_t key_as_int = bit_lib_bytes_to_num_be(keyarray[k].data, sizeof(MfClassicKey)); - struct Crypto1State temp = {0, 0}; - for(int i = 0; i < 24; i++) { - (&temp)->odd |= (BIT(key_as_int, 2 * i + 1) << (i ^ 3)); - (&temp)->even |= (BIT(key_as_int, 2 * i) << (i ^ 3)); - } - if(nonce->attack == mfkey32) { - crypt_word_noret(&temp, nonce->uid_xor_nt1, 0); - crypt_word_noret(&temp, nonce->nr1_enc, 1); - if(nonce->ar1_enc == (crypt_word(&temp) ^ nonce->p64b)) { - return true; - } - } else if(nonce->attack == static_nested) { - uint32_t expected_ks1 = crypt_word_ret(&temp, nonce->uid_xor_nt0, 0); - if(nonce->ks1_1_enc == expected_ks1) { - return true; - } - } - } - return false; + MfClassicKey *keyarray, + int keyarray_size, + MfClassicNonce *nonce) +{ + for (int k = 0; k < keyarray_size; k++) + { + uint64_t key_as_int = bit_lib_bytes_to_num_be(keyarray[k].data, sizeof(MfClassicKey)); + struct Crypto1State temp = {0, 0}; + for (int i = 0; i < 24; i++) + { + (&temp)->odd |= (BIT(key_as_int, 2 * i + 1) << (i ^ 3)); + (&temp)->even |= (BIT(key_as_int, 2 * i) << (i ^ 3)); + } + if (nonce->attack == mfkey32) + { + crypt_word_noret(&temp, nonce->uid_xor_nt1, 0); + crypt_word_noret(&temp, nonce->nr1_enc, 1); + if (nonce->ar1_enc == (crypt_word(&temp) ^ nonce->p64b)) + { + return true; + } + } + else if (nonce->attack == static_nested) + { + uint32_t expected_ks1 = crypt_word_ret(&temp, nonce->uid_xor_nt0, 0); + if (nonce->ks1_1_enc == expected_ks1) + { + return true; + } + } + } + return false; } #pragma GCC push_options #pragma GCC optimize("Os") -static void finished_beep() { - // Beep to indicate completion - NotificationApp* notification = furi_record_open("notification"); - notification_message(notification, &sequence_audiovisual_alert); - notification_message(notification, &sequence_display_backlight_on); - furi_record_close("notification"); +static void finished_beep() +{ + // Beep to indicate completion + NotificationApp *notification = furi_record_open("notification"); + notification_message(notification, &sequence_audiovisual_alert); + notification_message(notification, &sequence_display_backlight_on); + furi_record_close("notification"); } -void mfkey(ProgramState* program_state) { - uint32_t ks_enc = 0, nt_xor_uid = 0; - MfClassicKey found_key; // Recovered key - size_t keyarray_size = 0; - MfClassicKey* keyarray = malloc(sizeof(MfClassicKey) * 1); - uint32_t i = 0, j = 0; - //FURI_LOG_I(TAG, "Free heap before alloc(): %zub", memmgr_get_free_heap()); - Storage* storage = furi_record_open(RECORD_STORAGE); - FlipperApplication* app = flipper_application_alloc(storage, firmware_api_interface); - flipper_application_preload(app, APP_ASSETS_PATH("plugins/mfkey_init_plugin.fal")); - flipper_application_map_to_memory(app); - const FlipperAppPluginDescriptor* app_descriptor = - flipper_application_plugin_get_descriptor(app); - const MfkeyPlugin* init_plugin = app_descriptor->entry_point; - // Check for nonces - program_state->mfkey32_present = init_plugin->napi_mf_classic_mfkey32_nonces_check_presence(); - program_state->nested_present = init_plugin->napi_mf_classic_nested_nonces_check_presence(); - if(!(program_state->mfkey32_present) && !(program_state->nested_present)) { - program_state->err = MissingNonces; - program_state->mfkey_state = Error; - flipper_application_free(app); - furi_record_close(RECORD_STORAGE); - free(keyarray); - return; - } - // Read dictionaries (optional) - KeysDict* system_dict = {0}; - bool system_dict_exists = keys_dict_check_presence(KEYS_DICT_SYSTEM_PATH); - KeysDict* user_dict = {0}; - bool user_dict_exists = keys_dict_check_presence(KEYS_DICT_USER_PATH); - uint32_t total_dict_keys = 0; - if(system_dict_exists) { - system_dict = - keys_dict_alloc(KEYS_DICT_SYSTEM_PATH, KeysDictModeOpenExisting, sizeof(MfClassicKey)); - total_dict_keys += keys_dict_get_total_keys(system_dict); - } - user_dict = keys_dict_alloc(KEYS_DICT_USER_PATH, KeysDictModeOpenAlways, sizeof(MfClassicKey)); - if(user_dict_exists) { - total_dict_keys += keys_dict_get_total_keys(user_dict); - } - user_dict_exists = true; - program_state->dict_count = total_dict_keys; - program_state->mfkey_state = DictionaryAttack; - // Read nonces - MfClassicNonceArray* nonce_arr; - nonce_arr = init_plugin->napi_mf_classic_nonce_array_alloc( - system_dict, system_dict_exists, user_dict, program_state); - if(system_dict_exists) { - keys_dict_free(system_dict); - } - if(nonce_arr->total_nonces == 0) { - // Nothing to crack - program_state->err = ZeroNonces; - program_state->mfkey_state = Error; - init_plugin->napi_mf_classic_nonce_array_free(nonce_arr); - flipper_application_free(app); - furi_record_close(RECORD_STORAGE); - keys_dict_free(user_dict); - free(keyarray); - return; - } - flipper_application_free(app); - furi_record_close(RECORD_STORAGE); - // TODO: Track free state at the time this is called to ensure double free does not happen - furi_assert(nonce_arr); - furi_assert(nonce_arr->stream); - // TODO: Already closed? - buffered_file_stream_close(nonce_arr->stream); - stream_free(nonce_arr->stream); - //FURI_LOG_I(TAG, "Free heap after free(): %zub", memmgr_get_free_heap()); - program_state->mfkey_state = MFKeyAttack; - // TODO: Work backwards on this array and free memory - for(i = 0; i < nonce_arr->total_nonces; i++) { - MfClassicNonce next_nonce = nonce_arr->remaining_nonce_array[i]; - if(key_already_found_for_nonce_in_solved(keyarray, keyarray_size, &next_nonce)) { - nonce_arr->remaining_nonces--; - (program_state->cracked)++; - (program_state->num_completed)++; - continue; - } - //FURI_LOG_I(TAG, "Beginning recovery for %8lx", next_nonce.uid); - FuriString* cuid_dict_path; - switch(next_nonce.attack) { - case mfkey32: - ks_enc = next_nonce.ar0_enc ^ next_nonce.p64; - nt_xor_uid = 0; - break; - case static_nested: - ks_enc = next_nonce.ks1_2_enc; - nt_xor_uid = next_nonce.uid_xor_nt1; - break; - case static_encrypted: - ks_enc = next_nonce.ks1_1_enc; - nt_xor_uid = next_nonce.uid_xor_nt0; - cuid_dict_path = furi_string_alloc_printf( - "%s/mf_classic_dict_%08lx.nfc", EXT_PATH("nfc/assets"), next_nonce.uid); - // May need RECORD_STORAGE? - program_state->cuid_dict = keys_dict_alloc( - furi_string_get_cstr(cuid_dict_path), - KeysDictModeOpenAlways, - sizeof(MfClassicKey)); - break; - } +void mfkey(ProgramState *program_state) +{ + uint32_t ks_enc = 0, nt_xor_uid = 0; + MfClassicKey found_key; // Recovered key + size_t keyarray_size = 0; + MfClassicKey *keyarray = malloc(sizeof(MfClassicKey) * 1); + if (!keyarray) + { + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return; + } - if(!recover(&next_nonce, ks_enc, nt_xor_uid, program_state)) { - if((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) { - keys_dict_free(program_state->cuid_dict); - } - if(program_state->close_thread_please) { - break; - } - // No key found in recover() or static encrypted - (program_state->num_completed)++; - continue; - } - (program_state->cracked)++; - (program_state->num_completed)++; - found_key = next_nonce.key; - bool already_found = false; - for(j = 0; j < keyarray_size; j++) { - if(memcmp(keyarray[j].data, found_key.data, MF_CLASSIC_KEY_SIZE) == 0) { - already_found = true; - break; - } - } - if(already_found == false) { - // New key - keyarray = realloc(keyarray, sizeof(MfClassicKey) * (keyarray_size + 1)); //-V701 - keyarray_size += 1; - keyarray[keyarray_size - 1] = found_key; - (program_state->unique_cracked)++; - } - } - // TODO: Update display to show all keys were found - // TODO: Prepend found key(s) to user dictionary file - //FURI_LOG_I(TAG, "Unique keys found:"); - for(i = 0; i < keyarray_size; i++) { - //FURI_LOG_I(TAG, "%012" PRIx64, keyarray[i]); - keys_dict_add_key(user_dict, keyarray[i].data, sizeof(MfClassicKey)); - } - if(keyarray_size > 0) { - dolphin_deed(DolphinDeedNfcMfcAdd); - } - free(nonce_arr); - keys_dict_free(user_dict); - free(keyarray); - if(program_state->mfkey_state == Error) { - return; - } - //FURI_LOG_I(TAG, "mfkey function completed normally"); // DEBUG - program_state->mfkey_state = Complete; - // No need to alert the user if they asked it to stop - if(!(program_state->close_thread_please)) { - finished_beep(); - } - return; + uint32_t i = 0, j = 0; + // FURI_LOG_I(TAG, "Free heap before alloc(): %zub", memmgr_get_free_heap()); + Storage *storage = furi_record_open(RECORD_STORAGE); + FlipperApplication *app = flipper_application_alloc(storage, firmware_api_interface); + flipper_application_preload(app, APP_ASSETS_PATH("plugins/mfkey_init_plugin.fal")); + flipper_application_map_to_memory(app); + const FlipperAppPluginDescriptor *app_descriptor = + flipper_application_plugin_get_descriptor(app); + const MfkeyPlugin *init_plugin = app_descriptor->entry_point; + // Check for nonces + program_state->mfkey32_present = init_plugin->napi_mf_classic_mfkey32_nonces_check_presence(); + program_state->nested_present = init_plugin->napi_mf_classic_nested_nonces_check_presence(); + if (!(program_state->mfkey32_present) && !(program_state->nested_present)) + { + program_state->err = MissingNonces; + program_state->mfkey_state = Error; + flipper_application_free(app); + furi_record_close(RECORD_STORAGE); + free(keyarray); + return; + } + // Read dictionaries (optional) + KeysDict *system_dict = {0}; + bool system_dict_exists = keys_dict_check_presence(KEYS_DICT_SYSTEM_PATH); + KeysDict *user_dict = {0}; + bool user_dict_exists = keys_dict_check_presence(KEYS_DICT_USER_PATH); + uint32_t total_dict_keys = 0; + if (system_dict_exists) + { + system_dict = + keys_dict_alloc(KEYS_DICT_SYSTEM_PATH, KeysDictModeOpenExisting, sizeof(MfClassicKey)); + total_dict_keys += keys_dict_get_total_keys(system_dict); + } + user_dict = keys_dict_alloc(KEYS_DICT_USER_PATH, KeysDictModeOpenAlways, sizeof(MfClassicKey)); + if (user_dict_exists) + { + total_dict_keys += keys_dict_get_total_keys(user_dict); + } + user_dict_exists = true; + program_state->dict_count = total_dict_keys; + program_state->mfkey_state = DictionaryAttack; + // Read nonces + MfClassicNonceArray *nonce_arr; + nonce_arr = init_plugin->napi_mf_classic_nonce_array_alloc( + system_dict, system_dict_exists, user_dict, program_state); + if (system_dict_exists) + { + keys_dict_free(system_dict); + } + if (nonce_arr->total_nonces == 0) + { + // Nothing to crack + program_state->err = ZeroNonces; + program_state->mfkey_state = Error; + init_plugin->napi_mf_classic_nonce_array_free(nonce_arr); + flipper_application_free(app); + furi_record_close(RECORD_STORAGE); + keys_dict_free(user_dict); + free(keyarray); + return; + } + flipper_application_free(app); + furi_record_close(RECORD_STORAGE); + // TODO: Track free state at the time this is called to ensure double free does not happen + furi_assert(nonce_arr); + furi_assert(nonce_arr->stream); + // TODO: Already closed? + buffered_file_stream_close(nonce_arr->stream); + stream_free(nonce_arr->stream); + // FURI_LOG_I(TAG, "Free heap after free(): %zub", memmgr_get_free_heap()); + program_state->mfkey_state = MFKeyAttack; + // TODO: Work backwards on this array and free memory + for (i = 0; i < nonce_arr->total_nonces; i++) + { + MfClassicNonce next_nonce = nonce_arr->remaining_nonce_array[i]; + if (key_already_found_for_nonce_in_solved(keyarray, keyarray_size, &next_nonce)) + { + nonce_arr->remaining_nonces--; + (program_state->cracked)++; + (program_state->num_completed)++; + continue; + } + // FURI_LOG_I(TAG, "Beginning recovery for %8lx", next_nonce.uid); + FuriString *cuid_dict_path; + switch (next_nonce.attack) + { + case mfkey32: + ks_enc = next_nonce.ar0_enc ^ next_nonce.p64; + nt_xor_uid = 0; + break; + case static_nested: + ks_enc = next_nonce.ks1_2_enc; + nt_xor_uid = next_nonce.uid_xor_nt1; + break; + case static_encrypted: + ks_enc = next_nonce.ks1_1_enc; + nt_xor_uid = next_nonce.uid_xor_nt0; + cuid_dict_path = furi_string_alloc_printf( + "%s/mf_classic_dict_%08lx.nfc", EXT_PATH("nfc/assets"), next_nonce.uid); + // May need RECORD_STORAGE? + program_state->cuid_dict = keys_dict_alloc( + furi_string_get_cstr(cuid_dict_path), + KeysDictModeOpenAlways, + sizeof(MfClassicKey)); + furi_string_free(cuid_dict_path); + break; + } + + if (!recover(&next_nonce, ks_enc, nt_xor_uid, program_state)) + { + // Check for non-recoverable errors and break the loop + if (program_state->mfkey_state == Error) + { + if ((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) + { + keys_dict_free(program_state->cuid_dict); + program_state->cuid_dict = NULL; + } + break; + } + if (program_state->close_thread_please) + { + if ((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) + { + keys_dict_free(program_state->cuid_dict); + program_state->cuid_dict = NULL; + } + break; + } + // No key found in recover() or static encrypted + (program_state->num_completed)++; + // Free CUID dictionary after each static_encrypted nonce processing + if ((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) + { + keys_dict_free(program_state->cuid_dict); + program_state->cuid_dict = NULL; + } + continue; + } + (program_state->cracked)++; + (program_state->num_completed)++; + found_key = next_nonce.key; + bool already_found = false; + for (j = 0; j < keyarray_size; j++) + { + if (memcmp(keyarray[j].data, found_key.data, MF_CLASSIC_KEY_SIZE) == 0) + { + already_found = true; + break; + } + } + if (already_found == false) + { + // New key + MfClassicKey *new_keyarray = realloc(keyarray, sizeof(MfClassicKey) * (keyarray_size + 1)); + if (!new_keyarray) + { + // Realloc failed - continue with existing keyarray + FURI_LOG_E(TAG, "Failed to realloc keyarray"); + } + else + { + keyarray = new_keyarray; + keyarray_size += 1; + keyarray[keyarray_size - 1] = found_key; + (program_state->unique_cracked)++; + } + } + } + // TODO: Update display to show all keys were found + // TODO: Prepend found key(s) to user dictionary file + // FURI_LOG_I(TAG, "Unique keys found:"); + for (i = 0; i < keyarray_size; i++) + { + // FURI_LOG_I(TAG, "%012" PRIx64, keyarray[i]); + keys_dict_add_key(user_dict, keyarray[i].data, sizeof(MfClassicKey)); + } + if (keyarray_size > 0) + { + dolphin_deed(DolphinDeedNfcKeyAdd); + } + free(nonce_arr); + keys_dict_free(user_dict); + free(keyarray); + if (program_state->mfkey_state == Error) + { + return; + } + // FURI_LOG_I(TAG, "mfkey function completed normally"); // DEBUG + program_state->mfkey_state = Complete; + // No need to alert the user if they asked it to stop + if (!(program_state->close_thread_please)) + { + finished_beep(); + } + return; } // Screen is 128x64 px -static void render_callback(Canvas* const canvas, void* ctx) { - furi_assert(ctx); - ProgramState* program_state = ctx; - furi_mutex_acquire(program_state->mutex, FuriWaitForever); - char draw_str[44] = {}; +static void render_callback(Canvas *const canvas, void *ctx) +{ + furi_assert(ctx); + ProgramState *program_state = ctx; + furi_mutex_acquire(program_state->mutex, FuriWaitForever); + char draw_str[44] = {}; - canvas_draw_frame(canvas, 0, 0, 128, 64); - canvas_draw_frame(canvas, 0, 15, 128, 64); + canvas_draw_frame(canvas, 0, 0, 128, 64); + canvas_draw_frame(canvas, 0, 15, 128, 64); - // FontSecondary by default, title is drawn at the end - snprintf(draw_str, sizeof(draw_str), "RAM: %zub", memmgr_get_free_heap()); - canvas_draw_str_aligned(canvas, 48, 5, AlignLeft, AlignTop, draw_str); - canvas_draw_icon(canvas, 114, 4, &I_mfkey); - if(program_state->mfkey_state == MFKeyAttack) { - float eta_round = (float)1 - ((float)program_state->eta_round / (float)eta_round_time); - float eta_total = (float)1 - ((float)program_state->eta_total / (float)eta_total_time); - float progress = (float)program_state->num_completed / (float)program_state->total; - if(eta_round < 0 || eta_round > 1) { - // Round ETA miscalculated - eta_round = 1; - program_state->eta_round = 0; - } - if(eta_total < 0 || eta_round > 1) { - // Total ETA miscalculated - eta_total = 1; - program_state->eta_total = 0; - } - snprintf( - draw_str, - sizeof(draw_str), - "Cracking: %d/%d - in prog.", - program_state->num_completed, - program_state->total); - elements_progress_bar_with_text(canvas, 5, 18, 118, progress, draw_str); - snprintf( - draw_str, - sizeof(draw_str), - "Round: %d/%d - ETA %02d Sec", - (program_state->search) + 1, // Zero indexed - 256 / MSB_LIMIT, - program_state->eta_round); - elements_progress_bar_with_text(canvas, 5, 31, 118, eta_round, draw_str); - snprintf(draw_str, sizeof(draw_str), "Total ETA %03d Sec", program_state->eta_total); - elements_progress_bar_with_text(canvas, 5, 44, 118, eta_total, draw_str); - } else if(program_state->mfkey_state == DictionaryAttack) { - snprintf( - draw_str, sizeof(draw_str), "Dict solves: %d (in progress)", program_state->cracked); - canvas_draw_str_aligned(canvas, 10, 18, AlignLeft, AlignTop, draw_str); - snprintf(draw_str, sizeof(draw_str), "Keys in dict: %d", program_state->dict_count); - canvas_draw_str_aligned(canvas, 26, 28, AlignLeft, AlignTop, draw_str); - } else if(program_state->mfkey_state == Complete) { - // TODO: Scrollable list view to see cracked keys if user presses down - elements_progress_bar(canvas, 5, 18, 118, 1); - canvas_draw_str_aligned(canvas, 64, 31, AlignCenter, AlignTop, "Complete"); - snprintf( - draw_str, - sizeof(draw_str), - "Keys added to user dict: %d", - program_state->unique_cracked); - canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignTop, draw_str); - if(program_state->num_candidates > 0) { - snprintf( - draw_str, - sizeof(draw_str), - "SEN key candidates: %d", - program_state->num_candidates); - canvas_draw_str_aligned(canvas, 64, 51, AlignCenter, AlignTop, draw_str); - } - } else if(program_state->mfkey_state == Ready) { - canvas_draw_str_aligned(canvas, 50, 30, AlignLeft, AlignTop, "Ready"); - elements_button_center(canvas, "Start"); - elements_button_right(canvas, "Help"); - } else if(program_state->mfkey_state == Help) { - canvas_draw_str_aligned(canvas, 7, 20, AlignLeft, AlignTop, "Collect nonces by reading"); - canvas_draw_str_aligned(canvas, 7, 30, AlignLeft, AlignTop, "tag or reader in NFC app:"); - canvas_draw_str_aligned(canvas, 7, 40, AlignLeft, AlignTop, "https://docs.flipper.net/"); - canvas_draw_str_aligned(canvas, 7, 50, AlignLeft, AlignTop, "nfc/mfkey32"); - } else if(program_state->mfkey_state == Error) { - canvas_draw_str_aligned(canvas, 50, 25, AlignLeft, AlignTop, "Error"); - if(program_state->err == MissingNonces) { - canvas_draw_str_aligned(canvas, 25, 36, AlignLeft, AlignTop, "No nonces found"); - } else if(program_state->err == ZeroNonces) { - canvas_draw_str_aligned(canvas, 15, 36, AlignLeft, AlignTop, "Nonces already cracked"); - } else if(program_state->err == InsufficientRAM) { - canvas_draw_str_aligned(canvas, 35, 36, AlignLeft, AlignTop, "No free RAM"); - } else { - // Unhandled error - } - } else { - // Unhandled program state - } - // Title - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 5, 4, AlignLeft, AlignTop, "MFKey"); - furi_mutex_release(program_state->mutex); + // FontSecondary by default, title is drawn at the end + snprintf(draw_str, sizeof(draw_str), "RAM: %zub", memmgr_get_free_heap()); + canvas_draw_str_aligned(canvas, 48, 5, AlignLeft, AlignTop, draw_str); + canvas_draw_icon(canvas, 114, 4, &I_mfkey); + if (program_state->mfkey_state == MFKeyAttack) + { + float eta_round = (float)1 - ((float)program_state->eta_round / (float)eta_round_time); + float eta_total = (float)1 - ((float)program_state->eta_total / (float)eta_total_time); + float progress = (float)program_state->num_completed / (float)program_state->total; + if (eta_round < 0 || eta_round > 1) + { + // Round ETA miscalculated + eta_round = 1; + program_state->eta_round = 0; + } + if (eta_total < 0 || eta_round > 1) + { + // Total ETA miscalculated + eta_total = 1; + program_state->eta_total = 0; + } + snprintf( + draw_str, + sizeof(draw_str), + "Cracking: %d/%d - in prog.", + program_state->num_completed, + program_state->total); + elements_progress_bar_with_text(canvas, 5, 18, 118, progress, draw_str); + snprintf( + draw_str, + sizeof(draw_str), + "Round: %d/%d - ETA %02d Sec", + (program_state->search) + 1, // Zero indexed + 256 / MSB_LIMIT, + program_state->eta_round); + elements_progress_bar_with_text(canvas, 5, 31, 118, eta_round, draw_str); + snprintf(draw_str, sizeof(draw_str), "Total ETA %03d Sec", program_state->eta_total); + elements_progress_bar_with_text(canvas, 5, 44, 118, eta_total, draw_str); + } + else if (program_state->mfkey_state == DictionaryAttack) + { + snprintf( + draw_str, sizeof(draw_str), "Dict solves: %d (in progress)", program_state->cracked); + canvas_draw_str_aligned(canvas, 10, 18, AlignLeft, AlignTop, draw_str); + snprintf(draw_str, sizeof(draw_str), "Keys in dict: %d", program_state->dict_count); + canvas_draw_str_aligned(canvas, 26, 28, AlignLeft, AlignTop, draw_str); + } + else if (program_state->mfkey_state == Complete) + { + // TODO: Scrollable list view to see cracked keys if user presses down + elements_progress_bar(canvas, 5, 18, 118, 1); + canvas_draw_str_aligned(canvas, 64, 31, AlignCenter, AlignTop, "Complete"); + snprintf( + draw_str, + sizeof(draw_str), + "Keys added to user dict: %d", + program_state->unique_cracked); + canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignTop, draw_str); + if (program_state->num_candidates > 0) + { + snprintf( + draw_str, + sizeof(draw_str), + "SEN key candidates: %d", + program_state->num_candidates); + canvas_draw_str_aligned(canvas, 64, 51, AlignCenter, AlignTop, draw_str); + } + } + else if (program_state->mfkey_state == Ready) + { + canvas_draw_str_aligned(canvas, 50, 30, AlignLeft, AlignTop, "Ready"); + elements_button_center(canvas, "Start"); + elements_button_right(canvas, "Help"); + } + else if (program_state->mfkey_state == Help) + { + canvas_draw_str_aligned(canvas, 7, 20, AlignLeft, AlignTop, "Collect nonces by reading"); + canvas_draw_str_aligned(canvas, 7, 30, AlignLeft, AlignTop, "tag or reader in NFC app:"); + canvas_draw_str_aligned(canvas, 7, 40, AlignLeft, AlignTop, "https://docs.flipper.net/"); + canvas_draw_str_aligned(canvas, 7, 50, AlignLeft, AlignTop, "nfc/mfkey32"); + } + else if (program_state->mfkey_state == Error) + { + canvas_draw_str_aligned(canvas, 50, 25, AlignLeft, AlignTop, "Error"); + if (program_state->err == MissingNonces) + { + canvas_draw_str_aligned(canvas, 25, 36, AlignLeft, AlignTop, "No nonces found"); + } + else if (program_state->err == ZeroNonces) + { + canvas_draw_str_aligned(canvas, 15, 36, AlignLeft, AlignTop, "Nonces already cracked"); + } + else if (program_state->err == InsufficientRAM) + { + canvas_draw_str_aligned(canvas, 35, 36, AlignLeft, AlignTop, "No free RAM"); + } + else + { + // Unhandled error + } + } + else + { + // Unhandled program state + } + // Title + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 5, 4, AlignLeft, AlignTop, "MFKey"); + furi_mutex_release(program_state->mutex); } -static void input_callback(InputEvent* input_event, void* event_queue) { - furi_assert(event_queue); - furi_message_queue_put((FuriMessageQueue*)event_queue, input_event, FuriWaitForever); +static void input_callback(InputEvent *input_event, void *event_queue) +{ + furi_assert(event_queue); + furi_message_queue_put((FuriMessageQueue *)event_queue, input_event, FuriWaitForever); } -static void mfkey_state_init(ProgramState* program_state) { - program_state->mfkey_state = Ready; - program_state->cracked = 0; - program_state->unique_cracked = 0; - program_state->num_completed = 0; - program_state->num_candidates = 0; - program_state->total = 0; - program_state->dict_count = 0; +static void mfkey_state_init(ProgramState *program_state) +{ + program_state->mfkey_state = Ready; + program_state->cracked = 0; + program_state->unique_cracked = 0; + program_state->num_completed = 0; + program_state->num_candidates = 0; + program_state->total = 0; + program_state->dict_count = 0; } // Entrypoint for worker thread -static int32_t mfkey_worker_thread(void* ctx) { - ProgramState* program_state = ctx; - program_state->mfkey_state = Initializing; - mfkey(program_state); - return 0; +static int32_t mfkey_worker_thread(void *ctx) +{ + ProgramState *program_state = ctx; + program_state->mfkey_state = Initializing; + mfkey(program_state); + return 0; } -int32_t mfkey_main() { - FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); +int32_t mfkey_main() +{ + FuriMessageQueue *event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); - ProgramState* program_state = malloc(sizeof(ProgramState)); + ProgramState *program_state = malloc(sizeof(ProgramState)); - mfkey_state_init(program_state); + mfkey_state_init(program_state); - program_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + program_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); - // Set system callbacks - ViewPort* view_port = view_port_alloc(); - view_port_draw_callback_set(view_port, render_callback, program_state); - view_port_input_callback_set(view_port, input_callback, event_queue); + // Set system callbacks + ViewPort *view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, render_callback, program_state); + view_port_input_callback_set(view_port, input_callback, event_queue); - // Open GUI and register view_port - Gui* gui = furi_record_open(RECORD_GUI); - gui_add_view_port(gui, view_port, GuiLayerFullscreen); + // Open GUI and register view_port + Gui *gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); - program_state->mfkeythread = - furi_thread_alloc_ex("MFKeyWorker", 2048, mfkey_worker_thread, program_state); + program_state->mfkeythread = + furi_thread_alloc_ex("MFKeyWorker", 2048, mfkey_worker_thread, program_state); - InputEvent input_event; - for(bool main_loop = true; main_loop;) { - FuriStatus event_status = furi_message_queue_get(event_queue, &input_event, 100); + InputEvent input_event; + for (bool main_loop = true; main_loop;) + { + FuriStatus event_status = furi_message_queue_get(event_queue, &input_event, 100); - furi_mutex_acquire(program_state->mutex, FuriWaitForever); + furi_mutex_acquire(program_state->mutex, FuriWaitForever); - if(event_status == FuriStatusOk) { - if(input_event.type == InputTypePress) { - switch(input_event.key) { - case InputKeyRight: - if(program_state->mfkey_state == Ready) { - program_state->mfkey_state = Help; - } - break; - case InputKeyOk: - if(program_state->mfkey_state == Ready) { - furi_thread_start(program_state->mfkeythread); - } - break; - case InputKeyBack: - if(program_state->mfkey_state == Help) { - program_state->mfkey_state = Ready; - } else { - program_state->close_thread_please = true; - // Wait until thread is finished - furi_thread_join(program_state->mfkeythread); - main_loop = false; - } - break; - default: - break; - } - } - } + if (event_status == FuriStatusOk) + { + if (input_event.type == InputTypePress) + { + switch (input_event.key) + { + case InputKeyRight: + if (program_state->mfkey_state == Ready) + { + program_state->mfkey_state = Help; + } + break; + case InputKeyOk: + if (program_state->mfkey_state == Ready) + { + furi_thread_start(program_state->mfkeythread); + } + break; + case InputKeyBack: + if (program_state->mfkey_state == Help) + { + program_state->mfkey_state = Ready; + } + else + { + program_state->close_thread_please = true; + // Wait until thread is finished + furi_thread_join(program_state->mfkeythread); + main_loop = false; + } + break; + default: + break; + } + } + } - furi_mutex_release(program_state->mutex); - view_port_update(view_port); - } + furi_mutex_release(program_state->mutex); + view_port_update(view_port); + } - // Thread joined in back event handler - furi_thread_free(program_state->mfkeythread); - view_port_enabled_set(view_port, false); - gui_remove_view_port(gui, view_port); - furi_record_close(RECORD_GUI); - view_port_free(view_port); - furi_message_queue_free(event_queue); - furi_mutex_free(program_state->mutex); - free(program_state); + // Thread joined in back event handler + furi_thread_free(program_state->mfkeythread); + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close(RECORD_GUI); + view_port_free(view_port); + furi_message_queue_free(event_queue); + furi_mutex_free(program_state->mutex); + free(program_state); - return 0; + return 0; } #pragma GCC pop_options diff --git a/applications/system/mfkey/mfkey.h b/applications/system/mfkey/mfkey.h index 4a7ab3423..7b5be5c5a 100644 --- a/applications/system/mfkey/mfkey.h +++ b/applications/system/mfkey/mfkey.h @@ -9,100 +9,115 @@ #include #include -struct Crypto1State { - uint32_t odd, even; +struct Crypto1State +{ + uint32_t odd, even; }; -struct Msb { - int tail; - uint32_t states[768]; +struct Msb +{ + int tail; + uint32_t states[768]; }; -typedef enum { - MissingNonces, - ZeroNonces, - InsufficientRAM, +typedef enum +{ + MissingNonces, + ZeroNonces, + InsufficientRAM, } MFKeyError; -typedef enum { - Ready, - Initializing, - DictionaryAttack, - MFKeyAttack, - Complete, - Error, - Help, +typedef enum +{ + Ready, + Initializing, + DictionaryAttack, + MFKeyAttack, + Complete, + Error, + Help, } MFKeyState; // TODO: Can we eliminate any of the members of this struct? -typedef struct { - FuriMutex* mutex; - MFKeyError err; - MFKeyState mfkey_state; - int cracked; - int unique_cracked; - int num_completed; - int num_candidates; - int total; - int dict_count; - int search; - int eta_timestamp; - int eta_total; - int eta_round; - bool mfkey32_present; - bool nested_present; - bool close_thread_please; - FuriThread* mfkeythread; - KeysDict* cuid_dict; +typedef struct +{ + FuriMutex *mutex; + MFKeyError err; + MFKeyState mfkey_state; + int cracked; + int unique_cracked; + int num_completed; + int num_candidates; + int total; + int dict_count; + int search; + int eta_timestamp; + int eta_total; + int eta_round; + bool mfkey32_present; + bool nested_present; + bool close_thread_please; + FuriThread *mfkeythread; + KeysDict *cuid_dict; + MfClassicKey *key_buffer; + size_t key_buffer_size; + size_t key_buffer_count; } ProgramState; -typedef enum { - mfkey32, - static_nested, - static_encrypted +typedef enum +{ + mfkey32, + static_nested, + static_encrypted } AttackType; -typedef struct { - AttackType attack; - MfClassicKey key; // key - uint32_t uid; // serial number - uint32_t nt0; // tag challenge first - uint32_t nt1; // tag challenge second - uint32_t uid_xor_nt0; // uid ^ nt0 - uint32_t uid_xor_nt1; // uid ^ nt1 - union { - // Mfkey32 - struct { - uint32_t p64; // 64th successor of nt0 - uint32_t p64b; // 64th successor of nt1 - uint32_t nr0_enc; // first encrypted reader challenge - uint32_t ar0_enc; // first encrypted reader response - uint32_t nr1_enc; // second encrypted reader challenge - uint32_t ar1_enc; // second encrypted reader response - }; - // Nested - struct { - uint32_t ks1_1_enc; // first encrypted keystream - uint32_t ks1_2_enc; // second encrypted keystream - char par_1_str[5]; // first parity bits (string representation) - char par_2_str[5]; // second parity bits (string representation) - uint8_t par_1; // first parity bits - uint8_t par_2; // second parity bits - }; - }; +typedef struct +{ + AttackType attack; + MfClassicKey key; // key + uint32_t uid; // serial number + uint32_t nt0; // tag challenge first + uint32_t nt1; // tag challenge second + uint32_t uid_xor_nt0; // uid ^ nt0 + uint32_t uid_xor_nt1; // uid ^ nt1 + union + { + // Mfkey32 + struct + { + uint32_t p64; // 64th successor of nt0 + uint32_t p64b; // 64th successor of nt1 + uint32_t nr0_enc; // first encrypted reader challenge + uint32_t ar0_enc; // first encrypted reader response + uint32_t nr1_enc; // second encrypted reader challenge + uint32_t ar1_enc; // second encrypted reader response + }; + // Nested + struct + { + uint32_t ks1_1_enc; // first encrypted keystream + uint32_t ks1_2_enc; // second encrypted keystream + char par_1_str[5]; // first parity bits (string representation) + char par_2_str[5]; // second parity bits (string representation) + uint8_t par_1; // first parity bits + uint8_t par_2; // second parity bits + }; + }; } MfClassicNonce; -typedef struct { - Stream* stream; - uint32_t total_nonces; - MfClassicNonce* remaining_nonce_array; - size_t remaining_nonces; +typedef struct +{ + Stream *stream; + uint32_t total_nonces; + MfClassicNonce *remaining_nonce_array; + size_t remaining_nonces; } MfClassicNonceArray; -struct KeysDict { - Stream* stream; - size_t key_size; - size_t key_size_symbols; - size_t total_keys; +struct KeysDict +{ + Stream *stream; + size_t key_size; + size_t key_size_symbols; + size_t total_keys; }; -#endif // MFKEY_H +#endif // MFKEY_H \ No newline at end of file From 40c6c8b59c3a13d28e441093dcd7f8ad493e67b8 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 9 Sep 2025 15:34:19 +0300 Subject: [PATCH 043/192] format --- applications/system/mfkey/crypto1.h | 360 ++--- applications/system/mfkey/mfkey.c | 2245 ++++++++++++--------------- applications/system/mfkey/mfkey.h | 176 +-- 3 files changed, 1269 insertions(+), 1512 deletions(-) diff --git a/applications/system/mfkey/crypto1.h b/applications/system/mfkey/crypto1.h index b9f7c7725..25205ed70 100644 --- a/applications/system/mfkey/crypto1.h +++ b/applications/system/mfkey/crypto1.h @@ -6,202 +6,185 @@ #include #include -#define LF_POLY_ODD (0x29CE5C) +#define LF_POLY_ODD (0x29CE5C) #define LF_POLY_EVEN (0x870804) -#define BIT(x, n) ((x) >> (n) & 1) -#define BEBIT(x, n) BIT(x, (n) ^ 24) +#define BIT(x, n) ((x) >> (n) & 1) +#define BEBIT(x, n) BIT(x, (n) ^ 24) #define SWAPENDIAN(x) \ - ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) + ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) static inline uint32_t prng_successor(uint32_t x, uint32_t n); static inline int filter(uint32_t const x); static inline uint8_t evenparity32(uint32_t x); static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2); -void crypto1_get_lfsr(struct Crypto1State *state, MfClassicKey *lfsr); -static inline uint32_t crypt_word(struct Crypto1State *s); -static inline void crypt_word_noret(struct Crypto1State *s, uint32_t in, int x); -static inline uint32_t crypt_word_ret(struct Crypto1State *s, uint32_t in, int x); +void crypto1_get_lfsr(struct Crypto1State* state, MfClassicKey* lfsr); +static inline uint32_t crypt_word(struct Crypto1State* s); +static inline void crypt_word_noret(struct Crypto1State* s, uint32_t in, int x); +static inline uint32_t crypt_word_ret(struct Crypto1State* s, uint32_t in, int x); static uint32_t crypt_word_par( - struct Crypto1State *s, - uint32_t in, - int is_encrypted, - uint32_t nt_plain, - uint8_t *parity_keystream_bits); -static inline void rollback_word_noret(struct Crypto1State *s, uint32_t in, int x); -static inline uint8_t napi_lfsr_rollback_bit(struct Crypto1State *s, uint32_t in, int fb); -static inline uint32_t napi_lfsr_rollback_word(struct Crypto1State *s, uint32_t in, int fb); + struct Crypto1State* s, + uint32_t in, + int is_encrypted, + uint32_t nt_plain, + uint8_t* parity_keystream_bits); +static inline void rollback_word_noret(struct Crypto1State* s, uint32_t in, int x); +static inline uint8_t napi_lfsr_rollback_bit(struct Crypto1State* s, uint32_t in, int fb); +static inline uint32_t napi_lfsr_rollback_word(struct Crypto1State* s, uint32_t in, int fb); static const uint8_t lookup1[256] = { - 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, - 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, - 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24}; + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, + 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24}; static const uint8_t lookup2[256] = { - 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, - 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, - 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, - 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, - 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, - 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, - 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, - 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, - 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6}; + 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, + 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, + 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, + 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, + 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, + 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, + 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, + 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, + 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6}; -static inline int filter(uint32_t const x) -{ - uint32_t f; - f = lookup1[x & 0xff] | lookup2[(x >> 8) & 0xff]; - f |= 0x0d938 >> (x >> 16 & 0xf) & 1; - return BIT(0xEC57E80A, f); +static inline int filter(uint32_t const x) { + uint32_t f; + f = lookup1[x & 0xff] | lookup2[(x >> 8) & 0xff]; + f |= 0x0d938 >> (x >> 16 & 0xf) & 1; + return BIT(0xEC57E80A, f); } #ifdef __ARM_ARCH_7EM__ -static inline uint8_t evenparity32(uint32_t x) -{ - // fold 32 bits -> 16 -> 8 -> 4 - x ^= x >> 16; - x ^= x >> 8; - x ^= x >> 4; - // magic 0x6996: bit i tells you parity of i (0 ≤ i < 16) - return (uint8_t)((0x6996u >> (x & 0xF)) & 1); +static inline uint8_t evenparity32(uint32_t x) { + // fold 32 bits -> 16 -> 8 -> 4 + x ^= x >> 16; + x ^= x >> 8; + x ^= x >> 4; + // magic 0x6996: bit i tells you parity of i (0 ≤ i < 16) + return (uint8_t)((0x6996u >> (x & 0xF)) & 1); } #endif -static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2) -{ - int p = data[item] >> 25; - p = p << 1 | evenparity32(data[item] & mask1); - p = p << 1 | evenparity32(data[item] & mask2); - data[item] = p << 24 | (data[item] & 0xffffff); +static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2) { + int p = data[item] >> 25; + p = p << 1 | evenparity32(data[item] & mask1); + p = p << 1 | evenparity32(data[item] & mask2); + data[item] = p << 24 | (data[item] & 0xffffff); } -static inline uint32_t crypt_word(struct Crypto1State *s) -{ - // "in" and "x" are always 0 (last iteration) - uint32_t res_ret = 0; - uint32_t feedin, t; - for (int i = 0; i <= 31; i++) - { - res_ret |= (filter(s->odd) << (24 ^ i)); //-V629 - feedin = LF_POLY_EVEN & s->even; - feedin ^= LF_POLY_ODD & s->odd; - s->even = s->even << 1 | (evenparity32(feedin)); - t = s->odd, s->odd = s->even, s->even = t; - } - return res_ret; +static inline uint32_t crypt_word(struct Crypto1State* s) { + // "in" and "x" are always 0 (last iteration) + uint32_t res_ret = 0; + uint32_t feedin, t; + for(int i = 0; i <= 31; i++) { + res_ret |= (filter(s->odd) << (24 ^ i)); //-V629 + feedin = LF_POLY_EVEN & s->even; + feedin ^= LF_POLY_ODD & s->odd; + s->even = s->even << 1 | (evenparity32(feedin)); + t = s->odd, s->odd = s->even, s->even = t; + } + return res_ret; } -static inline void crypt_word_noret(struct Crypto1State *s, uint32_t in, int x) -{ - uint8_t ret; - uint32_t feedin, t, next_in; - for (int i = 0; i <= 31; i++) - { - next_in = BEBIT(in, i); - ret = filter(s->odd); - feedin = ret & (!!x); - feedin ^= LF_POLY_EVEN & s->even; - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= !!next_in; - s->even = s->even << 1 | (evenparity32(feedin)); - t = s->odd, s->odd = s->even, s->even = t; - } - return; +static inline void crypt_word_noret(struct Crypto1State* s, uint32_t in, int x) { + uint8_t ret; + uint32_t feedin, t, next_in; + for(int i = 0; i <= 31; i++) { + next_in = BEBIT(in, i); + ret = filter(s->odd); + feedin = ret & (!!x); + feedin ^= LF_POLY_EVEN & s->even; + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even = s->even << 1 | (evenparity32(feedin)); + t = s->odd, s->odd = s->even, s->even = t; + } + return; } -static inline uint32_t crypt_word_ret(struct Crypto1State *s, uint32_t in, int x) -{ - uint32_t ret = 0; - uint32_t feedin, t, next_in; - uint8_t next_ret; - for (int i = 0; i <= 31; i++) - { - next_in = BEBIT(in, i); - next_ret = filter(s->odd); - feedin = next_ret & (!!x); - feedin ^= LF_POLY_EVEN & s->even; - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= !!next_in; - s->even = s->even << 1 | (evenparity32(feedin)); - t = s->odd, s->odd = s->even, s->even = t; - ret |= next_ret << (24 ^ i); - } - return ret; +static inline uint32_t crypt_word_ret(struct Crypto1State* s, uint32_t in, int x) { + uint32_t ret = 0; + uint32_t feedin, t, next_in; + uint8_t next_ret; + for(int i = 0; i <= 31; i++) { + next_in = BEBIT(in, i); + next_ret = filter(s->odd); + feedin = next_ret & (!!x); + feedin ^= LF_POLY_EVEN & s->even; + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even = s->even << 1 | (evenparity32(feedin)); + t = s->odd, s->odd = s->even, s->even = t; + ret |= next_ret << (24 ^ i); + } + return ret; } -static uint8_t get_nth_byte(uint32_t value, int n) -{ - if (n < 0 || n > 3) - { - // Handle invalid input - return 0; - } - return (value >> (8 * (3 - n))) & 0xFF; +static uint8_t get_nth_byte(uint32_t value, int n) { + if(n < 0 || n > 3) { + // Handle invalid input + return 0; + } + return (value >> (8 * (3 - n))) & 0xFF; } -static uint8_t crypt_bit(struct Crypto1State *s, uint8_t in, int is_encrypted) -{ - uint32_t feedin, t; - uint8_t ret = filter(s->odd); - feedin = ret & !!is_encrypted; - feedin ^= !!in; - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= LF_POLY_EVEN & s->even; - s->even = s->even << 1 | evenparity32(feedin); - t = s->odd, s->odd = s->even, s->even = t; - return ret; +static uint8_t crypt_bit(struct Crypto1State* s, uint8_t in, int is_encrypted) { + uint32_t feedin, t; + uint8_t ret = filter(s->odd); + feedin = ret & !!is_encrypted; + feedin ^= !!in; + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= LF_POLY_EVEN & s->even; + s->even = s->even << 1 | evenparity32(feedin); + t = s->odd, s->odd = s->even, s->even = t; + return ret; } static inline uint32_t crypt_word_par( - struct Crypto1State *s, - uint32_t in, - int is_encrypted, - uint32_t nt_plain, - uint8_t *parity_keystream_bits) -{ - uint32_t ret = 0; - *parity_keystream_bits = 0; // Reset parity keystream bits + struct Crypto1State* s, + uint32_t in, + int is_encrypted, + uint32_t nt_plain, + uint8_t* parity_keystream_bits) { + uint32_t ret = 0; + *parity_keystream_bits = 0; // Reset parity keystream bits - for (int i = 0; i < 32; i++) - { - uint8_t bit = crypt_bit(s, BEBIT(in, i), is_encrypted); - ret |= bit << (24 ^ i); - // Save keystream parity bit - if ((i + 1) % 8 == 0) - { - *parity_keystream_bits |= - (filter(s->odd) ^ nfc_util_even_parity8(get_nth_byte(nt_plain, i / 8))) - << (3 - (i / 8)); - } - } - return ret; + for(int i = 0; i < 32; i++) { + uint8_t bit = crypt_bit(s, BEBIT(in, i), is_encrypted); + ret |= bit << (24 ^ i); + // Save keystream parity bit + if((i + 1) % 8 == 0) { + *parity_keystream_bits |= + (filter(s->odd) ^ nfc_util_even_parity8(get_nth_byte(nt_plain, i / 8))) + << (3 - (i / 8)); + } + } + return ret; } -static inline void rollback_word_noret(struct Crypto1State *s, uint32_t in, int x) -{ - uint8_t ret; - uint32_t feedin, t, next_in; - for (int i = 31; i >= 0; i--) - { - next_in = BEBIT(in, i); - s->odd &= 0xffffff; - t = s->odd, s->odd = s->even, s->even = t; - ret = filter(s->odd); - feedin = ret & (!!x); - feedin ^= s->even & 1; - feedin ^= LF_POLY_EVEN & (s->even >>= 1); - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= !!next_in; - s->even |= (evenparity32(feedin)) << 23; - } - return; +static inline void rollback_word_noret(struct Crypto1State* s, uint32_t in, int x) { + uint8_t ret; + uint32_t feedin, t, next_in; + for(int i = 31; i >= 0; i--) { + next_in = BEBIT(in, i); + s->odd &= 0xffffff; + t = s->odd, s->odd = s->even, s->even = t; + ret = filter(s->odd); + feedin = ret & (!!x); + feedin ^= s->even & 1; + feedin ^= LF_POLY_EVEN & (s->even >>= 1); + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even |= (evenparity32(feedin)) << 23; + } + return; } // TODO: @@ -227,39 +210,36 @@ uint32_t rollback_word(struct Crypto1State *s, uint32_t in, int x) { } */ -uint8_t napi_lfsr_rollback_bit(struct Crypto1State *s, uint32_t in, int fb) -{ - int out; - uint8_t ret; - uint32_t t; - s->odd &= 0xffffff; - t = s->odd, s->odd = s->even, s->even = t; +uint8_t napi_lfsr_rollback_bit(struct Crypto1State* s, uint32_t in, int fb) { + int out; + uint8_t ret; + uint32_t t; + s->odd &= 0xffffff; + t = s->odd, s->odd = s->even, s->even = t; - out = s->even & 1; - out ^= LF_POLY_EVEN & (s->even >>= 1); - out ^= LF_POLY_ODD & s->odd; - out ^= !!in; - out ^= (ret = filter(s->odd)) & !!fb; + out = s->even & 1; + out ^= LF_POLY_EVEN & (s->even >>= 1); + out ^= LF_POLY_ODD & s->odd; + out ^= !!in; + out ^= (ret = filter(s->odd)) & !!fb; - s->even |= evenparity32(out) << 23; - return ret; + s->even |= evenparity32(out) << 23; + return ret; } -uint32_t napi_lfsr_rollback_word(struct Crypto1State *s, uint32_t in, int fb) -{ - int i; - uint32_t ret = 0; - for (i = 31; i >= 0; --i) - ret |= napi_lfsr_rollback_bit(s, BEBIT(in, i), fb) << (i ^ 24); - return ret; +uint32_t napi_lfsr_rollback_word(struct Crypto1State* s, uint32_t in, int fb) { + int i; + uint32_t ret = 0; + for(i = 31; i >= 0; --i) + ret |= napi_lfsr_rollback_bit(s, BEBIT(in, i), fb) << (i ^ 24); + return ret; } -static inline uint32_t prng_successor(uint32_t x, uint32_t n) -{ - SWAPENDIAN(x); - while (n--) - x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31; - return SWAPENDIAN(x); +static inline uint32_t prng_successor(uint32_t x, uint32_t n) { + SWAPENDIAN(x); + while(n--) + x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31; + return SWAPENDIAN(x); } -#endif // CRYPTO1_H \ No newline at end of file +#endif // CRYPTO1_H diff --git a/applications/system/mfkey/mfkey.c b/applications/system/mfkey/mfkey.c index e49b96263..9e3e71847 100644 --- a/applications/system/mfkey/mfkey.c +++ b/applications/system/mfkey/mfkey.c @@ -34,29 +34,28 @@ #define TAG "MFKey" // TODO: Remove defines that are not needed -#define KEYS_DICT_SYSTEM_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc") -#define KEYS_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc") -#define MAX_NAME_LEN 32 -#define MAX_PATH_LEN 64 +#define KEYS_DICT_SYSTEM_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc") +#define KEYS_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc") +#define MAX_NAME_LEN 32 +#define MAX_PATH_LEN 64 #define STATIC_ENCRYPTED_RAM_THRESHOLD 4096 -#define LF_POLY_ODD (0x29CE5C) +#define LF_POLY_ODD (0x29CE5C) #define LF_POLY_EVEN (0x870804) -#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1) -#define CONST_M2_1 (LF_POLY_ODD << 1) -#define CONST_M1_2 (LF_POLY_ODD) -#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1) -#define BIT(x, n) ((x) >> (n) & 1) -#define BEBIT(x, n) BIT(x, (n) ^ 24) +#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1) +#define CONST_M2_1 (LF_POLY_ODD << 1) +#define CONST_M1_2 (LF_POLY_ODD) +#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1) +#define BIT(x, n) ((x) >> (n) & 1) +#define BEBIT(x, n) BIT(x, (n) ^ 24) #define SWAP(a, b) \ - do \ - { \ - unsigned int t = a; \ - a = b; \ - b = t; \ - } while (0) + do { \ + unsigned int t = a; \ + a = b; \ + b = t; \ + } while(0) #define SWAPENDIAN(x) \ - ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) + ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) // #define SIZEOF(arr) sizeof(arr) / sizeof(*arr) // Reduced to 16-bit as these values are small and don't need 32-bit @@ -65,1315 +64,1105 @@ static int16_t eta_total_time = 705; // MSB_LIMIT: Chunk size (out of 256) - can be 8-bit as it's a small value static uint8_t MSB_LIMIT = 16; -static inline void flush_key_buffer(ProgramState *program_state) -{ - if (program_state->key_buffer && program_state->key_buffer_count > 0 && program_state->cuid_dict) - { - // Pre-allocate exact size needed: 12 hex chars + 1 newline per key - size_t total_size = program_state->key_buffer_count * 13; - //FURI_LOG_I(TAG, "Flushing key buffer: %d keys", program_state->key_buffer_count); - //FURI_LOG_I(TAG, "Total size: %d bytes", total_size); - char* batch_buffer = malloc(total_size + 1); // +1 for null terminator - - char* ptr = batch_buffer; - const char hex_chars[] = "0123456789ABCDEF"; - - for (size_t i = 0; i < program_state->key_buffer_count; i++) - { - // Convert key to hex string directly into buffer - for (size_t j = 0; j < sizeof(MfClassicKey); j++) - { - uint8_t byte = program_state->key_buffer[i].data[j]; - *ptr++ = hex_chars[byte >> 4]; - *ptr++ = hex_chars[byte & 0x0F]; - } - *ptr++ = '\n'; - } - *ptr = '\0'; - - // Write all keys at once by directly accessing the stream - Stream* stream = program_state->cuid_dict->stream; - uint32_t actual_pos = stream_tell(stream); - - if (stream_seek(stream, 0, StreamOffsetFromEnd) && - stream_write(stream, (uint8_t*)batch_buffer, total_size) == total_size) - { - // Update total key count - program_state->cuid_dict->total_keys += program_state->key_buffer_count; - } - - // May not be needed - stream_seek(stream, actual_pos, StreamOffsetFromStart); - free(batch_buffer); - program_state->key_buffer_count = 0; - } +static inline void flush_key_buffer(ProgramState* program_state) { + if(program_state->key_buffer && program_state->key_buffer_count > 0 && + program_state->cuid_dict) { + // Pre-allocate exact size needed: 12 hex chars + 1 newline per key + size_t total_size = program_state->key_buffer_count * 13; + //FURI_LOG_I(TAG, "Flushing key buffer: %d keys", program_state->key_buffer_count); + //FURI_LOG_I(TAG, "Total size: %d bytes", total_size); + char* batch_buffer = malloc(total_size + 1); // +1 for null terminator + + char* ptr = batch_buffer; + const char hex_chars[] = "0123456789ABCDEF"; + + for(size_t i = 0; i < program_state->key_buffer_count; i++) { + // Convert key to hex string directly into buffer + for(size_t j = 0; j < sizeof(MfClassicKey); j++) { + uint8_t byte = program_state->key_buffer[i].data[j]; + *ptr++ = hex_chars[byte >> 4]; + *ptr++ = hex_chars[byte & 0x0F]; + } + *ptr++ = '\n'; + } + *ptr = '\0'; + + // Write all keys at once by directly accessing the stream + Stream* stream = program_state->cuid_dict->stream; + uint32_t actual_pos = stream_tell(stream); + + if(stream_seek(stream, 0, StreamOffsetFromEnd) && + stream_write(stream, (uint8_t*)batch_buffer, total_size) == total_size) { + // Update total key count + program_state->cuid_dict->total_keys += program_state->key_buffer_count; + } + + // May not be needed + stream_seek(stream, actual_pos, StreamOffsetFromStart); + free(batch_buffer); + program_state->key_buffer_count = 0; + } } static inline int -check_state(struct Crypto1State *t, MfClassicNonce *n, ProgramState *program_state) -{ - if (!(t->odd | t->even)) - return 0; - if (n->attack == mfkey32) - { - uint32_t rb = (napi_lfsr_rollback_word(t, 0, 0) ^ n->p64); - if (rb != n->ar0_enc) - { - return 0; - } - rollback_word_noret(t, n->nr0_enc, 1); - rollback_word_noret(t, n->uid_xor_nt0, 0); - struct Crypto1State temp = {t->odd, t->even}; - crypt_word_noret(t, n->uid_xor_nt1, 0); - crypt_word_noret(t, n->nr1_enc, 1); - if (n->ar1_enc == (crypt_word(t) ^ n->p64b)) - { - crypto1_get_lfsr(&temp, &(n->key)); - return 1; - } - } - else if (n->attack == static_nested) - { - struct Crypto1State temp = {t->odd, t->even}; - rollback_word_noret(t, n->uid_xor_nt1, 0); - if (n->ks1_1_enc == crypt_word_ret(t, n->uid_xor_nt0, 0)) - { - rollback_word_noret(&temp, n->uid_xor_nt1, 0); - crypto1_get_lfsr(&temp, &(n->key)); - return 1; - } - } - else if (n->attack == static_encrypted) - { - // TODO: Parity bits from rollback_word? - if (n->ks1_1_enc == napi_lfsr_rollback_word(t, n->uid_xor_nt0, 0)) - { - // Reduce with parity - uint8_t local_parity_keystream_bits; - struct Crypto1State temp = {t->odd, t->even}; - if ((crypt_word_par(&temp, n->uid_xor_nt0, 0, n->nt0, &local_parity_keystream_bits) == - n->ks1_1_enc) && - (local_parity_keystream_bits == n->par_1)) - { - // Found key candidate - crypto1_get_lfsr(t, &(n->key)); - program_state->num_candidates++; - - // Use key buffer - buffer is guaranteed to be available for static_encrypted - program_state->key_buffer[program_state->key_buffer_count] = n->key; - program_state->key_buffer_count++; - - // Flush buffer when full - if (program_state->key_buffer_count >= program_state->key_buffer_size) - { - flush_key_buffer(program_state); - } - } - } - } - return 0; + check_state(struct Crypto1State* t, MfClassicNonce* n, ProgramState* program_state) { + if(!(t->odd | t->even)) return 0; + if(n->attack == mfkey32) { + uint32_t rb = (napi_lfsr_rollback_word(t, 0, 0) ^ n->p64); + if(rb != n->ar0_enc) { + return 0; + } + rollback_word_noret(t, n->nr0_enc, 1); + rollback_word_noret(t, n->uid_xor_nt0, 0); + struct Crypto1State temp = {t->odd, t->even}; + crypt_word_noret(t, n->uid_xor_nt1, 0); + crypt_word_noret(t, n->nr1_enc, 1); + if(n->ar1_enc == (crypt_word(t) ^ n->p64b)) { + crypto1_get_lfsr(&temp, &(n->key)); + return 1; + } + } else if(n->attack == static_nested) { + struct Crypto1State temp = {t->odd, t->even}; + rollback_word_noret(t, n->uid_xor_nt1, 0); + if(n->ks1_1_enc == crypt_word_ret(t, n->uid_xor_nt0, 0)) { + rollback_word_noret(&temp, n->uid_xor_nt1, 0); + crypto1_get_lfsr(&temp, &(n->key)); + return 1; + } + } else if(n->attack == static_encrypted) { + // TODO: Parity bits from rollback_word? + if(n->ks1_1_enc == napi_lfsr_rollback_word(t, n->uid_xor_nt0, 0)) { + // Reduce with parity + uint8_t local_parity_keystream_bits; + struct Crypto1State temp = {t->odd, t->even}; + if((crypt_word_par(&temp, n->uid_xor_nt0, 0, n->nt0, &local_parity_keystream_bits) == + n->ks1_1_enc) && + (local_parity_keystream_bits == n->par_1)) { + // Found key candidate + crypto1_get_lfsr(t, &(n->key)); + program_state->num_candidates++; + + // Use key buffer - buffer is guaranteed to be available for static_encrypted + program_state->key_buffer[program_state->key_buffer_count] = n->key; + program_state->key_buffer_count++; + + // Flush buffer when full + if(program_state->key_buffer_count >= program_state->key_buffer_size) { + flush_key_buffer(program_state); + } + } + } + } + return 0; } -static inline __attribute__((hot)) int state_loop( - unsigned int *states_buffer, - int xks, - int m1, - int m2, - unsigned int in, - int and_val) -{ - int states_tail = 0; - int xks_bit = 0, round_in = 0; +static inline __attribute__((hot)) int + state_loop(unsigned int* states_buffer, int xks, int m1, int m2, unsigned int in, int and_val) { + int states_tail = 0; + int xks_bit = 0, round_in = 0; - // Unroll first 4 rounds (no round_in calculations needed) - // Hoist the filter() calls to just one iteration and reuse the results - // This avoids redundant calculations and improves performance and gives us 2000b of extra ram (11496b free on run) - // V.28/04. Aprox 3s speedup per round. Total 3 keys 7mins 17s!! - for (int round = 1; round <= 4; ++round) - { - xks_bit = BIT(xks, round); - for (int s = 0; s <= states_tail; ++s) - { - unsigned int v = states_buffer[s] << 1; - states_buffer[s] = v; - int f0 = filter(v); - int f1 = filter(v | 1); + // Unroll first 4 rounds (no round_in calculations needed) + // Hoist the filter() calls to just one iteration and reuse the results + // This avoids redundant calculations and improves performance and gives us 2000b of extra ram (11496b free on run) + // V.28/04. Aprox 3s speedup per round. Total 3 keys 7mins 17s!! + for(int round = 1; round <= 4; ++round) { + xks_bit = BIT(xks, round); + for(int s = 0; s <= states_tail; ++s) { + unsigned int v = states_buffer[s] << 1; + states_buffer[s] = v; + int f0 = filter(v); + int f1 = filter(v | 1); - if (__builtin_expect((f0 ^ f1) != 0, 0)) - { - states_buffer[s] |= f0 ^ xks_bit; - } - else if (__builtin_expect(f0 == xks_bit, 1)) - { - states_buffer[++states_tail] = states_buffer[++s]; - states_buffer[s] = states_buffer[s - 1] | 1; - } - else - { - states_buffer[s--] = states_buffer[states_tail--]; - } - } - } + if(__builtin_expect((f0 ^ f1) != 0, 0)) { + states_buffer[s] |= f0 ^ xks_bit; + } else if(__builtin_expect(f0 == xks_bit, 1)) { + states_buffer[++states_tail] = states_buffer[++s]; + states_buffer[s] = states_buffer[s - 1] | 1; + } else { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + } - // Round 5 (unrolled) - { - xks_bit = BIT(xks, 5); - int r5_in = ((in >> 2) & and_val) << 24; // 2*(5-4)=2 - for (int s = 0; s <= states_tail; ++s) - { - unsigned int v = states_buffer[s] << 1; - states_buffer[s] = v; - int f0 = filter(v), f1 = filter(v | 1); - if (__builtin_expect((f0 ^ f1) != 0, 0)) - { - states_buffer[s] |= f0 ^ xks_bit; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= r5_in; - } - else if (__builtin_expect(f0 == xks_bit, 1)) - { - states_buffer[++states_tail] = states_buffer[s + 1]; - states_buffer[s + 1] = v | 1; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s++] ^= r5_in; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= r5_in; - } - else - { - states_buffer[s--] = states_buffer[states_tail--]; - } - } - } + // Round 5 (unrolled) + { + xks_bit = BIT(xks, 5); + int r5_in = ((in >> 2) & and_val) << 24; // 2*(5-4)=2 + for(int s = 0; s <= states_tail; ++s) { + unsigned int v = states_buffer[s] << 1; + states_buffer[s] = v; + int f0 = filter(v), f1 = filter(v | 1); + if(__builtin_expect((f0 ^ f1) != 0, 0)) { + states_buffer[s] |= f0 ^ xks_bit; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= r5_in; + } else if(__builtin_expect(f0 == xks_bit, 1)) { + states_buffer[++states_tail] = states_buffer[s + 1]; + states_buffer[s + 1] = v | 1; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s++] ^= r5_in; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= r5_in; + } else { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + } - // Round 6 (unrolled) - { - xks_bit = BIT(xks, 6); - int r6_in = ((in >> 4) & and_val) << 24; // 2*(6-4)=4 - for (int s = 0; s <= states_tail; ++s) - { - unsigned int v = states_buffer[s] << 1; - states_buffer[s] = v; - int f0 = filter(v), f1 = filter(v | 1); - if (__builtin_expect((f0 ^ f1) != 0, 0)) - { - states_buffer[s] |= f0 ^ xks_bit; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= r6_in; - } - else if (__builtin_expect(f0 == xks_bit, 1)) - { - states_buffer[++states_tail] = states_buffer[s + 1]; - states_buffer[s + 1] = v | 1; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s++] ^= r6_in; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= r6_in; - } - else - { - states_buffer[s--] = states_buffer[states_tail--]; - } - } - } + // Round 6 (unrolled) + { + xks_bit = BIT(xks, 6); + int r6_in = ((in >> 4) & and_val) << 24; // 2*(6-4)=4 + for(int s = 0; s <= states_tail; ++s) { + unsigned int v = states_buffer[s] << 1; + states_buffer[s] = v; + int f0 = filter(v), f1 = filter(v | 1); + if(__builtin_expect((f0 ^ f1) != 0, 0)) { + states_buffer[s] |= f0 ^ xks_bit; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= r6_in; + } else if(__builtin_expect(f0 == xks_bit, 1)) { + states_buffer[++states_tail] = states_buffer[s + 1]; + states_buffer[s + 1] = v | 1; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s++] ^= r6_in; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= r6_in; + } else { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + } - // Loop rounds 7–12 - for (int round = 7; round <= 12; ++round) - { - xks_bit = BIT(xks, round); - round_in = ((in >> (2 * (round - 4))) & and_val) << 24; - for (int s = 0; s <= states_tail; ++s) - { - unsigned int v = states_buffer[s] << 1; - states_buffer[s] = v; - int f0 = filter(v), f1 = filter(v | 1); - if (__builtin_expect((f0 ^ f1) != 0, 0)) - { - states_buffer[s] |= f0 ^ xks_bit; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= round_in; - } - else if (__builtin_expect(f0 == xks_bit, 1)) - { - states_buffer[++states_tail] = states_buffer[s + 1]; - states_buffer[s + 1] = v | 1; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s++] ^= round_in; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= round_in; - } - else - { - states_buffer[s--] = states_buffer[states_tail--]; - } - } - } + // Loop rounds 7–12 + for(int round = 7; round <= 12; ++round) { + xks_bit = BIT(xks, round); + round_in = ((in >> (2 * (round - 4))) & and_val) << 24; + for(int s = 0; s <= states_tail; ++s) { + unsigned int v = states_buffer[s] << 1; + states_buffer[s] = v; + int f0 = filter(v), f1 = filter(v | 1); + if(__builtin_expect((f0 ^ f1) != 0, 0)) { + states_buffer[s] |= f0 ^ xks_bit; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= round_in; + } else if(__builtin_expect(f0 == xks_bit, 1)) { + states_buffer[++states_tail] = states_buffer[s + 1]; + states_buffer[s + 1] = v | 1; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s++] ^= round_in; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= round_in; + } else { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + } - return states_tail; + return states_tail; } -int binsearch(unsigned int data[], int start, int stop) -{ - int mid, val = data[stop] & 0xff000000; - while (start != stop) - { - mid = (stop - start) >> 1; - if ((data[start + mid] ^ 0x80000000) > (val ^ 0x80000000)) - stop = start + mid; - else - start += mid + 1; - } - return start; +int binsearch(unsigned int data[], int start, int stop) { + int mid, val = data[stop] & 0xff000000; + while(start != stop) { + mid = (stop - start) >> 1; + if((data[start + mid] ^ 0x80000000) > (val ^ 0x80000000)) + stop = start + mid; + else + start += mid + 1; + } + return start; } -void quicksort(unsigned int array[], int low, int high) -{ - // Use insertion sort for small arrays (threshold determined by testing) - if (high - low < 16) - { - // Insertion sort - for (int i = low + 1; i <= high; i++) - { - unsigned int key = array[i]; - int j = i - 1; - while (j >= low && array[j] > key) - { - array[j + 1] = array[j]; - j--; - } - array[j + 1] = key; - } - return; - } +void quicksort(unsigned int array[], int low, int high) { + // Use insertion sort for small arrays (threshold determined by testing) + if(high - low < 16) { + // Insertion sort + for(int i = low + 1; i <= high; i++) { + unsigned int key = array[i]; + int j = i - 1; + while(j >= low && array[j] > key) { + array[j + 1] = array[j]; + j--; + } + array[j + 1] = key; + } + return; + } - if (low >= high) - return; + if(low >= high) return; - // Median-of-three pivot selection - int middle = low + (high - low) / 2; - if (array[middle] < array[low]) - SWAP(array[middle], array[low]); - if (array[high] < array[low]) - SWAP(array[high], array[low]); - if (array[high] < array[middle]) - SWAP(array[high], array[middle]); + // Median-of-three pivot selection + int middle = low + (high - low) / 2; + if(array[middle] < array[low]) SWAP(array[middle], array[low]); + if(array[high] < array[low]) SWAP(array[high], array[low]); + if(array[high] < array[middle]) SWAP(array[high], array[middle]); - unsigned int pivot = array[middle]; + unsigned int pivot = array[middle]; - // Rest of quicksort with improved partitioning - int i = low, j = high; - while (i <= j) - { - while (array[i] < pivot) - i++; - while (array[j] > pivot) - j--; - if (i <= j) - { - // swap - unsigned int temp = array[i]; - array[i] = array[j]; - array[j] = temp; - i++; - j--; - } - } + // Rest of quicksort with improved partitioning + int i = low, j = high; + while(i <= j) { + while(array[i] < pivot) + i++; + while(array[j] > pivot) + j--; + if(i <= j) { + // swap + unsigned int temp = array[i]; + array[i] = array[j]; + array[j] = temp; + i++; + j--; + } + } - if (low < j) - quicksort(array, low, j); - if (high > i) - quicksort(array, i, high); + if(low < j) quicksort(array, low, j); + if(high > i) quicksort(array, i, high); } -int extend_table(unsigned int data[], int tbl, int end, int bit, int m1, int m2, unsigned int in) -{ - in <<= 24; - for (data[tbl] <<= 1; tbl <= end; data[++tbl] <<= 1) - { - if ((filter(data[tbl]) ^ filter(data[tbl] | 1)) != 0) - { - data[tbl] |= filter(data[tbl]) ^ bit; - update_contribution(data, tbl, m1, m2); - data[tbl] ^= in; - } - else if (filter(data[tbl]) == bit) - { - data[++end] = data[tbl + 1]; - data[tbl + 1] = data[tbl] | 1; - update_contribution(data, tbl, m1, m2); - data[tbl++] ^= in; - update_contribution(data, tbl, m1, m2); - data[tbl] ^= in; - } - else - { - data[tbl--] = data[end--]; - } - } - return end; +int extend_table(unsigned int data[], int tbl, int end, int bit, int m1, int m2, unsigned int in) { + in <<= 24; + for(data[tbl] <<= 1; tbl <= end; data[++tbl] <<= 1) { + if((filter(data[tbl]) ^ filter(data[tbl] | 1)) != 0) { + data[tbl] |= filter(data[tbl]) ^ bit; + update_contribution(data, tbl, m1, m2); + data[tbl] ^= in; + } else if(filter(data[tbl]) == bit) { + data[++end] = data[tbl + 1]; + data[tbl + 1] = data[tbl] | 1; + update_contribution(data, tbl, m1, m2); + data[tbl++] ^= in; + update_contribution(data, tbl, m1, m2); + data[tbl] ^= in; + } else { + data[tbl--] = data[end--]; + } + } + return end; } int old_recover( - unsigned int odd[], - int o_head, - int o_tail, - int oks, - unsigned int even[], - int e_head, - int e_tail, - int eks, - int rem, - int s, - MfClassicNonce *n, - unsigned int in, - int first_run, - ProgramState *program_state) -{ - int o, e, i; - if (rem == -1) - { - for (e = e_head; e <= e_tail; ++e) - { - even[e] = (even[e] << 1) ^ evenparity32(even[e] & LF_POLY_EVEN) ^ (!!(in & 4)); - for (o = o_head; o <= o_tail; ++o, ++s) - { - struct Crypto1State temp = {0, 0}; - temp.even = odd[o]; - temp.odd = even[e] ^ evenparity32(odd[o] & LF_POLY_ODD); - if (check_state(&temp, n, program_state)) - { - return -1; - } - } - } - return s; - } - if (first_run == 0) - { - for (i = 0; (i < 4) && (rem-- != 0); i++) - { - oks >>= 1; - eks >>= 1; - in >>= 2; - o_tail = extend_table( - odd, o_head, o_tail, oks & 1, LF_POLY_EVEN << 1 | 1, LF_POLY_ODD << 1, 0); - if (o_head > o_tail) - return s; - e_tail = extend_table( - even, e_head, e_tail, eks & 1, LF_POLY_ODD, LF_POLY_EVEN << 1 | 1, in & 3); - if (e_head > e_tail) - return s; - } - } - first_run = 0; - quicksort(odd, o_head, o_tail); - quicksort(even, e_head, e_tail); - while (o_tail >= o_head && e_tail >= e_head) - { - if (((odd[o_tail] ^ even[e_tail]) >> 24) == 0) - { - o_tail = binsearch(odd, o_head, o = o_tail); - e_tail = binsearch(even, e_head, e = e_tail); - s = old_recover( - odd, - o_tail--, - o, - oks, - even, - e_tail--, - e, - eks, - rem, - s, - n, - in, - first_run, - program_state); - if (s == -1) - { - break; - } - } - else if ((odd[o_tail] ^ 0x80000000) > (even[e_tail] ^ 0x80000000)) - { - o_tail = binsearch(odd, o_head, o_tail) - 1; - } - else - { - e_tail = binsearch(even, e_head, e_tail) - 1; - } - } - return s; + unsigned int odd[], + int o_head, + int o_tail, + int oks, + unsigned int even[], + int e_head, + int e_tail, + int eks, + int rem, + int s, + MfClassicNonce* n, + unsigned int in, + int first_run, + ProgramState* program_state) { + int o, e, i; + if(rem == -1) { + for(e = e_head; e <= e_tail; ++e) { + even[e] = (even[e] << 1) ^ evenparity32(even[e] & LF_POLY_EVEN) ^ (!!(in & 4)); + for(o = o_head; o <= o_tail; ++o, ++s) { + struct Crypto1State temp = {0, 0}; + temp.even = odd[o]; + temp.odd = even[e] ^ evenparity32(odd[o] & LF_POLY_ODD); + if(check_state(&temp, n, program_state)) { + return -1; + } + } + } + return s; + } + if(first_run == 0) { + for(i = 0; (i < 4) && (rem-- != 0); i++) { + oks >>= 1; + eks >>= 1; + in >>= 2; + o_tail = extend_table( + odd, o_head, o_tail, oks & 1, LF_POLY_EVEN << 1 | 1, LF_POLY_ODD << 1, 0); + if(o_head > o_tail) return s; + e_tail = extend_table( + even, e_head, e_tail, eks & 1, LF_POLY_ODD, LF_POLY_EVEN << 1 | 1, in & 3); + if(e_head > e_tail) return s; + } + } + first_run = 0; + quicksort(odd, o_head, o_tail); + quicksort(even, e_head, e_tail); + while(o_tail >= o_head && e_tail >= e_head) { + if(((odd[o_tail] ^ even[e_tail]) >> 24) == 0) { + o_tail = binsearch(odd, o_head, o = o_tail); + e_tail = binsearch(even, e_head, e = e_tail); + s = old_recover( + odd, + o_tail--, + o, + oks, + even, + e_tail--, + e, + eks, + rem, + s, + n, + in, + first_run, + program_state); + if(s == -1) { + break; + } + } else if((odd[o_tail] ^ 0x80000000) > (even[e_tail] ^ 0x80000000)) { + o_tail = binsearch(odd, o_head, o_tail) - 1; + } else { + e_tail = binsearch(even, e_head, e_tail) - 1; + } + } + return s; } -static inline int sync_state(ProgramState *program_state) -{ - int ts = furi_hal_rtc_get_timestamp(); - int elapsed_time = ts - program_state->eta_timestamp; - if (elapsed_time < program_state->eta_round) - { - program_state->eta_round -= elapsed_time; - } - else - { - program_state->eta_round = 0; - } - if (elapsed_time < program_state->eta_total) - { - program_state->eta_total -= elapsed_time; - } - else - { - program_state->eta_total = 0; - } - program_state->eta_timestamp = ts; - if (program_state->close_thread_please) - { - return 1; - } - return 0; +static inline int sync_state(ProgramState* program_state) { + int ts = furi_hal_rtc_get_timestamp(); + int elapsed_time = ts - program_state->eta_timestamp; + if(elapsed_time < program_state->eta_round) { + program_state->eta_round -= elapsed_time; + } else { + program_state->eta_round = 0; + } + if(elapsed_time < program_state->eta_total) { + program_state->eta_total -= elapsed_time; + } else { + program_state->eta_total = 0; + } + program_state->eta_timestamp = ts; + if(program_state->close_thread_please) { + return 1; + } + return 0; } int calculate_msb_tables( - int oks, - int eks, - int msb_round, - MfClassicNonce *n, - unsigned int *states_buffer, - struct Msb *odd_msbs, - struct Msb *even_msbs, - unsigned int *temp_states_odd, - unsigned int *temp_states_even, - unsigned int in, - ProgramState *program_state) -{ - unsigned int msb_head = (MSB_LIMIT * msb_round); - unsigned int msb_tail = (MSB_LIMIT * (msb_round + 1)); - int states_tail = 0; - int semi_state = 0; - unsigned int msb = 0; + int oks, + int eks, + int msb_round, + MfClassicNonce* n, + unsigned int* states_buffer, + struct Msb* odd_msbs, + struct Msb* even_msbs, + unsigned int* temp_states_odd, + unsigned int* temp_states_even, + unsigned int in, + ProgramState* program_state) { + unsigned int msb_head = (MSB_LIMIT * msb_round); + unsigned int msb_tail = (MSB_LIMIT * (msb_round + 1)); + int states_tail = 0; + int semi_state = 0; + unsigned int msb = 0; - // Preprocessed in value - in = ((in >> 16 & 0xff) | (in << 16) | (in & 0xff00)) << 1; + // Preprocessed in value + in = ((in >> 16 & 0xff) | (in << 16) | (in & 0xff00)) << 1; - // Clear MSB arrays once before loop instead of inside loop - memset(odd_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); - memset(even_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); + // Clear MSB arrays once before loop instead of inside loop + memset(odd_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); + memset(even_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); - // Bit values to check - calculate once outside the loop - int oks_bit = oks & 1; - int eks_bit = eks & 1; + // Bit values to check - calculate once outside the loop + int oks_bit = oks & 1; + int eks_bit = eks & 1; - // Check for stop request less frequently - int sync_check_interval = 32768 * 2; // Doubled the interval + // Check for stop request less frequently + int sync_check_interval = 32768 * 2; // Doubled the interval - for (semi_state = 1 << 20; semi_state >= 0; semi_state--) - { - if (semi_state % sync_check_interval == 0) - { - if (sync_state(program_state) == 1) - { - return 0; - } - } + for(semi_state = 1 << 20; semi_state >= 0; semi_state--) { + if(semi_state % sync_check_interval == 0) { + if(sync_state(program_state) == 1) { + return 0; + } + } - // Process both filter conditions in one pass when possible - int filter_semi_state = filter(semi_state); + // Process both filter conditions in one pass when possible + int filter_semi_state = filter(semi_state); - // Check oks condition - if (filter_semi_state == oks_bit) - { - states_buffer[0] = semi_state; - states_tail = state_loop(states_buffer, oks, CONST_M1_1, CONST_M2_1, 0, 0); + // Check oks condition + if(filter_semi_state == oks_bit) { + states_buffer[0] = semi_state; + states_tail = state_loop(states_buffer, oks, CONST_M1_1, CONST_M2_1, 0, 0); - for (int i = states_tail; i >= 0; i--) - { - msb = states_buffer[i] >> 24; - if ((msb >= msb_head) && (msb < msb_tail)) - { - // Calculate index once - int msb_idx = msb - msb_head; + for(int i = states_tail; i >= 0; i--) { + msb = states_buffer[i] >> 24; + if((msb >= msb_head) && (msb < msb_tail)) { + // Calculate index once + int msb_idx = msb - msb_head; - // Avoid sequential scan by using a direct flag - int found = 0; - for (int j = 0; j < odd_msbs[msb_idx].tail; j++) - { - if (odd_msbs[msb_idx].states[j] == states_buffer[i]) - { - found = 1; - break; - } - } + // Avoid sequential scan by using a direct flag + int found = 0; + for(int j = 0; j < odd_msbs[msb_idx].tail; j++) { + if(odd_msbs[msb_idx].states[j] == states_buffer[i]) { + found = 1; + break; + } + } - if (!found) - { - int tail = odd_msbs[msb_idx].tail++; - odd_msbs[msb_idx].states[tail] = states_buffer[i]; - } - } - } - } + if(!found) { + int tail = odd_msbs[msb_idx].tail++; + odd_msbs[msb_idx].states[tail] = states_buffer[i]; + } + } + } + } - // Check eks condition - if (filter_semi_state == eks_bit) - { - states_buffer[0] = semi_state; - states_tail = state_loop(states_buffer, eks, CONST_M1_2, CONST_M2_2, in, 3); + // Check eks condition + if(filter_semi_state == eks_bit) { + states_buffer[0] = semi_state; + states_tail = state_loop(states_buffer, eks, CONST_M1_2, CONST_M2_2, in, 3); - for (int i = 0; i <= states_tail; i++) - { - msb = states_buffer[i] >> 24; - if ((msb >= msb_head) && (msb < msb_tail)) - { - // Calculate index once - int msb_idx = msb - msb_head; + for(int i = 0; i <= states_tail; i++) { + msb = states_buffer[i] >> 24; + if((msb >= msb_head) && (msb < msb_tail)) { + // Calculate index once + int msb_idx = msb - msb_head; - // Avoid sequential scan - int found = 0; - for (int j = 0; j < even_msbs[msb_idx].tail; j++) - { - if (even_msbs[msb_idx].states[j] == states_buffer[i]) - { - found = 1; - break; - } - } + // Avoid sequential scan + int found = 0; + for(int j = 0; j < even_msbs[msb_idx].tail; j++) { + if(even_msbs[msb_idx].states[j] == states_buffer[i]) { + found = 1; + break; + } + } - if (!found) - { - int tail = even_msbs[msb_idx].tail++; - even_msbs[msb_idx].states[tail] = states_buffer[i]; - } - } - } - } - } + if(!found) { + int tail = even_msbs[msb_idx].tail++; + even_msbs[msb_idx].states[tail] = states_buffer[i]; + } + } + } + } + } - // Shift once outside the loop - oks >>= 12; - eks >>= 12; + // Shift once outside the loop + oks >>= 12; + eks >>= 12; - // Process results - for (int i = 0; i < MSB_LIMIT; i++) - { - if ((i % 4) == 0 && sync_state(program_state) == 1) - { - return 0; - } + // Process results + for(int i = 0; i < MSB_LIMIT; i++) { + if((i % 4) == 0 && sync_state(program_state) == 1) { + return 0; + } - // Only clear buffers if they're going to be used - if (odd_msbs[i].tail > 0 || even_msbs[i].tail > 0) - { - memset(temp_states_even, 0, sizeof(unsigned int) * (1280)); - memset(temp_states_odd, 0, sizeof(unsigned int) * (1280)); - memcpy(temp_states_odd, odd_msbs[i].states, odd_msbs[i].tail * sizeof(unsigned int)); - memcpy(temp_states_even, even_msbs[i].states, even_msbs[i].tail * sizeof(unsigned int)); + // Only clear buffers if they're going to be used + if(odd_msbs[i].tail > 0 || even_msbs[i].tail > 0) { + memset(temp_states_even, 0, sizeof(unsigned int) * (1280)); + memset(temp_states_odd, 0, sizeof(unsigned int) * (1280)); + memcpy(temp_states_odd, odd_msbs[i].states, odd_msbs[i].tail * sizeof(unsigned int)); + memcpy( + temp_states_even, even_msbs[i].states, even_msbs[i].tail * sizeof(unsigned int)); - int res = old_recover( - temp_states_odd, - 0, - odd_msbs[i].tail, - oks, - temp_states_even, - 0, - even_msbs[i].tail, - eks, - 3, - 0, - n, - in >> 16, - 1, - program_state); + int res = old_recover( + temp_states_odd, + 0, + odd_msbs[i].tail, + oks, + temp_states_even, + 0, + even_msbs[i].tail, + eks, + 3, + 0, + n, + in >> 16, + 1, + program_state); - if (res == -1) - { - return 1; - } - } - } + if(res == -1) { + return 1; + } + } + } - return 0; + return 0; } -void **allocate_blocks(const size_t *block_sizes, int num_blocks) -{ - void **block_pointers = malloc(num_blocks * sizeof(void *)); - if (!block_pointers) - { - return NULL; - } +void** allocate_blocks(const size_t* block_sizes, int num_blocks) { + void** block_pointers = malloc(num_blocks * sizeof(void*)); + if(!block_pointers) { + return NULL; + } - for (int i = 0; i < num_blocks; i++) - { - if (memmgr_heap_get_max_free_block() < block_sizes[i]) - { - // Not enough memory, free previously allocated blocks - for (int j = 0; j < i; j++) - { - free(block_pointers[j]); - } - free(block_pointers); - return NULL; - } + for(int i = 0; i < num_blocks; i++) { + if(memmgr_heap_get_max_free_block() < block_sizes[i]) { + // Not enough memory, free previously allocated blocks + for(int j = 0; j < i; j++) { + free(block_pointers[j]); + } + free(block_pointers); + return NULL; + } - block_pointers[i] = malloc(block_sizes[i]); - if (!block_pointers[i]) - { - // Allocation failed - for (int j = 0; j < i; j++) - { - free(block_pointers[j]); - } - free(block_pointers); - return NULL; - } - } + block_pointers[i] = malloc(block_sizes[i]); + if(!block_pointers[i]) { + // Allocation failed + for(int j = 0; j < i; j++) { + free(block_pointers[j]); + } + free(block_pointers); + return NULL; + } + } - return block_pointers; + return block_pointers; } -bool recover(MfClassicNonce *n, int ks2, unsigned int in, ProgramState *program_state) -{ - bool found = false; - const size_t block_sizes[] = {49216, 49216, 5120, 5120, 4096}; - const size_t reduced_block_sizes[] = {24608, 24608, 5120, 5120, 4096}; - const int num_blocks = sizeof(block_sizes) / sizeof(block_sizes[0]); - // Reset globals each nonce - eta_round_time = 44; +bool recover(MfClassicNonce* n, int ks2, unsigned int in, ProgramState* program_state) { + bool found = false; + const size_t block_sizes[] = {49216, 49216, 5120, 5120, 4096}; + const size_t reduced_block_sizes[] = {24608, 24608, 5120, 5120, 4096}; + const int num_blocks = sizeof(block_sizes) / sizeof(block_sizes[0]); + // Reset globals each nonce + eta_round_time = 44; eta_total_time = 705; - MSB_LIMIT = 16; - - // Use half speed (reduced block sizes) for static encrypted nonces so we can buffer keys - bool use_half_speed = (n->attack == static_encrypted); - if (use_half_speed) - { - //eta_round_time *= 2; - eta_total_time *= 2; - MSB_LIMIT /= 2; - } + MSB_LIMIT = 16; - void **block_pointers = allocate_blocks(use_half_speed ? reduced_block_sizes : block_sizes, num_blocks); - if (block_pointers == NULL) { - if (n->attack != static_encrypted) - { - // System has less than the guaranteed amount of RAM (140 KB) - adjust some parameters to run anyway at half speed - // eta_round_time *= 2; - eta_total_time *= 2; - MSB_LIMIT /= 2; - block_pointers = allocate_blocks(reduced_block_sizes, num_blocks); - if (block_pointers == NULL) - { - // System has less than 70 KB of RAM - should never happen so we don't reduce speed further - program_state->err = InsufficientRAM; - program_state->mfkey_state = Error; - return false; - } - } else - { - program_state->err = InsufficientRAM; - program_state->mfkey_state = Error; - return false; - } - } - struct Msb *odd_msbs = block_pointers[0]; - struct Msb *even_msbs = block_pointers[1]; - unsigned int *temp_states_odd = block_pointers[2]; - unsigned int *temp_states_even = block_pointers[3]; - unsigned int *states_buffer = block_pointers[4]; + // Use half speed (reduced block sizes) for static encrypted nonces so we can buffer keys + bool use_half_speed = (n->attack == static_encrypted); + if(use_half_speed) { + //eta_round_time *= 2; + eta_total_time *= 2; + MSB_LIMIT /= 2; + } - // Allocate key buffer for static encrypted nonces - if (n->attack == static_encrypted) - { - size_t available_ram = memmgr_heap_get_max_free_block(); - // Each key becomes 12 hex chars + 1 newline = 13 bytes in the batch string - // Plus original 6 bytes in buffer = 19 bytes total per key - // Add extra safety margin for string overhead and other allocations - const size_t safety_threshold = STATIC_ENCRYPTED_RAM_THRESHOLD; - const size_t bytes_per_key = sizeof(MfClassicKey) + 13; // buffer + string representation - if (available_ram > safety_threshold) - { - program_state->key_buffer_size = (available_ram - safety_threshold) / bytes_per_key; - program_state->key_buffer = malloc(program_state->key_buffer_size * sizeof(MfClassicKey)); - program_state->key_buffer_count = 0; - if (!program_state->key_buffer) - { - // Free the allocated blocks before returning - for (int i = 0; i < num_blocks; i++) - { - free(block_pointers[i]); - } - free(block_pointers); - program_state->err = InsufficientRAM; - program_state->mfkey_state = Error; - return false; - } - } - else - { - // Free the allocated blocks before returning - for (int i = 0; i < num_blocks; i++) - { - free(block_pointers[i]); - } - free(block_pointers); - program_state->err = InsufficientRAM; - program_state->mfkey_state = Error; - return false; - } - } - else - { - program_state->key_buffer = NULL; - program_state->key_buffer_size = 0; - program_state->key_buffer_count = 0; - } + void** block_pointers = + allocate_blocks(use_half_speed ? reduced_block_sizes : block_sizes, num_blocks); + if(block_pointers == NULL) { + if(n->attack != static_encrypted) { + // System has less than the guaranteed amount of RAM (140 KB) - adjust some parameters to run anyway at half speed + // eta_round_time *= 2; + eta_total_time *= 2; + MSB_LIMIT /= 2; + block_pointers = allocate_blocks(reduced_block_sizes, num_blocks); + if(block_pointers == NULL) { + // System has less than 70 KB of RAM - should never happen so we don't reduce speed further + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } else { + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } + struct Msb* odd_msbs = block_pointers[0]; + struct Msb* even_msbs = block_pointers[1]; + unsigned int* temp_states_odd = block_pointers[2]; + unsigned int* temp_states_even = block_pointers[3]; + unsigned int* states_buffer = block_pointers[4]; - int oks = 0, eks = 0; - int i = 0, msb = 0; - for (i = 31; i >= 0; i -= 2) - { - oks = oks << 1 | BEBIT(ks2, i); - } - for (i = 30; i >= 0; i -= 2) - { - eks = eks << 1 | BEBIT(ks2, i); - } - int bench_start = furi_hal_rtc_get_timestamp(); - program_state->eta_total = eta_total_time; - program_state->eta_timestamp = bench_start; - for (msb = 0; msb <= ((256 / MSB_LIMIT) - 1); msb++) - { - program_state->search = msb; - program_state->eta_round = eta_round_time; - program_state->eta_total = eta_total_time - (eta_round_time * msb); - if (calculate_msb_tables( - oks, - eks, - msb, - n, - states_buffer, - odd_msbs, - even_msbs, - temp_states_odd, - temp_states_even, - in, - program_state)) - { - // int bench_stop = furi_hal_rtc_get_timestamp(); - // FURI_LOG_I(TAG, "Cracked in %i seconds", bench_stop - bench_start); - found = true; - break; - } - if (program_state->close_thread_please) - { - break; - } - } + // Allocate key buffer for static encrypted nonces + if(n->attack == static_encrypted) { + size_t available_ram = memmgr_heap_get_max_free_block(); + // Each key becomes 12 hex chars + 1 newline = 13 bytes in the batch string + // Plus original 6 bytes in buffer = 19 bytes total per key + // Add extra safety margin for string overhead and other allocations + const size_t safety_threshold = STATIC_ENCRYPTED_RAM_THRESHOLD; + const size_t bytes_per_key = sizeof(MfClassicKey) + 13; // buffer + string representation + if(available_ram > safety_threshold) { + program_state->key_buffer_size = (available_ram - safety_threshold) / bytes_per_key; + program_state->key_buffer = + malloc(program_state->key_buffer_size * sizeof(MfClassicKey)); + program_state->key_buffer_count = 0; + if(!program_state->key_buffer) { + // Free the allocated blocks before returning + for(int i = 0; i < num_blocks; i++) { + free(block_pointers[i]); + } + free(block_pointers); + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } else { + // Free the allocated blocks before returning + for(int i = 0; i < num_blocks; i++) { + free(block_pointers[i]); + } + free(block_pointers); + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } else { + program_state->key_buffer = NULL; + program_state->key_buffer_size = 0; + program_state->key_buffer_count = 0; + } - // Final flush and cleanup for key buffer - if (n->attack == static_encrypted && program_state->key_buffer) - { - flush_key_buffer(program_state); - free(program_state->key_buffer); - program_state->key_buffer = NULL; - program_state->key_buffer_size = 0; - program_state->key_buffer_count = 0; - } + int oks = 0, eks = 0; + int i = 0, msb = 0; + for(i = 31; i >= 0; i -= 2) { + oks = oks << 1 | BEBIT(ks2, i); + } + for(i = 30; i >= 0; i -= 2) { + eks = eks << 1 | BEBIT(ks2, i); + } + int bench_start = furi_hal_rtc_get_timestamp(); + program_state->eta_total = eta_total_time; + program_state->eta_timestamp = bench_start; + for(msb = 0; msb <= ((256 / MSB_LIMIT) - 1); msb++) { + program_state->search = msb; + program_state->eta_round = eta_round_time; + program_state->eta_total = eta_total_time - (eta_round_time * msb); + if(calculate_msb_tables( + oks, + eks, + msb, + n, + states_buffer, + odd_msbs, + even_msbs, + temp_states_odd, + temp_states_even, + in, + program_state)) { + // int bench_stop = furi_hal_rtc_get_timestamp(); + // FURI_LOG_I(TAG, "Cracked in %i seconds", bench_stop - bench_start); + found = true; + break; + } + if(program_state->close_thread_please) { + break; + } + } - // Free the allocated blocks - for (int i = 0; i < num_blocks; i++) - { - free(block_pointers[i]); - } - free(block_pointers); - return found; + // Final flush and cleanup for key buffer + if(n->attack == static_encrypted && program_state->key_buffer) { + flush_key_buffer(program_state); + free(program_state->key_buffer); + program_state->key_buffer = NULL; + program_state->key_buffer_size = 0; + program_state->key_buffer_count = 0; + } + + // Free the allocated blocks + for(int i = 0; i < num_blocks; i++) { + free(block_pointers[i]); + } + free(block_pointers); + return found; } bool key_already_found_for_nonce_in_solved( - MfClassicKey *keyarray, - int keyarray_size, - MfClassicNonce *nonce) -{ - for (int k = 0; k < keyarray_size; k++) - { - uint64_t key_as_int = bit_lib_bytes_to_num_be(keyarray[k].data, sizeof(MfClassicKey)); - struct Crypto1State temp = {0, 0}; - for (int i = 0; i < 24; i++) - { - (&temp)->odd |= (BIT(key_as_int, 2 * i + 1) << (i ^ 3)); - (&temp)->even |= (BIT(key_as_int, 2 * i) << (i ^ 3)); - } - if (nonce->attack == mfkey32) - { - crypt_word_noret(&temp, nonce->uid_xor_nt1, 0); - crypt_word_noret(&temp, nonce->nr1_enc, 1); - if (nonce->ar1_enc == (crypt_word(&temp) ^ nonce->p64b)) - { - return true; - } - } - else if (nonce->attack == static_nested) - { - uint32_t expected_ks1 = crypt_word_ret(&temp, nonce->uid_xor_nt0, 0); - if (nonce->ks1_1_enc == expected_ks1) - { - return true; - } - } - } - return false; + MfClassicKey* keyarray, + int keyarray_size, + MfClassicNonce* nonce) { + for(int k = 0; k < keyarray_size; k++) { + uint64_t key_as_int = bit_lib_bytes_to_num_be(keyarray[k].data, sizeof(MfClassicKey)); + struct Crypto1State temp = {0, 0}; + for(int i = 0; i < 24; i++) { + (&temp)->odd |= (BIT(key_as_int, 2 * i + 1) << (i ^ 3)); + (&temp)->even |= (BIT(key_as_int, 2 * i) << (i ^ 3)); + } + if(nonce->attack == mfkey32) { + crypt_word_noret(&temp, nonce->uid_xor_nt1, 0); + crypt_word_noret(&temp, nonce->nr1_enc, 1); + if(nonce->ar1_enc == (crypt_word(&temp) ^ nonce->p64b)) { + return true; + } + } else if(nonce->attack == static_nested) { + uint32_t expected_ks1 = crypt_word_ret(&temp, nonce->uid_xor_nt0, 0); + if(nonce->ks1_1_enc == expected_ks1) { + return true; + } + } + } + return false; } #pragma GCC push_options #pragma GCC optimize("Os") -static void finished_beep() -{ - // Beep to indicate completion - NotificationApp *notification = furi_record_open("notification"); - notification_message(notification, &sequence_audiovisual_alert); - notification_message(notification, &sequence_display_backlight_on); - furi_record_close("notification"); +static void finished_beep() { + // Beep to indicate completion + NotificationApp* notification = furi_record_open("notification"); + notification_message(notification, &sequence_audiovisual_alert); + notification_message(notification, &sequence_display_backlight_on); + furi_record_close("notification"); } -void mfkey(ProgramState *program_state) -{ - uint32_t ks_enc = 0, nt_xor_uid = 0; - MfClassicKey found_key; // Recovered key - size_t keyarray_size = 0; - MfClassicKey *keyarray = malloc(sizeof(MfClassicKey) * 1); - if (!keyarray) - { - program_state->err = InsufficientRAM; - program_state->mfkey_state = Error; - return; - } +void mfkey(ProgramState* program_state) { + uint32_t ks_enc = 0, nt_xor_uid = 0; + MfClassicKey found_key; // Recovered key + size_t keyarray_size = 0; + MfClassicKey* keyarray = malloc(sizeof(MfClassicKey) * 1); + if(!keyarray) { + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return; + } - uint32_t i = 0, j = 0; - // FURI_LOG_I(TAG, "Free heap before alloc(): %zub", memmgr_get_free_heap()); - Storage *storage = furi_record_open(RECORD_STORAGE); - FlipperApplication *app = flipper_application_alloc(storage, firmware_api_interface); - flipper_application_preload(app, APP_ASSETS_PATH("plugins/mfkey_init_plugin.fal")); - flipper_application_map_to_memory(app); - const FlipperAppPluginDescriptor *app_descriptor = - flipper_application_plugin_get_descriptor(app); - const MfkeyPlugin *init_plugin = app_descriptor->entry_point; - // Check for nonces - program_state->mfkey32_present = init_plugin->napi_mf_classic_mfkey32_nonces_check_presence(); - program_state->nested_present = init_plugin->napi_mf_classic_nested_nonces_check_presence(); - if (!(program_state->mfkey32_present) && !(program_state->nested_present)) - { - program_state->err = MissingNonces; - program_state->mfkey_state = Error; - flipper_application_free(app); - furi_record_close(RECORD_STORAGE); - free(keyarray); - return; - } - // Read dictionaries (optional) - KeysDict *system_dict = {0}; - bool system_dict_exists = keys_dict_check_presence(KEYS_DICT_SYSTEM_PATH); - KeysDict *user_dict = {0}; - bool user_dict_exists = keys_dict_check_presence(KEYS_DICT_USER_PATH); - uint32_t total_dict_keys = 0; - if (system_dict_exists) - { - system_dict = - keys_dict_alloc(KEYS_DICT_SYSTEM_PATH, KeysDictModeOpenExisting, sizeof(MfClassicKey)); - total_dict_keys += keys_dict_get_total_keys(system_dict); - } - user_dict = keys_dict_alloc(KEYS_DICT_USER_PATH, KeysDictModeOpenAlways, sizeof(MfClassicKey)); - if (user_dict_exists) - { - total_dict_keys += keys_dict_get_total_keys(user_dict); - } - user_dict_exists = true; - program_state->dict_count = total_dict_keys; - program_state->mfkey_state = DictionaryAttack; - // Read nonces - MfClassicNonceArray *nonce_arr; - nonce_arr = init_plugin->napi_mf_classic_nonce_array_alloc( - system_dict, system_dict_exists, user_dict, program_state); - if (system_dict_exists) - { - keys_dict_free(system_dict); - } - if (nonce_arr->total_nonces == 0) - { - // Nothing to crack - program_state->err = ZeroNonces; - program_state->mfkey_state = Error; - init_plugin->napi_mf_classic_nonce_array_free(nonce_arr); - flipper_application_free(app); - furi_record_close(RECORD_STORAGE); - keys_dict_free(user_dict); - free(keyarray); - return; - } - flipper_application_free(app); - furi_record_close(RECORD_STORAGE); - // TODO: Track free state at the time this is called to ensure double free does not happen - furi_assert(nonce_arr); - furi_assert(nonce_arr->stream); - // TODO: Already closed? - buffered_file_stream_close(nonce_arr->stream); - stream_free(nonce_arr->stream); - // FURI_LOG_I(TAG, "Free heap after free(): %zub", memmgr_get_free_heap()); - program_state->mfkey_state = MFKeyAttack; - // TODO: Work backwards on this array and free memory - for (i = 0; i < nonce_arr->total_nonces; i++) - { - MfClassicNonce next_nonce = nonce_arr->remaining_nonce_array[i]; - if (key_already_found_for_nonce_in_solved(keyarray, keyarray_size, &next_nonce)) - { - nonce_arr->remaining_nonces--; - (program_state->cracked)++; - (program_state->num_completed)++; - continue; - } - // FURI_LOG_I(TAG, "Beginning recovery for %8lx", next_nonce.uid); - FuriString *cuid_dict_path; - switch (next_nonce.attack) - { - case mfkey32: - ks_enc = next_nonce.ar0_enc ^ next_nonce.p64; - nt_xor_uid = 0; - break; - case static_nested: - ks_enc = next_nonce.ks1_2_enc; - nt_xor_uid = next_nonce.uid_xor_nt1; - break; - case static_encrypted: - ks_enc = next_nonce.ks1_1_enc; - nt_xor_uid = next_nonce.uid_xor_nt0; - cuid_dict_path = furi_string_alloc_printf( - "%s/mf_classic_dict_%08lx.nfc", EXT_PATH("nfc/assets"), next_nonce.uid); - // May need RECORD_STORAGE? - program_state->cuid_dict = keys_dict_alloc( - furi_string_get_cstr(cuid_dict_path), - KeysDictModeOpenAlways, - sizeof(MfClassicKey)); - furi_string_free(cuid_dict_path); - break; - } + uint32_t i = 0, j = 0; + // FURI_LOG_I(TAG, "Free heap before alloc(): %zub", memmgr_get_free_heap()); + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperApplication* app = flipper_application_alloc(storage, firmware_api_interface); + flipper_application_preload(app, APP_ASSETS_PATH("plugins/mfkey_init_plugin.fal")); + flipper_application_map_to_memory(app); + const FlipperAppPluginDescriptor* app_descriptor = + flipper_application_plugin_get_descriptor(app); + const MfkeyPlugin* init_plugin = app_descriptor->entry_point; + // Check for nonces + program_state->mfkey32_present = init_plugin->napi_mf_classic_mfkey32_nonces_check_presence(); + program_state->nested_present = init_plugin->napi_mf_classic_nested_nonces_check_presence(); + if(!(program_state->mfkey32_present) && !(program_state->nested_present)) { + program_state->err = MissingNonces; + program_state->mfkey_state = Error; + flipper_application_free(app); + furi_record_close(RECORD_STORAGE); + free(keyarray); + return; + } + // Read dictionaries (optional) + KeysDict* system_dict = {0}; + bool system_dict_exists = keys_dict_check_presence(KEYS_DICT_SYSTEM_PATH); + KeysDict* user_dict = {0}; + bool user_dict_exists = keys_dict_check_presence(KEYS_DICT_USER_PATH); + uint32_t total_dict_keys = 0; + if(system_dict_exists) { + system_dict = + keys_dict_alloc(KEYS_DICT_SYSTEM_PATH, KeysDictModeOpenExisting, sizeof(MfClassicKey)); + total_dict_keys += keys_dict_get_total_keys(system_dict); + } + user_dict = keys_dict_alloc(KEYS_DICT_USER_PATH, KeysDictModeOpenAlways, sizeof(MfClassicKey)); + if(user_dict_exists) { + total_dict_keys += keys_dict_get_total_keys(user_dict); + } + user_dict_exists = true; + program_state->dict_count = total_dict_keys; + program_state->mfkey_state = DictionaryAttack; + // Read nonces + MfClassicNonceArray* nonce_arr; + nonce_arr = init_plugin->napi_mf_classic_nonce_array_alloc( + system_dict, system_dict_exists, user_dict, program_state); + if(system_dict_exists) { + keys_dict_free(system_dict); + } + if(nonce_arr->total_nonces == 0) { + // Nothing to crack + program_state->err = ZeroNonces; + program_state->mfkey_state = Error; + init_plugin->napi_mf_classic_nonce_array_free(nonce_arr); + flipper_application_free(app); + furi_record_close(RECORD_STORAGE); + keys_dict_free(user_dict); + free(keyarray); + return; + } + flipper_application_free(app); + furi_record_close(RECORD_STORAGE); + // TODO: Track free state at the time this is called to ensure double free does not happen + furi_assert(nonce_arr); + furi_assert(nonce_arr->stream); + // TODO: Already closed? + buffered_file_stream_close(nonce_arr->stream); + stream_free(nonce_arr->stream); + // FURI_LOG_I(TAG, "Free heap after free(): %zub", memmgr_get_free_heap()); + program_state->mfkey_state = MFKeyAttack; + // TODO: Work backwards on this array and free memory + for(i = 0; i < nonce_arr->total_nonces; i++) { + MfClassicNonce next_nonce = nonce_arr->remaining_nonce_array[i]; + if(key_already_found_for_nonce_in_solved(keyarray, keyarray_size, &next_nonce)) { + nonce_arr->remaining_nonces--; + (program_state->cracked)++; + (program_state->num_completed)++; + continue; + } + // FURI_LOG_I(TAG, "Beginning recovery for %8lx", next_nonce.uid); + FuriString* cuid_dict_path; + switch(next_nonce.attack) { + case mfkey32: + ks_enc = next_nonce.ar0_enc ^ next_nonce.p64; + nt_xor_uid = 0; + break; + case static_nested: + ks_enc = next_nonce.ks1_2_enc; + nt_xor_uid = next_nonce.uid_xor_nt1; + break; + case static_encrypted: + ks_enc = next_nonce.ks1_1_enc; + nt_xor_uid = next_nonce.uid_xor_nt0; + cuid_dict_path = furi_string_alloc_printf( + "%s/mf_classic_dict_%08lx.nfc", EXT_PATH("nfc/assets"), next_nonce.uid); + // May need RECORD_STORAGE? + program_state->cuid_dict = keys_dict_alloc( + furi_string_get_cstr(cuid_dict_path), + KeysDictModeOpenAlways, + sizeof(MfClassicKey)); + furi_string_free(cuid_dict_path); + break; + } - if (!recover(&next_nonce, ks_enc, nt_xor_uid, program_state)) - { - // Check for non-recoverable errors and break the loop - if (program_state->mfkey_state == Error) - { - if ((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) - { - keys_dict_free(program_state->cuid_dict); - program_state->cuid_dict = NULL; - } - break; - } - if (program_state->close_thread_please) - { - if ((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) - { - keys_dict_free(program_state->cuid_dict); - program_state->cuid_dict = NULL; - } - break; - } - // No key found in recover() or static encrypted - (program_state->num_completed)++; - // Free CUID dictionary after each static_encrypted nonce processing - if ((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) - { - keys_dict_free(program_state->cuid_dict); - program_state->cuid_dict = NULL; - } - continue; - } - (program_state->cracked)++; - (program_state->num_completed)++; - found_key = next_nonce.key; - bool already_found = false; - for (j = 0; j < keyarray_size; j++) - { - if (memcmp(keyarray[j].data, found_key.data, MF_CLASSIC_KEY_SIZE) == 0) - { - already_found = true; - break; - } - } - if (already_found == false) - { - // New key - MfClassicKey *new_keyarray = realloc(keyarray, sizeof(MfClassicKey) * (keyarray_size + 1)); - if (!new_keyarray) - { - // Realloc failed - continue with existing keyarray - FURI_LOG_E(TAG, "Failed to realloc keyarray"); - } - else - { - keyarray = new_keyarray; - keyarray_size += 1; - keyarray[keyarray_size - 1] = found_key; - (program_state->unique_cracked)++; - } - } - } - // TODO: Update display to show all keys were found - // TODO: Prepend found key(s) to user dictionary file - // FURI_LOG_I(TAG, "Unique keys found:"); - for (i = 0; i < keyarray_size; i++) - { - // FURI_LOG_I(TAG, "%012" PRIx64, keyarray[i]); - keys_dict_add_key(user_dict, keyarray[i].data, sizeof(MfClassicKey)); - } - if (keyarray_size > 0) - { - dolphin_deed(DolphinDeedNfcKeyAdd); - } - free(nonce_arr); - keys_dict_free(user_dict); - free(keyarray); - if (program_state->mfkey_state == Error) - { - return; - } - // FURI_LOG_I(TAG, "mfkey function completed normally"); // DEBUG - program_state->mfkey_state = Complete; - // No need to alert the user if they asked it to stop - if (!(program_state->close_thread_please)) - { - finished_beep(); - } - return; + if(!recover(&next_nonce, ks_enc, nt_xor_uid, program_state)) { + // Check for non-recoverable errors and break the loop + if(program_state->mfkey_state == Error) { + if((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) { + keys_dict_free(program_state->cuid_dict); + program_state->cuid_dict = NULL; + } + break; + } + if(program_state->close_thread_please) { + if((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) { + keys_dict_free(program_state->cuid_dict); + program_state->cuid_dict = NULL; + } + break; + } + // No key found in recover() or static encrypted + (program_state->num_completed)++; + // Free CUID dictionary after each static_encrypted nonce processing + if((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) { + keys_dict_free(program_state->cuid_dict); + program_state->cuid_dict = NULL; + } + continue; + } + (program_state->cracked)++; + (program_state->num_completed)++; + found_key = next_nonce.key; + bool already_found = false; + for(j = 0; j < keyarray_size; j++) { + if(memcmp(keyarray[j].data, found_key.data, MF_CLASSIC_KEY_SIZE) == 0) { + already_found = true; + break; + } + } + if(already_found == false) { + // New key + MfClassicKey* new_keyarray = + realloc(keyarray, sizeof(MfClassicKey) * (keyarray_size + 1)); + if(!new_keyarray) { + // Realloc failed - continue with existing keyarray + FURI_LOG_E(TAG, "Failed to realloc keyarray"); + } else { + keyarray = new_keyarray; + keyarray_size += 1; + keyarray[keyarray_size - 1] = found_key; + (program_state->unique_cracked)++; + } + } + } + // TODO: Update display to show all keys were found + // TODO: Prepend found key(s) to user dictionary file + // FURI_LOG_I(TAG, "Unique keys found:"); + for(i = 0; i < keyarray_size; i++) { + // FURI_LOG_I(TAG, "%012" PRIx64, keyarray[i]); + keys_dict_add_key(user_dict, keyarray[i].data, sizeof(MfClassicKey)); + } + if(keyarray_size > 0) { + dolphin_deed(DolphinDeedNfcKeyAdd); + } + free(nonce_arr); + keys_dict_free(user_dict); + free(keyarray); + if(program_state->mfkey_state == Error) { + return; + } + // FURI_LOG_I(TAG, "mfkey function completed normally"); // DEBUG + program_state->mfkey_state = Complete; + // No need to alert the user if they asked it to stop + if(!(program_state->close_thread_please)) { + finished_beep(); + } + return; } // Screen is 128x64 px -static void render_callback(Canvas *const canvas, void *ctx) -{ - furi_assert(ctx); - ProgramState *program_state = ctx; - furi_mutex_acquire(program_state->mutex, FuriWaitForever); - char draw_str[44] = {}; +static void render_callback(Canvas* const canvas, void* ctx) { + furi_assert(ctx); + ProgramState* program_state = ctx; + furi_mutex_acquire(program_state->mutex, FuriWaitForever); + char draw_str[44] = {}; - canvas_draw_frame(canvas, 0, 0, 128, 64); - canvas_draw_frame(canvas, 0, 15, 128, 64); + canvas_draw_frame(canvas, 0, 0, 128, 64); + canvas_draw_frame(canvas, 0, 15, 128, 64); - // FontSecondary by default, title is drawn at the end - snprintf(draw_str, sizeof(draw_str), "RAM: %zub", memmgr_get_free_heap()); - canvas_draw_str_aligned(canvas, 48, 5, AlignLeft, AlignTop, draw_str); - canvas_draw_icon(canvas, 114, 4, &I_mfkey); - if (program_state->mfkey_state == MFKeyAttack) - { - float eta_round = (float)1 - ((float)program_state->eta_round / (float)eta_round_time); - float eta_total = (float)1 - ((float)program_state->eta_total / (float)eta_total_time); - float progress = (float)program_state->num_completed / (float)program_state->total; - if (eta_round < 0 || eta_round > 1) - { - // Round ETA miscalculated - eta_round = 1; - program_state->eta_round = 0; - } - if (eta_total < 0 || eta_round > 1) - { - // Total ETA miscalculated - eta_total = 1; - program_state->eta_total = 0; - } - snprintf( - draw_str, - sizeof(draw_str), - "Cracking: %d/%d - in prog.", - program_state->num_completed, - program_state->total); - elements_progress_bar_with_text(canvas, 5, 18, 118, progress, draw_str); - snprintf( - draw_str, - sizeof(draw_str), - "Round: %d/%d - ETA %02d Sec", - (program_state->search) + 1, // Zero indexed - 256 / MSB_LIMIT, - program_state->eta_round); - elements_progress_bar_with_text(canvas, 5, 31, 118, eta_round, draw_str); - snprintf(draw_str, sizeof(draw_str), "Total ETA %03d Sec", program_state->eta_total); - elements_progress_bar_with_text(canvas, 5, 44, 118, eta_total, draw_str); - } - else if (program_state->mfkey_state == DictionaryAttack) - { - snprintf( - draw_str, sizeof(draw_str), "Dict solves: %d (in progress)", program_state->cracked); - canvas_draw_str_aligned(canvas, 10, 18, AlignLeft, AlignTop, draw_str); - snprintf(draw_str, sizeof(draw_str), "Keys in dict: %d", program_state->dict_count); - canvas_draw_str_aligned(canvas, 26, 28, AlignLeft, AlignTop, draw_str); - } - else if (program_state->mfkey_state == Complete) - { - // TODO: Scrollable list view to see cracked keys if user presses down - elements_progress_bar(canvas, 5, 18, 118, 1); - canvas_draw_str_aligned(canvas, 64, 31, AlignCenter, AlignTop, "Complete"); - snprintf( - draw_str, - sizeof(draw_str), - "Keys added to user dict: %d", - program_state->unique_cracked); - canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignTop, draw_str); - if (program_state->num_candidates > 0) - { - snprintf( - draw_str, - sizeof(draw_str), - "SEN key candidates: %d", - program_state->num_candidates); - canvas_draw_str_aligned(canvas, 64, 51, AlignCenter, AlignTop, draw_str); - } - } - else if (program_state->mfkey_state == Ready) - { - canvas_draw_str_aligned(canvas, 50, 30, AlignLeft, AlignTop, "Ready"); - elements_button_center(canvas, "Start"); - elements_button_right(canvas, "Help"); - } - else if (program_state->mfkey_state == Help) - { - canvas_draw_str_aligned(canvas, 7, 20, AlignLeft, AlignTop, "Collect nonces by reading"); - canvas_draw_str_aligned(canvas, 7, 30, AlignLeft, AlignTop, "tag or reader in NFC app:"); - canvas_draw_str_aligned(canvas, 7, 40, AlignLeft, AlignTop, "https://docs.flipper.net/"); - canvas_draw_str_aligned(canvas, 7, 50, AlignLeft, AlignTop, "nfc/mfkey32"); - } - else if (program_state->mfkey_state == Error) - { - canvas_draw_str_aligned(canvas, 50, 25, AlignLeft, AlignTop, "Error"); - if (program_state->err == MissingNonces) - { - canvas_draw_str_aligned(canvas, 25, 36, AlignLeft, AlignTop, "No nonces found"); - } - else if (program_state->err == ZeroNonces) - { - canvas_draw_str_aligned(canvas, 15, 36, AlignLeft, AlignTop, "Nonces already cracked"); - } - else if (program_state->err == InsufficientRAM) - { - canvas_draw_str_aligned(canvas, 35, 36, AlignLeft, AlignTop, "No free RAM"); - } - else - { - // Unhandled error - } - } - else - { - // Unhandled program state - } - // Title - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 5, 4, AlignLeft, AlignTop, "MFKey"); - furi_mutex_release(program_state->mutex); + // FontSecondary by default, title is drawn at the end + snprintf(draw_str, sizeof(draw_str), "RAM: %zub", memmgr_get_free_heap()); + canvas_draw_str_aligned(canvas, 48, 5, AlignLeft, AlignTop, draw_str); + canvas_draw_icon(canvas, 114, 4, &I_mfkey); + if(program_state->mfkey_state == MFKeyAttack) { + float eta_round = (float)1 - ((float)program_state->eta_round / (float)eta_round_time); + float eta_total = (float)1 - ((float)program_state->eta_total / (float)eta_total_time); + float progress = (float)program_state->num_completed / (float)program_state->total; + if(eta_round < 0 || eta_round > 1) { + // Round ETA miscalculated + eta_round = 1; + program_state->eta_round = 0; + } + if(eta_total < 0 || eta_round > 1) { + // Total ETA miscalculated + eta_total = 1; + program_state->eta_total = 0; + } + snprintf( + draw_str, + sizeof(draw_str), + "Cracking: %d/%d - in prog.", + program_state->num_completed, + program_state->total); + elements_progress_bar_with_text(canvas, 5, 18, 118, progress, draw_str); + snprintf( + draw_str, + sizeof(draw_str), + "Round: %d/%d - ETA %02d Sec", + (program_state->search) + 1, // Zero indexed + 256 / MSB_LIMIT, + program_state->eta_round); + elements_progress_bar_with_text(canvas, 5, 31, 118, eta_round, draw_str); + snprintf(draw_str, sizeof(draw_str), "Total ETA %03d Sec", program_state->eta_total); + elements_progress_bar_with_text(canvas, 5, 44, 118, eta_total, draw_str); + } else if(program_state->mfkey_state == DictionaryAttack) { + snprintf( + draw_str, sizeof(draw_str), "Dict solves: %d (in progress)", program_state->cracked); + canvas_draw_str_aligned(canvas, 10, 18, AlignLeft, AlignTop, draw_str); + snprintf(draw_str, sizeof(draw_str), "Keys in dict: %d", program_state->dict_count); + canvas_draw_str_aligned(canvas, 26, 28, AlignLeft, AlignTop, draw_str); + } else if(program_state->mfkey_state == Complete) { + // TODO: Scrollable list view to see cracked keys if user presses down + elements_progress_bar(canvas, 5, 18, 118, 1); + canvas_draw_str_aligned(canvas, 64, 31, AlignCenter, AlignTop, "Complete"); + snprintf( + draw_str, + sizeof(draw_str), + "Keys added to user dict: %d", + program_state->unique_cracked); + canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignTop, draw_str); + if(program_state->num_candidates > 0) { + snprintf( + draw_str, + sizeof(draw_str), + "SEN key candidates: %d", + program_state->num_candidates); + canvas_draw_str_aligned(canvas, 64, 51, AlignCenter, AlignTop, draw_str); + } + } else if(program_state->mfkey_state == Ready) { + canvas_draw_str_aligned(canvas, 50, 30, AlignLeft, AlignTop, "Ready"); + elements_button_center(canvas, "Start"); + elements_button_right(canvas, "Help"); + } else if(program_state->mfkey_state == Help) { + canvas_draw_str_aligned(canvas, 7, 20, AlignLeft, AlignTop, "Collect nonces by reading"); + canvas_draw_str_aligned(canvas, 7, 30, AlignLeft, AlignTop, "tag or reader in NFC app:"); + canvas_draw_str_aligned(canvas, 7, 40, AlignLeft, AlignTop, "https://docs.flipper.net/"); + canvas_draw_str_aligned(canvas, 7, 50, AlignLeft, AlignTop, "nfc/mfkey32"); + } else if(program_state->mfkey_state == Error) { + canvas_draw_str_aligned(canvas, 50, 25, AlignLeft, AlignTop, "Error"); + if(program_state->err == MissingNonces) { + canvas_draw_str_aligned(canvas, 25, 36, AlignLeft, AlignTop, "No nonces found"); + } else if(program_state->err == ZeroNonces) { + canvas_draw_str_aligned(canvas, 15, 36, AlignLeft, AlignTop, "Nonces already cracked"); + } else if(program_state->err == InsufficientRAM) { + canvas_draw_str_aligned(canvas, 35, 36, AlignLeft, AlignTop, "No free RAM"); + } else { + // Unhandled error + } + } else { + // Unhandled program state + } + // Title + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 5, 4, AlignLeft, AlignTop, "MFKey"); + furi_mutex_release(program_state->mutex); } -static void input_callback(InputEvent *input_event, void *event_queue) -{ - furi_assert(event_queue); - furi_message_queue_put((FuriMessageQueue *)event_queue, input_event, FuriWaitForever); +static void input_callback(InputEvent* input_event, void* event_queue) { + furi_assert(event_queue); + furi_message_queue_put((FuriMessageQueue*)event_queue, input_event, FuriWaitForever); } -static void mfkey_state_init(ProgramState *program_state) -{ - program_state->mfkey_state = Ready; - program_state->cracked = 0; - program_state->unique_cracked = 0; - program_state->num_completed = 0; - program_state->num_candidates = 0; - program_state->total = 0; - program_state->dict_count = 0; +static void mfkey_state_init(ProgramState* program_state) { + program_state->mfkey_state = Ready; + program_state->cracked = 0; + program_state->unique_cracked = 0; + program_state->num_completed = 0; + program_state->num_candidates = 0; + program_state->total = 0; + program_state->dict_count = 0; } // Entrypoint for worker thread -static int32_t mfkey_worker_thread(void *ctx) -{ - ProgramState *program_state = ctx; - program_state->mfkey_state = Initializing; - mfkey(program_state); - return 0; +static int32_t mfkey_worker_thread(void* ctx) { + ProgramState* program_state = ctx; + program_state->mfkey_state = Initializing; + mfkey(program_state); + return 0; } -int32_t mfkey_main() -{ - FuriMessageQueue *event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); +int32_t mfkey_main() { + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); - ProgramState *program_state = malloc(sizeof(ProgramState)); + ProgramState* program_state = malloc(sizeof(ProgramState)); - mfkey_state_init(program_state); + mfkey_state_init(program_state); - program_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + program_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); - // Set system callbacks - ViewPort *view_port = view_port_alloc(); - view_port_draw_callback_set(view_port, render_callback, program_state); - view_port_input_callback_set(view_port, input_callback, event_queue); + // Set system callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, render_callback, program_state); + view_port_input_callback_set(view_port, input_callback, event_queue); - // Open GUI and register view_port - Gui *gui = furi_record_open(RECORD_GUI); - gui_add_view_port(gui, view_port, GuiLayerFullscreen); + // Open GUI and register view_port + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); - program_state->mfkeythread = - furi_thread_alloc_ex("MFKeyWorker", 2048, mfkey_worker_thread, program_state); + program_state->mfkeythread = + furi_thread_alloc_ex("MFKeyWorker", 2048, mfkey_worker_thread, program_state); - InputEvent input_event; - for (bool main_loop = true; main_loop;) - { - FuriStatus event_status = furi_message_queue_get(event_queue, &input_event, 100); + InputEvent input_event; + for(bool main_loop = true; main_loop;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &input_event, 100); - furi_mutex_acquire(program_state->mutex, FuriWaitForever); + furi_mutex_acquire(program_state->mutex, FuriWaitForever); - if (event_status == FuriStatusOk) - { - if (input_event.type == InputTypePress) - { - switch (input_event.key) - { - case InputKeyRight: - if (program_state->mfkey_state == Ready) - { - program_state->mfkey_state = Help; - } - break; - case InputKeyOk: - if (program_state->mfkey_state == Ready) - { - furi_thread_start(program_state->mfkeythread); - } - break; - case InputKeyBack: - if (program_state->mfkey_state == Help) - { - program_state->mfkey_state = Ready; - } - else - { - program_state->close_thread_please = true; - // Wait until thread is finished - furi_thread_join(program_state->mfkeythread); - main_loop = false; - } - break; - default: - break; - } - } - } + if(event_status == FuriStatusOk) { + if(input_event.type == InputTypePress) { + switch(input_event.key) { + case InputKeyRight: + if(program_state->mfkey_state == Ready) { + program_state->mfkey_state = Help; + } + break; + case InputKeyOk: + if(program_state->mfkey_state == Ready) { + furi_thread_start(program_state->mfkeythread); + } + break; + case InputKeyBack: + if(program_state->mfkey_state == Help) { + program_state->mfkey_state = Ready; + } else { + program_state->close_thread_please = true; + // Wait until thread is finished + furi_thread_join(program_state->mfkeythread); + main_loop = false; + } + break; + default: + break; + } + } + } - furi_mutex_release(program_state->mutex); - view_port_update(view_port); - } + furi_mutex_release(program_state->mutex); + view_port_update(view_port); + } - // Thread joined in back event handler - furi_thread_free(program_state->mfkeythread); - view_port_enabled_set(view_port, false); - gui_remove_view_port(gui, view_port); - furi_record_close(RECORD_GUI); - view_port_free(view_port); - furi_message_queue_free(event_queue); - furi_mutex_free(program_state->mutex); - free(program_state); + // Thread joined in back event handler + furi_thread_free(program_state->mfkeythread); + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close(RECORD_GUI); + view_port_free(view_port); + furi_message_queue_free(event_queue); + furi_mutex_free(program_state->mutex); + free(program_state); - return 0; + return 0; } #pragma GCC pop_options diff --git a/applications/system/mfkey/mfkey.h b/applications/system/mfkey/mfkey.h index 7b5be5c5a..43e558874 100644 --- a/applications/system/mfkey/mfkey.h +++ b/applications/system/mfkey/mfkey.h @@ -9,115 +9,103 @@ #include #include -struct Crypto1State -{ - uint32_t odd, even; +struct Crypto1State { + uint32_t odd, even; }; -struct Msb -{ - int tail; - uint32_t states[768]; +struct Msb { + int tail; + uint32_t states[768]; }; -typedef enum -{ - MissingNonces, - ZeroNonces, - InsufficientRAM, +typedef enum { + MissingNonces, + ZeroNonces, + InsufficientRAM, } MFKeyError; -typedef enum -{ - Ready, - Initializing, - DictionaryAttack, - MFKeyAttack, - Complete, - Error, - Help, +typedef enum { + Ready, + Initializing, + DictionaryAttack, + MFKeyAttack, + Complete, + Error, + Help, } MFKeyState; // TODO: Can we eliminate any of the members of this struct? -typedef struct -{ - FuriMutex *mutex; - MFKeyError err; - MFKeyState mfkey_state; - int cracked; - int unique_cracked; - int num_completed; - int num_candidates; - int total; - int dict_count; - int search; - int eta_timestamp; - int eta_total; - int eta_round; - bool mfkey32_present; - bool nested_present; - bool close_thread_please; - FuriThread *mfkeythread; - KeysDict *cuid_dict; - MfClassicKey *key_buffer; - size_t key_buffer_size; - size_t key_buffer_count; +typedef struct { + FuriMutex* mutex; + MFKeyError err; + MFKeyState mfkey_state; + int cracked; + int unique_cracked; + int num_completed; + int num_candidates; + int total; + int dict_count; + int search; + int eta_timestamp; + int eta_total; + int eta_round; + bool mfkey32_present; + bool nested_present; + bool close_thread_please; + FuriThread* mfkeythread; + KeysDict* cuid_dict; + MfClassicKey* key_buffer; + size_t key_buffer_size; + size_t key_buffer_count; } ProgramState; -typedef enum -{ - mfkey32, - static_nested, - static_encrypted +typedef enum { + mfkey32, + static_nested, + static_encrypted } AttackType; -typedef struct -{ - AttackType attack; - MfClassicKey key; // key - uint32_t uid; // serial number - uint32_t nt0; // tag challenge first - uint32_t nt1; // tag challenge second - uint32_t uid_xor_nt0; // uid ^ nt0 - uint32_t uid_xor_nt1; // uid ^ nt1 - union - { - // Mfkey32 - struct - { - uint32_t p64; // 64th successor of nt0 - uint32_t p64b; // 64th successor of nt1 - uint32_t nr0_enc; // first encrypted reader challenge - uint32_t ar0_enc; // first encrypted reader response - uint32_t nr1_enc; // second encrypted reader challenge - uint32_t ar1_enc; // second encrypted reader response - }; - // Nested - struct - { - uint32_t ks1_1_enc; // first encrypted keystream - uint32_t ks1_2_enc; // second encrypted keystream - char par_1_str[5]; // first parity bits (string representation) - char par_2_str[5]; // second parity bits (string representation) - uint8_t par_1; // first parity bits - uint8_t par_2; // second parity bits - }; - }; +typedef struct { + AttackType attack; + MfClassicKey key; // key + uint32_t uid; // serial number + uint32_t nt0; // tag challenge first + uint32_t nt1; // tag challenge second + uint32_t uid_xor_nt0; // uid ^ nt0 + uint32_t uid_xor_nt1; // uid ^ nt1 + union { + // Mfkey32 + struct { + uint32_t p64; // 64th successor of nt0 + uint32_t p64b; // 64th successor of nt1 + uint32_t nr0_enc; // first encrypted reader challenge + uint32_t ar0_enc; // first encrypted reader response + uint32_t nr1_enc; // second encrypted reader challenge + uint32_t ar1_enc; // second encrypted reader response + }; + // Nested + struct { + uint32_t ks1_1_enc; // first encrypted keystream + uint32_t ks1_2_enc; // second encrypted keystream + char par_1_str[5]; // first parity bits (string representation) + char par_2_str[5]; // second parity bits (string representation) + uint8_t par_1; // first parity bits + uint8_t par_2; // second parity bits + }; + }; } MfClassicNonce; -typedef struct -{ - Stream *stream; - uint32_t total_nonces; - MfClassicNonce *remaining_nonce_array; - size_t remaining_nonces; +typedef struct { + Stream* stream; + uint32_t total_nonces; + MfClassicNonce* remaining_nonce_array; + size_t remaining_nonces; } MfClassicNonceArray; -struct KeysDict -{ - Stream *stream; - size_t key_size; - size_t key_size_symbols; - size_t total_keys; +struct KeysDict { + Stream* stream; + size_t key_size; + size_t key_size_symbols; + size_t total_keys; }; -#endif // MFKEY_H \ No newline at end of file +#endif // MFKEY_H From b07cb9401f1bd4a6a8442e03e88dff75731a6c2a Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 9 Sep 2025 16:48:08 +0300 Subject: [PATCH 044/192] subghz raw protocol fixes by WillyJL --- lib/subghz/protocols/raw.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/subghz/protocols/raw.c b/lib/subghz/protocols/raw.c index 96908130e..326576bde 100644 --- a/lib/subghz/protocols/raw.c +++ b/lib/subghz/protocols/raw.c @@ -291,9 +291,11 @@ void subghz_protocol_encoder_raw_stop(void* context) { furi_check(context); SubGhzProtocolEncoderRAW* instance = context; instance->is_running = false; - if(subghz_file_encoder_worker_is_running(instance->file_worker_encoder)) { + if(instance->file_worker_encoder && + subghz_file_encoder_worker_is_running(instance->file_worker_encoder)) { subghz_file_encoder_worker_stop(instance->file_worker_encoder); subghz_file_encoder_worker_free(instance->file_worker_encoder); + instance->file_worker_encoder = NULL; } } @@ -318,6 +320,7 @@ void subghz_protocol_raw_file_encoder_worker_set_callback_end( static bool subghz_protocol_encoder_raw_worker_init(SubGhzProtocolEncoderRAW* instance) { furi_assert(instance); + furi_check(!instance->file_worker_encoder); instance->file_worker_encoder = subghz_file_encoder_worker_alloc(); if(subghz_file_encoder_worker_start( From 19bb5cb0d92db87310ce20adc8e6abe75cea0ffe Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 9 Sep 2025 17:22:35 +0300 Subject: [PATCH 045/192] upd subghz remote --- 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 25ff072cc..0cec9db02 160000 --- a/applications/main/subghz_remote +++ b/applications/main/subghz_remote @@ -1 +1 @@ -Subproject commit 25ff072cc7f90a4af3a8b43bb418bb74dcf9da41 +Subproject commit 0cec9db022cdca2f5ead27115bf54c715abe1ca7 From efc608cf328c59d9457b7139aa01261be05716fb Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 9 Sep 2025 18:59:02 +0300 Subject: [PATCH 046/192] upd changelog [ci skip] --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06a3728aa..714670fb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## Main changes - Current API: 86.0 -* SubGHz: Tune Linear (edited by @WillyJL in PR #919) (add better EZCode support), Dickert MAHS decoders +* SubGHz: Tune Linear (edited by @WillyJL in PR #919 #) (add better EZCode support), Dickert MAHS decoders * SubGHz: Add ZKTeco 430.5 MHz add manually support * SubGHz: Add variant of 'Add Manually' menu with manual editing for each value (PR #909 #911 #914 | by @MrLego8-9) * OFW PR 4265: NFC: Fix read crash with unexpectedly large MFC AUTH(0) response (by @WillyJL) From 55d2348171462e73fcce6ac3aaded3bf5c9b1a5e Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 9 Sep 2025 22:07:40 +0300 Subject: [PATCH 047/192] fix mfkey [ci skip] --- applications/system/mfkey/mfkey.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/system/mfkey/mfkey.c b/applications/system/mfkey/mfkey.c index 9e3e71847..4d55a5b03 100644 --- a/applications/system/mfkey/mfkey.c +++ b/applications/system/mfkey/mfkey.c @@ -955,7 +955,7 @@ void mfkey(ProgramState* program_state) { keys_dict_add_key(user_dict, keyarray[i].data, sizeof(MfClassicKey)); } if(keyarray_size > 0) { - dolphin_deed(DolphinDeedNfcKeyAdd); + dolphin_deed(DolphinDeedNfcMfcAdd); } free(nonce_arr); keys_dict_free(user_dict); From 3efa40b079eff5290f3e50f15069696d33476138 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 10 Sep 2025 00:10:55 +0300 Subject: [PATCH 048/192] upd changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 714670fb2..31ea11bf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## Main changes - Current API: 86.0 -* SubGHz: Tune Linear (edited by @WillyJL in PR #919 #) (add better EZCode support), Dickert MAHS decoders +* SubGHz: Tune Linear (edited by @WillyJL in PR #919 #920) (add better EZCode support), Dickert MAHS decoders +* SubGHz: RAW protocol fixes (by @WillyJL) * SubGHz: Add ZKTeco 430.5 MHz add manually support * SubGHz: Add variant of 'Add Manually' menu with manual editing for each value (PR #909 #911 #914 | by @MrLego8-9) * OFW PR 4265: NFC: Fix read crash with unexpectedly large MFC AUTH(0) response (by @WillyJL) From 1eefbb77dab4edd4380b7701c6974ca2df3f24c7 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Tue, 16 Sep 2025 01:50:33 +0200 Subject: [PATCH 049/192] CI: Fix redeploy for PRs --- .github/workflows/build.yml | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4f7e2972b..a1e15b6ec 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -85,19 +85,22 @@ jobs: if ${{ github.ref_name == 'dev' }} ; then SHOULD_DEVBUILD=${{ !contains(github.event.head_commit.message, '--nobuild') }} SHOULD_UPLOAD=$SHOULD_DEVBUILD + SHOULD_DEPLOY=$SHOULD_DEVBUILD elif ${{ startsWith(github.ref, 'refs/tags/') }} ; then SHOULD_UPLOAD=true fi elif ${{ !!github.event.pull_request }} ; then - SHOULD_PRCOMMENT=${{ matrix.target == env.DEFAULT_TARGET }} - SHOULD_UPLOAD=true + SHOULD_PRBUILD=${{ matrix.target == env.DEFAULT_TARGET }} + SHOULD_UPLOAD=$SHOULD_PRBUILD + SHOULD_DEPLOY=$SHOULD_PRBUILD else echo "What?" exit 1 fi echo "SHOULD_DEVBUILD=$SHOULD_DEVBUILD" >> $GITHUB_ENV - echo "SHOULD_PRCOMMENT=$SHOULD_PRCOMMENT" >> $GITHUB_ENV + echo "SHOULD_PRBUILD=$SHOULD_PRBUILD" >> $GITHUB_ENV echo "SHOULD_UPLOAD=$SHOULD_UPLOAD" >> $GITHUB_ENV + echo "SHOULD_DEPLOY=$SHOULD_DEPLOY" >> $GITHUB_ENV - name: "Build the firmware and apps" id: build-fw @@ -150,7 +153,7 @@ jobs: "${{ secrets.INDEXER_URL }}"/firmware/uploadfiles - name: "Force deploy website to refresh devbuilds" - if: ${{ env.SHOULD_DEVBUILD == 'true' }} + if: ${{ env.SHOULD_DEPLOY == 'true' }} uses: Codex-/return-dispatch@v2 id: force-deploy with: @@ -163,7 +166,7 @@ jobs: workflow_job_steps_retry_seconds: 5 - name: "Calculate DFU sizes" - if: ${{ env.SHOULD_PRCOMMENT == 'true' }} + if: ${{ env.SHOULD_PRBUILD == 'true' }} run: | curl -L "${{ secrets.INDEXER_URL }}"/firmware/development/f7/full_dfu -o dev.dfu dfu_size_new=$(du --apparent-size -B 1 artifacts/flipper-z-${TARGET}-full-*.dfu | cut -f1) @@ -186,7 +189,7 @@ jobs: echo "FLASH_USABLE=$FLASH_USABLE" >> $GITHUB_ENV - name: "Find previous comment" - if: ${{ env.SHOULD_PRCOMMENT == 'true' }} + if: ${{ env.SHOULD_PRBUILD == 'true' }} uses: peter-evans/find-comment@v3 id: find-comment with: @@ -195,7 +198,7 @@ jobs: body-includes: "Compiled ${{ matrix.target }} firmware for commit" - name: "Create or update comment" - if: ${{ env.SHOULD_PRCOMMENT == 'true' }} + if: ${{ env.SHOULD_PRBUILD == 'true' }} uses: peter-evans/create-or-update-comment@v4 with: comment-id: ${{ steps.find-comment.outputs.comment-id }} @@ -209,7 +212,7 @@ jobs: edit-mode: replace - name: "Wait for deploy (Run ID ${{ steps.force-deploy.outputs.run_id }})" - if: ${{ env.SHOULD_DEVBUILD == 'true' }} + if: ${{ env.SHOULD_DEPLOY == 'true' }} uses: Codex-/await-remote-run@v1 with: token: ${{ github.token }} From 9deecbc363c2a297e103c107778f16301a4d4bc8 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Tue, 16 Sep 2025 01:52:45 +0200 Subject: [PATCH 050/192] CI: Less scary looking force push webhook --nobuild --- .github/workflow_data/webhook.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflow_data/webhook.py b/.github/workflow_data/webhook.py index 70bda300b..c92d4594d 100644 --- a/.github/workflow_data/webhook.py +++ b/.github/workflow_data/webhook.py @@ -33,7 +33,7 @@ if __name__ == "__main__": change = ( "Force Push" if event["forced"] and not count - else f"{count} New Commit{'' if count == 1 else 's'}" + else f"{count} New Commit{'' if count == 1 else 's'}{' (Force Push)' if event['forced'] else ''}" ) desc = f"[**{change}**]({event['compare']}) | [{branch}]({event['repository']['html_url']}/tree/{branch})\n" for i, commit in enumerate(event["commits"]): @@ -49,7 +49,7 @@ if __name__ == "__main__": desc = desc.rsplit("\n", 1)[0] + f"\n+ {count - i} more commits" break url = event["compare"] - color = 16723712 if event["forced"] else 11761899 + color = 11761899 case "release": webhook = "RELEASE_WEBHOOK" From d382ecf44a128cba8e9b90c882c4b4662e708fbd Mon Sep 17 00:00:00 2001 From: WillyJL Date: Tue, 16 Sep 2025 02:28:57 +0200 Subject: [PATCH 051/192] CI: Web Updater link in PR comment --nobuild --- .github/workflows/build.yml | 29 +++++++++++++++-------------- scripts/get_env.py | 5 +++++ 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a1e15b6ec..ea1ed9549 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -197,20 +197,6 @@ jobs: comment-author: "github-actions[bot]" body-includes: "Compiled ${{ matrix.target }} firmware for commit" - - name: "Create or update comment" - if: ${{ env.SHOULD_PRBUILD == 'true' }} - uses: peter-evans/create-or-update-comment@v4 - with: - comment-id: ${{ steps.find-comment.outputs.comment-id }} - issue-number: ${{ github.event.pull_request.number }} - body: | - **Compiled ${{ matrix.target }} firmware for commit `${{steps.names.outputs.commit_sha}}`:** - - [☁️ Flipper Lab/App](https://lab.flipper.net/?url=${{secrets.INDEXER_URL}}/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=mntm-${{steps.names.outputs.branch_name}}&version=${{steps.names.outputs.commit_sha}}) - - [📦 qFlipper Package](${{secrets.INDEXER_URL}}/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz) - - DFU Size: `${{ env.DFU_SIZE }}` (`${{ env.DFU_DIFF }}` from dev) - - Free Flash: `${{ env.FLASH_FREE }}` (`${{ env.FLASH_USABLE }}` usable) - edit-mode: replace - - name: "Wait for deploy (Run ID ${{ steps.force-deploy.outputs.run_id }})" if: ${{ env.SHOULD_DEPLOY == 'true' }} uses: Codex-/await-remote-run@v1 @@ -222,6 +208,21 @@ jobs: run_timeout_seconds: 300 poll_interval_ms: 5000 + - name: "Create or update comment" + if: ${{ env.SHOULD_PRBUILD == 'true' }} + uses: peter-evans/create-or-update-comment@v4 + with: + comment-id: ${{ steps.find-comment.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body: | + **Compiled ${{ matrix.target }} firmware for commit `${{steps.names.outputs.commit_sha}}`:** + - [🖥️ Web Updater](https://momentum-fw.dev/update?branch=${{steps.names.outputs.branch_name_urlencoded}}) + - [☁️ Flipper Lab/App](https://lab.flipper.net/?url=${{secrets.INDEXER_URL}}/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=mntm-${{steps.names.outputs.branch_name}}&version=${{steps.names.outputs.commit_sha}}) + - [📦 qFlipper Package](${{secrets.INDEXER_URL}}/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz) + - DFU Size: `${{ env.DFU_SIZE }}` (`${{ env.DFU_DIFF }}` from dev) + - Free Flash: `${{ env.FLASH_FREE }}` (`${{ env.FLASH_USABLE }}` usable) + edit-mode: replace + - name: "Send devbuild webhook" env: INDEXER_URL: ${{ secrets.INDEXER_URL }} diff --git a/scripts/get_env.py b/scripts/get_env.py index 643418c4b..3f6d41977 100755 --- a/scripts/get_env.py +++ b/scripts/get_env.py @@ -9,6 +9,7 @@ import re import shlex import ssl import string +import urllib.parse import urllib.request @@ -63,6 +64,7 @@ def get_details(event, args): ref = event["ref"] data["commit_sha"] = data["commit_hash"][:8] data["branch_name"] = re.sub("refs/\w+/", "", ref) + data["branch_name_urlencoded"] = urllib.parse.quote_plus(data["branch_name"]) data["suffix"] = ( "mntm-" + data["branch_name"].removeprefix("mntm-").replace("/", "-") @@ -94,6 +96,9 @@ def add_envs(data, gh_env_file, gh_out_file, args): add_env("DIST_SUFFIX", data["suffix"], gh_env_file) add_env("WORKFLOW_BRANCH_OR_TAG", data["branch_name"], gh_env_file) add_set_output_var("branch_name", data["branch_name"], gh_out_file) + add_set_output_var( + "branch_name_urlencoded", data["branch_name_urlencoded"], gh_out_file + ) add_set_output_var("commit_sha", data["commit_sha"], gh_out_file) add_set_output_var("default_target", os.getenv("DEFAULT_TARGET"), gh_out_file) add_set_output_var("suffix", data["suffix"], gh_out_file) From 2c2228a4b91af0b88360b8e3ac7d55144ccb77e6 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 21 Sep 2025 16:45:31 +0300 Subject: [PATCH 052/192] merge ofw pr 4271 also thanks WillyJL link to PR https://github.com/flipperdevices/flipperzero-firmware/pull/4271/files --- .../main/nfc/helpers/mf_ultralight_auth.c | 6 +- .../mf_ultralight/mf_ultralight.c | 51 ++- .../protocol_support/nfc_protocol_support.c | 1 + applications/main/nfc/nfc_app_i.h | 16 +- .../nfc/assets/mf_ultralight_c_dict.nfc | 55 ++++ .../main/nfc/scenes/nfc_scene_config.h | 7 + .../nfc/scenes/nfc_scene_delete_success.c | 4 + .../main/nfc/scenes/nfc_scene_extra_actions.c | 10 + .../nfc/scenes/nfc_scene_mf_classic_keys.c | 2 - .../nfc_scene_mf_ultralight_c_dict_attack.c | 238 ++++++++++++++ .../scenes/nfc_scene_mf_ultralight_c_keys.c | 96 ++++++ .../nfc_scene_mf_ultralight_c_keys_add.c | 63 ++++ .../nfc_scene_mf_ultralight_c_keys_delete.c | 108 +++++++ .../nfc_scene_mf_ultralight_c_keys_list.c | 66 ++++ ...cene_mf_ultralight_c_keys_warn_duplicate.c | 49 +++ .../main/nfc/scenes/nfc_scene_save_success.c | 4 + applications/main/nfc/views/dict_attack.c | 299 +++++++++++------- applications/main/nfc/views/dict_attack.h | 13 + .../protocols/mf_ultralight/mf_ultralight.h | 6 - .../mf_ultralight/mf_ultralight_poller.c | 170 ++++++---- .../mf_ultralight/mf_ultralight_poller.h | 34 +- .../mf_ultralight/mf_ultralight_poller_i.c | 33 -- .../mf_ultralight/mf_ultralight_poller_i.h | 3 +- lib/toolbox/keys_dict.c | 19 +- targets/f7/api_symbols.csv | 1 - 25 files changed, 1091 insertions(+), 263 deletions(-) create mode 100644 applications/main/nfc/resources/nfc/assets/mf_ultralight_c_dict.nfc create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_delete.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_list.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_warn_duplicate.c diff --git a/applications/main/nfc/helpers/mf_ultralight_auth.c b/applications/main/nfc/helpers/mf_ultralight_auth.c index fccf50cdd..e97649cb3 100644 --- a/applications/main/nfc/helpers/mf_ultralight_auth.c +++ b/applications/main/nfc/helpers/mf_ultralight_auth.c @@ -18,11 +18,9 @@ void mf_ultralight_auth_free(MfUltralightAuth* instance) { void mf_ultralight_auth_reset(MfUltralightAuth* instance) { furi_assert(instance); - uint32_t default_password = MF_ULTRALIGHT_DEFAULT_PASSWORD; - instance->type = MfUltralightAuthTypeNone; - memcpy(&instance->password, &default_password, sizeof(MfUltralightAuthPassword)); - memcpy(&instance->tdes_key, MF_ULTRALIGHT_C_DEFAULT_KEY, sizeof(MfUltralightC3DesAuthKey)); + memset(&instance->password, 0, sizeof(MfUltralightAuthPassword)); + memset(&instance->tdes_key, 0, sizeof(MfUltralightC3DesAuthKey)); memset(&instance->pack, 0, sizeof(MfUltralightAuthPack)); } diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index 8eb42b89b..aa3cc2e75 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -14,6 +14,7 @@ enum { SubmenuIndexUnlock = SubmenuIndexCommonMax, SubmenuIndexUnlockByReader, SubmenuIndexUnlockByPassword, + SubmenuIndexDictAttack }; enum { @@ -149,7 +150,15 @@ static NfcCommand } if(!mf_ultralight_event->data->auth_context.skip_auth) { mf_ultralight_event->data->auth_context.password = instance->mf_ul_auth->password; - mf_ultralight_event->data->auth_context.tdes_key = instance->mf_ul_auth->tdes_key; + + // Only set tdes_key for Manual/Reader auth types, not for dictionary attacks + if(instance->mf_ul_auth->type == MfUltralightAuthTypeManual || + instance->mf_ul_auth->type == MfUltralightAuthTypeReader) { + mf_ultralight_event->data->key_request_data.key = instance->mf_ul_auth->tdes_key; + mf_ultralight_event->data->key_request_data.key_provided = true; + } else { + mf_ultralight_event->data->key_request_data.key_provided = false; + } } } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthSuccess) { instance->mf_ul_auth->pack = mf_ultralight_event->data->auth_context.pack; @@ -165,15 +174,31 @@ static void nfc_scene_read_on_enter_mf_ultralight(NfcApp* instance) { bool nfc_scene_read_on_event_mf_ultralight(NfcApp* instance, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventCardDetected) { - nfc_unlock_helper_card_detected_handler(instance); - } else if(event.event == NfcCustomEventPollerIncomplete) { - notification_message(instance->notifications, &sequence_semi_success); + if(event.event == NfcCustomEventPollerSuccess) { + notification_message(instance->notifications, &sequence_success); scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); dolphin_deed(DolphinDeedNfcReadSuccess); + return true; + } else if(event.event == NfcCustomEventPollerIncomplete) { + const MfUltralightData* data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight); + if(data->type == MfUltralightTypeMfulC && + instance->mf_ul_auth->type == MfUltralightAuthTypeNone) { + // Start dict attack for MFUL C cards only if no specific auth was attempted + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCDictAttack); + } else { + if(data->pages_read == data->pages_total) { + notification_message(instance->notifications, &sequence_success); + } else { + notification_message(instance->notifications, &sequence_semi_success); + } + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + } + return true; } } - return true; + return false; } static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instance) { @@ -197,6 +222,14 @@ static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instanc SubmenuIndexUnlock, nfc_protocol_support_common_submenu_callback, instance); + if(data->type == MfUltralightTypeMfulC) { + submenu_add_item( + submenu, + "Unlock with Dictionary", + SubmenuIndexDictAttack, + nfc_protocol_support_common_submenu_callback, + instance); + } } } @@ -249,6 +282,12 @@ static bool nfc_scene_read_and_saved_menu_on_event_mf_ultralight( NfcSceneMfUltralightUnlockMenu; scene_manager_next_scene(instance->scene_manager, next_scene); consumed = true; + } else if(event.event == SubmenuIndexDictAttack) { + if(!scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneMfUltralightCDictAttack)) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCDictAttack); + } + consumed = true; } } return consumed; diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c index 008f9c317..2ee29668c 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -680,6 +680,7 @@ static void nfc_protocol_support_scene_save_name_on_enter(NfcApp* instance) { furi_string_replace(prefix, " Plus", "+"); // NTAG I2C+ furi_string_replace(prefix, " (Unknown)", ""); furi_string_replace_all(prefix, " ", "_"); + furi_string_replace_all(prefix, "/", "_"); name_generator_make_auto( instance->text_store, NFC_TEXT_STORE_SIZE, furi_string_get_cstr(prefix)); furi_string_free(prefix); diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 4336d25e0..a142e47da 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -50,6 +50,7 @@ #include #include #include +#include #include #include @@ -67,7 +68,7 @@ #define NFC_NAME_SIZE 22 #define NFC_TEXT_STORE_SIZE 128 -#define NFC_BYTE_INPUT_STORE_SIZE 10 +#define NFC_BYTE_INPUT_STORE_SIZE 16 #define NFC_LOG_SIZE_MAX (1024) #define NFC_APP_FOLDER EXT_PATH("nfc") #define NFC_APP_EXTENSION ".nfc" @@ -83,6 +84,10 @@ #define NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict.nfc") #define NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH \ (NFC_APP_FOLDER "/assets/mf_classic_dict_nested.nfc") +#define NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH \ + (NFC_APP_FOLDER "/assets/mf_ultralight_c_dict_user.nfc") +#define NFC_APP_MF_ULTRALIGHT_C_DICT_SYSTEM_PATH \ + (NFC_APP_FOLDER "/assets/mf_ultralight_c_dict.nfc") #define NFC_MFKEY32_APP_PATH (EXT_PATH("apps/NFC/mfkey.fap")) @@ -110,6 +115,14 @@ typedef struct { bool enhanced_dict; } NfcMfClassicDictAttackContext; +typedef struct { + KeysDict* dict; + bool auth_success; + bool is_card_present; + size_t dict_keys_total; + size_t dict_keys_current; +} NfcMfUltralightCDictContext; + struct NfcApp { DialogsApp* dialogs; Storage* storage; @@ -148,6 +161,7 @@ struct NfcApp { MfUltralightAuth* mf_ul_auth; SlixUnlock* slix_unlock; NfcMfClassicDictAttackContext nfc_dict_context; + NfcMfUltralightCDictContext mf_ultralight_c_dict_context; Mfkey32Logger* mfkey32_logger; MfUserDict* mf_user_dict; MfClassicKeyCache* mfc_key_cache; diff --git a/applications/main/nfc/resources/nfc/assets/mf_ultralight_c_dict.nfc b/applications/main/nfc/resources/nfc/assets/mf_ultralight_c_dict.nfc new file mode 100644 index 000000000..fa5dbb1fb --- /dev/null +++ b/applications/main/nfc/resources/nfc/assets/mf_ultralight_c_dict.nfc @@ -0,0 +1,55 @@ +# Sample Key (BREAKMEIFYOUCAN!) +425245414B4D454946594F5543414E21 +# Hexadecimal-Reversed Sample Key +12E4143455F495649454D4B414542524 +# Byte-Reversed Sample Key (!NACUOYFIEMKAERB) +214E4143554F594649454D4B41455242 +# Semnox Key (IEMKAERB!NACUOY ) +49454D4B41455242214E4143554F5900 +# Modified Semnox Key (IEMKAERB!NACUOYF) +49454D4B41455242214E4143554F5946 + +# Mix of Proxmark and ChameleonMiniLiveDebugger +00000000000000000000000000000000 +000102030405060708090A0B0C0D0E0F +01010101010101010101010101010101 +FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +00112233445566778899AABBCCDDEEFF +47454D5850524553534F53414D504C45 +79702553797025537970255379702553 +4E617468616E2E4C6920546564647920 +43464F494D48504E4C4359454E528841 +6AC292FAA1315B4D858AB3A3D7D5933A +404142434445464748494A4B4C4D4E4F +2B7E151628AED2A6ABF7158809CF4F3C +FBEED618357133667C85E08F7236A8DE +F7DDAC306AE266CCF90BC11EE46D513B +54686973206973206D79206B65792020 +A0A1A2A3A4A5A6A7A0A1A2A3A4A5A6A7 +B0B1B2B3B4B5B6B7B0B1B2B3B4B5B6B7 +B0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF +D3F7D3F7D3F7D3F7D3F7D3F7D3F7D3F7 +11111111111111111111111111111111 +22222222222222222222222222222222 +33333333333333333333333333333333 +44444444444444444444444444444444 +55555555555555555555555555555555 +66666666666666666666666666666666 +77777777777777777777777777777777 +88888888888888888888888888888888 +99999999999999999999999999999999 +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC +DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +0102030405060708090A0B0C0D0E0F10 +00010203040506070809101112131415 +01020304050607080910111213141516 +16151413121110090807060504030201 +15141312111009080706050403020100 +0F0E0D0C0B0A09080706050403020100 +100F0E0D0C0B0A090807060504030201 +303132333435363738393A3B3C3D3E3F +9CABF398358405AE2F0E2B3D31C99A8A +605F5E5D5C5B5A59605F5E5D5C5B5A59 diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index b902bc940..d498eb90c 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -26,6 +26,7 @@ ADD_SCENE(nfc, retry_confirm, RetryConfirm) ADD_SCENE(nfc, exit_confirm, ExitConfirm) ADD_SCENE(nfc, save_confirm, SaveConfirm) +ADD_SCENE(nfc, mf_ultralight_c_dict_attack, MfUltralightCDictAttack) ADD_SCENE(nfc, mf_ultralight_unlock_menu, MfUltralightUnlockMenu) ADD_SCENE(nfc, mf_ultralight_unlock_warn, MfUltralightUnlockWarn) ADD_SCENE(nfc, mf_ultralight_key_input, MfUltralightKeyInput) @@ -52,6 +53,12 @@ ADD_SCENE(nfc, mf_classic_keys_delete, MfClassicKeysDelete) ADD_SCENE(nfc, mf_classic_keys_add, MfClassicKeysAdd) ADD_SCENE(nfc, mf_classic_keys_warn_duplicate, MfClassicKeysWarnDuplicate) +ADD_SCENE(nfc, mf_ultralight_c_keys, MfUltralightCKeys) +ADD_SCENE(nfc, mf_ultralight_c_keys_list, MfUltralightCKeysList) +ADD_SCENE(nfc, mf_ultralight_c_keys_delete, MfUltralightCKeysDelete) +ADD_SCENE(nfc, mf_ultralight_c_keys_add, MfUltralightCKeysAdd) +ADD_SCENE(nfc, mf_ultralight_c_keys_warn_duplicate, MfUltralightCKeysWarnDuplicate) + ADD_SCENE(nfc, set_type, SetType) ADD_SCENE(nfc, set_sak, SetSak) ADD_SCENE(nfc, set_atqa, SetAtqa) diff --git a/applications/main/nfc/scenes/nfc_scene_delete_success.c b/applications/main/nfc/scenes/nfc_scene_delete_success.c index d41e52549..d8308addd 100644 --- a/applications/main/nfc/scenes/nfc_scene_delete_success.c +++ b/applications/main/nfc/scenes/nfc_scene_delete_success.c @@ -28,6 +28,10 @@ bool nfc_scene_delete_success_on_event(void* context, SceneManagerEvent event) { if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicKeys)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneMfClassicKeys); + } else if(scene_manager_has_previous_scene( + nfc->scene_manager, NfcSceneMfUltralightCKeys)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneMfUltralightCKeys); } else { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneFileSelect); diff --git a/applications/main/nfc/scenes/nfc_scene_extra_actions.c b/applications/main/nfc/scenes/nfc_scene_extra_actions.c index 2943c0c55..6720b2d7b 100644 --- a/applications/main/nfc/scenes/nfc_scene_extra_actions.c +++ b/applications/main/nfc/scenes/nfc_scene_extra_actions.c @@ -3,6 +3,7 @@ enum SubmenuIndex { SubmenuIndexReadCardType, SubmenuIndexMfClassicKeys, + SubmenuIndexMfUltralightCKeys, SubmenuIndexMfUltralightUnlock, SubmenuIndexSlixUnlock, }; @@ -29,6 +30,12 @@ void nfc_scene_extra_actions_on_enter(void* context) { SubmenuIndexMfClassicKeys, nfc_scene_extra_actions_submenu_callback, instance); + submenu_add_item( + submenu, + "MIFARE Ultralight C Keys", + SubmenuIndexMfUltralightCKeys, + nfc_scene_extra_actions_submenu_callback, + instance); submenu_add_item( submenu, "Unlock NTAG/Ultralight", @@ -54,6 +61,9 @@ bool nfc_scene_extra_actions_on_event(void* context, SceneManagerEvent event) { if(event.event == SubmenuIndexMfClassicKeys) { scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicKeys); consumed = true; + } else if(event.event == SubmenuIndexMfUltralightCKeys) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCKeys); + consumed = true; } else if(event.event == SubmenuIndexMfUltralightUnlock) { mf_ultralight_auth_reset(instance->mf_ul_auth); scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c index eaa054149..7ee203285 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c @@ -1,7 +1,5 @@ #include "../nfc_app_i.h" -#define NFC_SCENE_MF_CLASSIC_KEYS_MAX (100) - void nfc_scene_mf_classic_keys_widget_callback(GuiButtonType result, InputType type, void* context) { NfcApp* instance = context; if(type == InputTypeShort) { diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c new file mode 100644 index 000000000..b1e24def2 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c @@ -0,0 +1,238 @@ +#include "../nfc_app_i.h" +#include + +#define TAG "NfcMfUlCDictAttack" + +// TODO: Support card_detected properly + +enum { + DictAttackStateUserDictInProgress, + DictAttackStateSystemDictInProgress, +}; + +NfcCommand nfc_mf_ultralight_c_dict_attack_worker_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.protocol == NfcProtocolMfUltralight); + NfcCommand command = NfcCommandContinue; + NfcApp* instance = context; + MfUltralightPollerEvent* poller_event = event.event_data; + + if(poller_event->type == MfUltralightPollerEventTypeRequestMode) { + poller_event->data->poller_mode = MfUltralightPollerModeDictAttack; + command = NfcCommandContinue; + } else if(poller_event->type == MfUltralightPollerEventTypeRequestKey) { + MfUltralightC3DesAuthKey key = {}; + if(keys_dict_get_next_key( + instance->mf_ultralight_c_dict_context.dict, + key.data, + sizeof(MfUltralightC3DesAuthKey))) { + poller_event->data->key_request_data.key = key; + poller_event->data->key_request_data.key_provided = true; + instance->mf_ultralight_c_dict_context.dict_keys_current++; + + if(instance->mf_ultralight_c_dict_context.dict_keys_current % 10 == 0) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); + } + } else { + poller_event->data->key_request_data.key_provided = false; + } + } else if(poller_event->type == MfUltralightPollerEventTypeReadSuccess) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(instance->poller)); + // Check if this is a successful authentication by looking at the poller's auth context + const MfUltralightData* data = nfc_poller_get_data(instance->poller); + + // Update page information + dict_attack_set_pages_read(instance->dict_attack, data->pages_read); + dict_attack_set_pages_total(instance->dict_attack, data->pages_total); + + if(data->pages_read == data->pages_total) { + // Full read indicates successful authentication in dict attack mode + instance->mf_ultralight_c_dict_context.auth_success = true; + dict_attack_set_key_found(instance->dict_attack, true); + } + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventDictAttackComplete); + command = NfcCommandStop; + } + return command; +} + +void nfc_scene_mf_ultralight_c_dict_attack_dict_attack_result_callback( + DictAttackEvent event, + void* context) { + furi_assert(context); + NfcApp* instance = context; + if(event == DictAttackEventSkipPressed) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventDictAttackSkip); + } +} + +void nfc_scene_mf_ultralight_c_dict_attack_prepare_view(NfcApp* instance) { + uint32_t state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightCDictAttack); + + // Set attack type to Ultralight C + dict_attack_set_type(instance->dict_attack, DictAttackTypeMfUltralightC); + + if(state == DictAttackStateUserDictInProgress) { + do { + if(!keys_dict_check_presence(NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH)) { + state = DictAttackStateSystemDictInProgress; + break; + } + instance->mf_ultralight_c_dict_context.dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + if(keys_dict_get_total_keys(instance->mf_ultralight_c_dict_context.dict) == 0) { + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + state = DictAttackStateSystemDictInProgress; + break; + } + dict_attack_set_header(instance->dict_attack, "MFUL C User Dictionary"); + } while(false); + } + if(state == DictAttackStateSystemDictInProgress) { + instance->mf_ultralight_c_dict_context.dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_SYSTEM_PATH, + KeysDictModeOpenExisting, + sizeof(MfUltralightC3DesAuthKey)); + dict_attack_set_header(instance->dict_attack, "MFUL C System Dictionary"); + } + + instance->mf_ultralight_c_dict_context.dict_keys_total = + keys_dict_get_total_keys(instance->mf_ultralight_c_dict_context.dict); + dict_attack_set_total_dict_keys( + instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_total); + instance->mf_ultralight_c_dict_context.dict_keys_current = 0; + dict_attack_set_current_dict_key( + instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_current); + + // Set initial Ultralight C specific values + dict_attack_set_key_found(instance->dict_attack, false); + dict_attack_set_pages_total(instance->dict_attack, 48); // Ultralight C page count + dict_attack_set_pages_read(instance->dict_attack, 0); + + dict_attack_set_callback( + instance->dict_attack, + nfc_scene_mf_ultralight_c_dict_attack_dict_attack_result_callback, + instance); + scene_manager_set_scene_state(instance->scene_manager, NfcSceneMfUltralightCDictAttack, state); +} + +void nfc_scene_mf_ultralight_c_dict_attack_on_enter(void* context) { + NfcApp* instance = context; + + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfUltralightCDictAttack, + DictAttackStateUserDictInProgress); + nfc_scene_mf_ultralight_c_dict_attack_prepare_view(instance); + + // Setup and start worker + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); + nfc_poller_start(instance->poller, nfc_mf_ultralight_c_dict_attack_worker_callback, instance); + + dict_attack_set_card_state(instance->dict_attack, true); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewDictAttack); + nfc_blink_read_start(instance); +} + +void nfc_scene_mf_ul_c_dict_attack_update_view(NfcApp* instance) { + dict_attack_set_card_state( + instance->dict_attack, instance->mf_ultralight_c_dict_context.is_card_present); + dict_attack_set_current_dict_key( + instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_current); +} + +bool nfc_scene_mf_ultralight_c_dict_attack_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + uint32_t state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightCDictAttack); + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventDictAttackComplete) { + if(state == DictAttackStateUserDictInProgress) { + if(instance->mf_ultralight_c_dict_context.auth_success) { + notification_message(instance->notifications, &sequence_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + consumed = true; + } else { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfUltralightCDictAttack, + DictAttackStateSystemDictInProgress); + nfc_scene_mf_ultralight_c_dict_attack_prepare_view(instance); + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); + nfc_poller_start( + instance->poller, + nfc_mf_ultralight_c_dict_attack_worker_callback, + instance); + consumed = true; + } + } else { + // Could check if card is fully read here like MFC dict attack, but found key means fully read + if(instance->mf_ultralight_c_dict_context.auth_success) { + notification_message(instance->notifications, &sequence_success); + } else { + notification_message(instance->notifications, &sequence_semi_success); + } + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + consumed = true; + } + } else if(event.event == NfcCustomEventDictAttackDataUpdate) { + dict_attack_set_current_dict_key( + instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_current); + consumed = true; + } else if(event.event == NfcCustomEventDictAttackSkip) { + if(state == DictAttackStateUserDictInProgress) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfUltralightCDictAttack, + DictAttackStateSystemDictInProgress); + nfc_scene_mf_ultralight_c_dict_attack_prepare_view(instance); + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); + nfc_poller_start( + instance->poller, nfc_mf_ultralight_c_dict_attack_worker_callback, instance); + } else { + notification_message(instance->notifications, &sequence_semi_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + } + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + scene_manager_next_scene(instance->scene_manager, NfcSceneExitConfirm); + consumed = true; + } + return consumed; +} + +void nfc_scene_mf_ultralight_c_dict_attack_on_exit(void* context) { + NfcApp* instance = context; + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfUltralightCDictAttack, + DictAttackStateUserDictInProgress); + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + instance->mf_ultralight_c_dict_context.dict_keys_total = 0; + instance->mf_ultralight_c_dict_context.dict_keys_current = 0; + instance->mf_ultralight_c_dict_context.auth_success = false; + instance->mf_ultralight_c_dict_context.is_card_present = false; + nfc_blink_stop(instance); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys.c new file mode 100644 index 000000000..9bf96f0b4 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys.c @@ -0,0 +1,96 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_ultralight_c_keys_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + NfcApp* instance = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(instance->view_dispatcher, result); + } +} + +void nfc_scene_mf_ultralight_c_keys_on_enter(void* context) { + NfcApp* instance = context; + + // Load flipper dict keys total + uint32_t flipper_dict_keys_total = 0; + KeysDict* dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_SYSTEM_PATH, + KeysDictModeOpenExisting, + sizeof(MfUltralightC3DesAuthKey)); + flipper_dict_keys_total = keys_dict_get_total_keys(dict); + keys_dict_free(dict); + + // Load user dict keys total + uint32_t user_dict_keys_total = 0; + dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + user_dict_keys_total = keys_dict_get_total_keys(dict); + keys_dict_free(dict); + + FuriString* temp_str = furi_string_alloc(); + widget_add_string_element( + instance->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "MIFARE Ultralight C Keys"); + furi_string_printf(temp_str, "System dict: %lu", flipper_dict_keys_total); + widget_add_string_element( + instance->widget, + 0, + 20, + AlignLeft, + AlignTop, + FontSecondary, + furi_string_get_cstr(temp_str)); + furi_string_printf(temp_str, "User dict: %lu", user_dict_keys_total); + widget_add_string_element( + instance->widget, + 0, + 32, + AlignLeft, + AlignTop, + FontSecondary, + furi_string_get_cstr(temp_str)); + widget_add_icon_element(instance->widget, 87, 13, &I_Keychain_39x36); + widget_add_button_element( + instance->widget, + GuiButtonTypeCenter, + "Add", + nfc_scene_mf_ultralight_c_keys_widget_callback, + instance); + if(user_dict_keys_total > 0) { + widget_add_button_element( + instance->widget, + GuiButtonTypeRight, + "List", + nfc_scene_mf_ultralight_c_keys_widget_callback, + instance); + } + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_mf_ultralight_c_keys_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeCenter) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCKeysAdd); + consumed = true; + } else if(event.event == GuiButtonTypeRight) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCKeysList); + consumed = true; + } + } + + return consumed; +} + +void nfc_scene_mf_ultralight_c_keys_on_exit(void* context) { + NfcApp* instance = context; + + widget_reset(instance->widget); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c new file mode 100644 index 000000000..aa4c2fe1c --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c @@ -0,0 +1,63 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_ultralight_c_keys_add_byte_input_callback(void* context) { + NfcApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventByteInputDone); +} + +void nfc_scene_mf_ultralight_c_keys_add_on_enter(void* context) { + NfcApp* instance = context; + + // Setup view + ByteInput* byte_input = instance->byte_input; + byte_input_set_header_text(byte_input, "Enter the key in hex"); + byte_input_set_result_callback( + byte_input, + nfc_scene_mf_ultralight_c_keys_add_byte_input_callback, + NULL, + instance, + instance->byte_input_store, + sizeof(MfUltralightC3DesAuthKey)); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewByteInput); +} + +bool nfc_scene_mf_ultralight_c_keys_add_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventByteInputDone) { + // Add key to dict + KeysDict* dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + + MfUltralightC3DesAuthKey key = {}; + memcpy(key.data, instance->byte_input_store, sizeof(MfUltralightC3DesAuthKey)); + if(keys_dict_is_key_present(dict, key.data, sizeof(MfUltralightC3DesAuthKey))) { + scene_manager_next_scene( + instance->scene_manager, NfcSceneMfUltralightCKeysWarnDuplicate); + } else if(keys_dict_add_key(dict, key.data, sizeof(MfUltralightC3DesAuthKey))) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSaveSuccess); + dolphin_deed(DolphinDeedNfcMfcAdd); + } else { + scene_manager_previous_scene(instance->scene_manager); + } + + keys_dict_free(dict); + consumed = true; + } + } + + return consumed; +} + +void nfc_scene_mf_ultralight_c_keys_add_on_exit(void* context) { + NfcApp* instance = context; + + // Clear view + byte_input_set_result_callback(instance->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(instance->byte_input, ""); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_delete.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_delete.c new file mode 100644 index 000000000..db2903939 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_delete.c @@ -0,0 +1,108 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_ultralight_c_keys_delete_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + NfcApp* instance = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(instance->view_dispatcher, result); + } +} + +void nfc_scene_mf_ultralight_c_keys_delete_on_enter(void* context) { + NfcApp* instance = context; + + uint32_t key_index = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightCKeysDelete); + FuriString* key_str = furi_string_alloc(); + + widget_add_string_element( + instance->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Delete this key?"); + widget_add_button_element( + instance->widget, + GuiButtonTypeLeft, + "Cancel", + nfc_scene_mf_ultralight_c_keys_delete_widget_callback, + instance); + widget_add_button_element( + instance->widget, + GuiButtonTypeRight, + "Delete", + nfc_scene_mf_ultralight_c_keys_delete_widget_callback, + instance); + + KeysDict* mf_ultralight_c_user_dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + size_t dict_keys_num = keys_dict_get_total_keys(mf_ultralight_c_user_dict); + furi_assert(key_index < dict_keys_num); + MfUltralightC3DesAuthKey stack_key; + for(size_t i = 0; i < (key_index + 1); i++) { + bool key_loaded = keys_dict_get_next_key( + mf_ultralight_c_user_dict, stack_key.data, sizeof(MfUltralightC3DesAuthKey)); + furi_assert(key_loaded); + } + furi_string_reset(key_str); + for(size_t i = 0; i < sizeof(MfUltralightC3DesAuthKey); i++) { + furi_string_cat_printf(key_str, "%02X", stack_key.data[i]); + } + + widget_add_string_element( + instance->widget, + 64, + 32, + AlignCenter, + AlignCenter, + FontSecondary, + furi_string_get_cstr(key_str)); + + keys_dict_free(mf_ultralight_c_user_dict); + furi_string_free(key_str); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_mf_ultralight_c_keys_delete_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeRight) { + uint32_t key_index = scene_manager_get_scene_state( + instance->scene_manager, NfcSceneMfUltralightCKeysDelete); + KeysDict* mf_ultralight_c_user_dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + size_t dict_keys_num = keys_dict_get_total_keys(mf_ultralight_c_user_dict); + furi_assert(key_index < dict_keys_num); + MfUltralightC3DesAuthKey stack_key; + for(size_t i = 0; i < (key_index + 1); i++) { + bool key_loaded = keys_dict_get_next_key( + mf_ultralight_c_user_dict, stack_key.data, sizeof(MfUltralightC3DesAuthKey)); + furi_assert(key_loaded); + } + bool key_delete_success = keys_dict_delete_key( + mf_ultralight_c_user_dict, stack_key.data, sizeof(MfUltralightC3DesAuthKey)); + keys_dict_free(mf_ultralight_c_user_dict); + if(key_delete_success) { + scene_manager_next_scene(instance->scene_manager, NfcSceneDeleteSuccess); + } else { + scene_manager_previous_scene(instance->scene_manager); + } + } else if(event.event == GuiButtonTypeLeft) { + scene_manager_previous_scene(instance->scene_manager); + } + consumed = true; + } + + return consumed; +} + +void nfc_scene_mf_ultralight_c_keys_delete_on_exit(void* context) { + NfcApp* instance = context; + + widget_reset(instance->widget); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_list.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_list.c new file mode 100644 index 000000000..e2fda3aea --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_list.c @@ -0,0 +1,66 @@ +#include "../nfc_app_i.h" + +#define NFC_SCENE_MF_ULTRALIGHT_C_KEYS_LIST_MAX (100) + +void nfc_scene_mf_ultralight_c_keys_list_submenu_callback(void* context, uint32_t index) { + NfcApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, index); +} + +void nfc_scene_mf_ultralight_c_keys_list_on_enter(void* context) { + NfcApp* instance = context; + + KeysDict* mf_ultralight_c_user_dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + + submenu_set_header(instance->submenu, "Select key to delete:"); + FuriString* temp_str = furi_string_alloc(); + + size_t dict_keys_num = keys_dict_get_total_keys(mf_ultralight_c_user_dict); + size_t keys_num = MIN((size_t)NFC_SCENE_MF_ULTRALIGHT_C_KEYS_LIST_MAX, dict_keys_num); + MfUltralightC3DesAuthKey stack_key; + + if(keys_num > 0) { + for(size_t i = 0; i < keys_num; i++) { + bool key_loaded = keys_dict_get_next_key( + mf_ultralight_c_user_dict, stack_key.data, sizeof(MfUltralightC3DesAuthKey)); + furi_assert(key_loaded); + furi_string_reset(temp_str); + for(size_t i = 0; i < sizeof(MfUltralightC3DesAuthKey); i++) { + furi_string_cat_printf(temp_str, "%02X", stack_key.data[i]); + } + submenu_add_item( + instance->submenu, + furi_string_get_cstr(temp_str), + i, + nfc_scene_mf_ultralight_c_keys_list_submenu_callback, + instance); + } + } + keys_dict_free(mf_ultralight_c_user_dict); + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_mf_ultralight_c_keys_list_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + + bool consumed = false; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneMfUltralightCKeysDelete, event.event); + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCKeysDelete); + } + + return consumed; +} + +void nfc_scene_mf_ultralight_c_keys_list_on_exit(void* context) { + NfcApp* instance = context; + + submenu_reset(instance->submenu); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_warn_duplicate.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_warn_duplicate.c new file mode 100644 index 000000000..c8881e5d4 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_warn_duplicate.c @@ -0,0 +1,49 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_ultralight_c_keys_warn_duplicate_popup_callback(void* context) { + NfcApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventViewExit); +} + +void nfc_scene_mf_ultralight_c_keys_warn_duplicate_on_enter(void* context) { + NfcApp* instance = context; + + // Setup view + Popup* popup = instance->popup; + popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42); + popup_set_header(popup, "Key Already Exists!", 64, 3, AlignCenter, AlignTop); + popup_set_text( + popup, + "Please enter a\n" + "different key.", + 4, + 24, + AlignLeft, + AlignTop); + popup_set_timeout(popup, 1500); + popup_set_context(popup, instance); + popup_set_callback(popup, nfc_scene_mf_ultralight_c_keys_warn_duplicate_popup_callback); + popup_enable_timeout(popup); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); +} + +bool nfc_scene_mf_ultralight_c_keys_warn_duplicate_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventViewExit) { + consumed = scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneMfUltralightCKeysAdd); + } + } + + return consumed; +} + +void nfc_scene_mf_ultralight_c_keys_warn_duplicate_on_exit(void* context) { + NfcApp* instance = context; + + popup_reset(instance->popup); +} diff --git a/applications/main/nfc/scenes/nfc_scene_save_success.c b/applications/main/nfc/scenes/nfc_scene_save_success.c index 5f812ba9c..06999fe9f 100644 --- a/applications/main/nfc/scenes/nfc_scene_save_success.c +++ b/applications/main/nfc/scenes/nfc_scene_save_success.c @@ -28,6 +28,10 @@ bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) { if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicKeys)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneMfClassicKeys); + } else if(scene_manager_has_previous_scene( + nfc->scene_manager, NfcSceneMfUltralightCKeys)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneMfUltralightCKeys); } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSaveConfirm)) { NfcSceneSaveConfirmState scene_state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneSaveConfirm); diff --git a/applications/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index 726076972..a71e466f8 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -13,12 +13,13 @@ struct DictAttack { typedef struct { FuriString* header; bool card_detected; + DictAttackType attack_type; + + // MIFARE Classic specific uint8_t sectors_total; uint8_t sectors_read; uint8_t current_sector; uint8_t keys_found; - size_t dict_keys_total; - size_t dict_keys_current; bool is_key_attack; uint8_t key_attack_current_sector; MfClassicNestedPhase nested_phase; @@ -26,8 +27,150 @@ typedef struct { MfClassicBackdoor backdoor; uint16_t nested_target_key; uint16_t msb_count; + + // Ultralight C specific + uint8_t pages_total; + uint8_t pages_read; + bool key_found; + + // Common + size_t dict_keys_total; + size_t dict_keys_current; } DictAttackViewModel; +static void dict_attack_draw_mf_classic(Canvas* canvas, DictAttackViewModel* m) { + char draw_str[32] = {}; + canvas_set_font(canvas, FontSecondary); + + switch(m->nested_phase) { + case MfClassicNestedPhaseAnalyzePRNG: + furi_string_set(m->header, "PRNG Analysis"); + break; + case MfClassicNestedPhaseDictAttack: + case MfClassicNestedPhaseDictAttackVerify: + case MfClassicNestedPhaseDictAttackResume: + furi_string_set(m->header, "Nested Dictionary"); + break; + case MfClassicNestedPhaseCalibrate: + case MfClassicNestedPhaseRecalibrate: + furi_string_set(m->header, "Calibration"); + break; + case MfClassicNestedPhaseCollectNtEnc: + furi_string_set(m->header, "Nonce Collection"); + break; + default: + break; + } + + if(m->prng_type == MfClassicPrngTypeHard) { + furi_string_cat(m->header, " (Hard)"); + } + + if(m->backdoor != MfClassicBackdoorNone && m->backdoor != MfClassicBackdoorUnknown) { + if(m->nested_phase != MfClassicNestedPhaseNone) { + furi_string_cat(m->header, " (Backdoor)"); + } else { + furi_string_set(m->header, "Backdoor Read"); + } + } + + canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(m->header)); + if(m->nested_phase == MfClassicNestedPhaseCollectNtEnc) { + uint8_t nonce_sector = + m->nested_target_key / (m->prng_type == MfClassicPrngTypeWeak ? 4 : 2); + snprintf(draw_str, sizeof(draw_str), "Collecting from sector: %d", nonce_sector); + canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); + } else if(m->is_key_attack) { + snprintf( + draw_str, + sizeof(draw_str), + "Reuse key check for sector: %d", + m->key_attack_current_sector); + } else { + snprintf(draw_str, sizeof(draw_str), "Unlocking sector: %d", m->current_sector); + } + canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); + float dict_progress = 0; + if(m->nested_phase == MfClassicNestedPhaseAnalyzePRNG || + m->nested_phase == MfClassicNestedPhaseDictAttack || + m->nested_phase == MfClassicNestedPhaseDictAttackVerify || + m->nested_phase == MfClassicNestedPhaseDictAttackResume) { + // Phase: Nested dictionary attack + uint8_t target_sector = + m->nested_target_key / (m->prng_type == MfClassicPrngTypeWeak ? 2 : 16); + dict_progress = (float)(target_sector) / (float)(m->sectors_total); + snprintf(draw_str, sizeof(draw_str), "%d/%d", target_sector, m->sectors_total); + } else if( + m->nested_phase == MfClassicNestedPhaseCalibrate || + m->nested_phase == MfClassicNestedPhaseRecalibrate || + m->nested_phase == MfClassicNestedPhaseCollectNtEnc) { + // Phase: Nonce collection + if(m->prng_type == MfClassicPrngTypeWeak) { + uint8_t target_sector = m->nested_target_key / 4; + dict_progress = (float)(target_sector) / (float)(m->sectors_total); + snprintf(draw_str, sizeof(draw_str), "%d/%d", target_sector, m->sectors_total); + } else { + uint16_t max_msb = UINT8_MAX + 1; + dict_progress = (float)(m->msb_count) / (float)(max_msb); + snprintf(draw_str, sizeof(draw_str), "%d/%d", m->msb_count, max_msb); + } + } else { + dict_progress = m->dict_keys_total == 0 ? + 0 : + (float)(m->dict_keys_current) / (float)(m->dict_keys_total); + if(m->dict_keys_current == 0) { + // Cause when people see 0 they think it's broken + snprintf(draw_str, sizeof(draw_str), "%d/%zu", 1, m->dict_keys_total); + } else { + snprintf( + draw_str, sizeof(draw_str), "%zu/%zu", m->dict_keys_current, m->dict_keys_total); + } + } + if(dict_progress > 1.0f) { + dict_progress = 1.0f; + } + elements_progress_bar_with_text(canvas, 0, 20, 128, dict_progress, draw_str); + canvas_set_font(canvas, FontSecondary); + snprintf( + draw_str, + sizeof(draw_str), + "Keys found: %d/%d", + m->keys_found, + m->sectors_total * NFC_CLASSIC_KEYS_PER_SECTOR); + canvas_draw_str_aligned(canvas, 0, 33, AlignLeft, AlignTop, draw_str); + snprintf(draw_str, sizeof(draw_str), "Sectors Read: %d/%d", m->sectors_read, m->sectors_total); + canvas_draw_str_aligned(canvas, 0, 43, AlignLeft, AlignTop, draw_str); +} + +static void dict_attack_draw_mf_ultralight_c(Canvas* canvas, DictAttackViewModel* m) { + char draw_str[32] = {}; + canvas_set_font(canvas, FontSecondary); + + canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(m->header)); + + snprintf(draw_str, sizeof(draw_str), "Trying keys"); + canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); + + float dict_progress = + m->dict_keys_total == 0 ? 0 : (float)(m->dict_keys_current) / (float)(m->dict_keys_total); + if(m->dict_keys_current == 0) { + snprintf(draw_str, sizeof(draw_str), "%d/%zu", 1, m->dict_keys_total); + } else { + snprintf(draw_str, sizeof(draw_str), "%zu/%zu", m->dict_keys_current, m->dict_keys_total); + } + if(dict_progress > 1.0f) { + dict_progress = 1.0f; + } + elements_progress_bar_with_text(canvas, 0, 20, 128, dict_progress, draw_str); + + canvas_set_font(canvas, FontSecondary); + snprintf(draw_str, sizeof(draw_str), "Key found: %s", m->key_found ? "Yes" : "No"); + canvas_draw_str_aligned(canvas, 0, 33, AlignLeft, AlignTop, draw_str); + + snprintf(draw_str, sizeof(draw_str), "Pages read: %d/%d", m->pages_read, m->pages_total); + canvas_draw_str_aligned(canvas, 0, 43, AlignLeft, AlignTop, draw_str); +} + static void dict_attack_draw_callback(Canvas* canvas, void* model) { DictAttackViewModel* m = model; if(!m->card_detected) { @@ -37,113 +180,11 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { elements_multiline_text_aligned( canvas, 64, 23, AlignCenter, AlignTop, "Make sure the tag is\npositioned correctly."); } else { - char draw_str[32] = {}; - canvas_set_font(canvas, FontSecondary); - - switch(m->nested_phase) { - case MfClassicNestedPhaseAnalyzePRNG: - furi_string_set(m->header, "PRNG Analysis"); - break; - case MfClassicNestedPhaseDictAttack: - case MfClassicNestedPhaseDictAttackVerify: - case MfClassicNestedPhaseDictAttackResume: - furi_string_set(m->header, "Nested Dictionary"); - break; - case MfClassicNestedPhaseCalibrate: - case MfClassicNestedPhaseRecalibrate: - furi_string_set(m->header, "Calibration"); - break; - case MfClassicNestedPhaseCollectNtEnc: - furi_string_set(m->header, "Nonce Collection"); - break; - default: - break; + if(m->attack_type == DictAttackTypeMfClassic) { + dict_attack_draw_mf_classic(canvas, m); + } else if(m->attack_type == DictAttackTypeMfUltralightC) { + dict_attack_draw_mf_ultralight_c(canvas, m); } - - if(m->prng_type == MfClassicPrngTypeHard) { - furi_string_cat(m->header, " (Hard)"); - } - - if(m->backdoor != MfClassicBackdoorNone && m->backdoor != MfClassicBackdoorUnknown) { - if(m->nested_phase != MfClassicNestedPhaseNone) { - furi_string_cat(m->header, " (Backdoor)"); - } else { - furi_string_set(m->header, "Backdoor Read"); - } - } - - canvas_draw_str_aligned( - canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(m->header)); - if(m->nested_phase == MfClassicNestedPhaseCollectNtEnc) { - uint8_t nonce_sector = - m->nested_target_key / (m->prng_type == MfClassicPrngTypeWeak ? 4 : 2); - snprintf(draw_str, sizeof(draw_str), "Collecting from sector: %d", nonce_sector); - canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); - } else if(m->is_key_attack) { - snprintf( - draw_str, - sizeof(draw_str), - "Reuse key check for sector: %d", - m->key_attack_current_sector); - } else { - snprintf(draw_str, sizeof(draw_str), "Unlocking sector: %d", m->current_sector); - } - canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); - float dict_progress = 0; - if(m->nested_phase == MfClassicNestedPhaseAnalyzePRNG || - m->nested_phase == MfClassicNestedPhaseDictAttack || - m->nested_phase == MfClassicNestedPhaseDictAttackVerify || - m->nested_phase == MfClassicNestedPhaseDictAttackResume) { - // Phase: Nested dictionary attack - uint8_t target_sector = - m->nested_target_key / (m->prng_type == MfClassicPrngTypeWeak ? 2 : 16); - dict_progress = (float)(target_sector) / (float)(m->sectors_total); - snprintf(draw_str, sizeof(draw_str), "%d/%d", target_sector, m->sectors_total); - } else if( - m->nested_phase == MfClassicNestedPhaseCalibrate || - m->nested_phase == MfClassicNestedPhaseRecalibrate || - m->nested_phase == MfClassicNestedPhaseCollectNtEnc) { - // Phase: Nonce collection - if(m->prng_type == MfClassicPrngTypeWeak) { - uint8_t target_sector = m->nested_target_key / 4; - dict_progress = (float)(target_sector) / (float)(m->sectors_total); - snprintf(draw_str, sizeof(draw_str), "%d/%d", target_sector, m->sectors_total); - } else { - uint16_t max_msb = UINT8_MAX + 1; - dict_progress = (float)(m->msb_count) / (float)(max_msb); - snprintf(draw_str, sizeof(draw_str), "%d/%d", m->msb_count, max_msb); - } - } else { - dict_progress = m->dict_keys_total == 0 ? - 0 : - (float)(m->dict_keys_current) / (float)(m->dict_keys_total); - if(m->dict_keys_current == 0) { - // Cause when people see 0 they think it's broken - snprintf(draw_str, sizeof(draw_str), "%d/%zu", 1, m->dict_keys_total); - } else { - snprintf( - draw_str, - sizeof(draw_str), - "%zu/%zu", - m->dict_keys_current, - m->dict_keys_total); - } - } - if(dict_progress > 1.0f) { - dict_progress = 1.0f; - } - elements_progress_bar_with_text(canvas, 0, 20, 128, dict_progress, draw_str); - canvas_set_font(canvas, FontSecondary); - snprintf( - draw_str, - sizeof(draw_str), - "Keys found: %d/%d", - m->keys_found, - m->sectors_total * NFC_CLASSIC_KEYS_PER_SECTOR); - canvas_draw_str_aligned(canvas, 0, 33, AlignLeft, AlignTop, draw_str); - snprintf( - draw_str, sizeof(draw_str), "Sectors Read: %d/%d", m->sectors_read, m->sectors_total); - canvas_draw_str_aligned(canvas, 0, 43, AlignLeft, AlignTop, draw_str); } elements_button_center(canvas, "Skip"); } @@ -195,18 +236,28 @@ void dict_attack_reset(DictAttack* instance) { instance->view, DictAttackViewModel * model, { + model->attack_type = DictAttackTypeMfClassic; + + // MIFARE Classic fields model->sectors_total = 0; model->sectors_read = 0; model->current_sector = 0; model->keys_found = 0; - model->dict_keys_total = 0; - model->dict_keys_current = 0; model->is_key_attack = false; model->nested_phase = MfClassicNestedPhaseNone; model->prng_type = MfClassicPrngTypeUnknown; model->backdoor = MfClassicBackdoorUnknown; model->nested_target_key = 0; model->msb_count = 0; + + // Ultralight C fields + model->pages_total = 0; + model->pages_read = 0; + model->key_found = false; + + // Common fields + model->dict_keys_total = 0; + model->dict_keys_current = 0; furi_string_reset(model->header); }, false); @@ -355,3 +406,31 @@ void dict_attack_set_msb_count(DictAttack* instance, uint16_t msb_count) { with_view_model( instance->view, DictAttackViewModel * model, { model->msb_count = msb_count; }, true); } + +void dict_attack_set_type(DictAttack* instance, DictAttackType type) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->attack_type = type; }, true); +} + +void dict_attack_set_pages_total(DictAttack* instance, uint8_t pages_total) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->pages_total = pages_total; }, true); +} + +void dict_attack_set_pages_read(DictAttack* instance, uint8_t pages_read) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->pages_read = pages_read; }, true); +} + +void dict_attack_set_key_found(DictAttack* instance, bool key_found) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->key_found = key_found; }, true); +} diff --git a/applications/main/nfc/views/dict_attack.h b/applications/main/nfc/views/dict_attack.h index b6c6fdbdc..70709f86e 100644 --- a/applications/main/nfc/views/dict_attack.h +++ b/applications/main/nfc/views/dict_attack.h @@ -8,6 +8,11 @@ extern "C" { #endif +typedef enum { + DictAttackTypeMfClassic, + DictAttackTypeMfUltralightC, +} DictAttackType; + typedef struct DictAttack DictAttack; typedef enum { @@ -56,6 +61,14 @@ void dict_attack_set_nested_target_key(DictAttack* instance, uint16_t target_key void dict_attack_set_msb_count(DictAttack* instance, uint16_t msb_count); +void dict_attack_set_type(DictAttack* instance, DictAttackType type); + +void dict_attack_set_pages_total(DictAttack* instance, uint8_t pages_total); + +void dict_attack_set_pages_read(DictAttack* instance, uint8_t pages_read); + +void dict_attack_set_key_found(DictAttack* instance, bool key_found); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight.h index 191deeda6..caf25ceee 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight.h @@ -38,7 +38,6 @@ extern "C" { #define MF_ULTRALIGHT_TEARING_FLAG_NUM (3) #define MF_ULTRALIGHT_AUTH_PASSWORD_SIZE (4) #define MF_ULTRALIGHT_AUTH_PACK_SIZE (2) -#define MF_ULTRALIGHT_DEFAULT_PASSWORD (0xffffffffUL) #define MF_ULTRALIGHT_C_AUTH_RESPONSE_SIZE (9) #define MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE (16) @@ -48,11 +47,6 @@ extern "C" { #define MF_ULTRALIGHT_C_AUTH_RND_A_BLOCK_OFFSET (0) #define MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET (8) #define MF_ULTRALIGHT_C_ENCRYPTED_PACK_SIZE (MF_ULTRALIGHT_C_AUTH_DATA_SIZE + 1) -#define MF_ULTRALIGHT_C_DEFAULT_KEY \ - (uint8_t[]) { \ - 0x49, 0x45, 0x4D, 0x4B, 0x41, 0x45, 0x52, 0x42, 0x21, 0x4E, 0x41, 0x43, 0x55, 0x4F, 0x59, \ - 0x46 \ - } typedef enum { MfUltralightErrorNone, diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index 6c6e230f0..cf942172b 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -251,7 +251,7 @@ static NfcCommand mf_ultralight_poller_handler_read_version(MfUltralightPoller* instance->data->type = mf_ultralight_get_type_by_version(&instance->data->version); instance->state = MfUltralightPollerStateGetFeatureSet; } else { - FURI_LOG_D(TAG, "Didn't response. Check Ultralight C"); + FURI_LOG_D(TAG, "Didn't respond. Check Ultralight C"); iso14443_3a_poller_halt(instance->iso14443_3a_poller); instance->state = MfUltralightPollerStateDetectMfulC; } @@ -266,7 +266,7 @@ static NfcCommand mf_ultralight_poller_handler_check_ultralight_c(MfUltralightPo instance->data->type = MfUltralightTypeMfulC; instance->state = MfUltralightPollerStateGetFeatureSet; } else { - FURI_LOG_D(TAG, "Didn't response. Check NTAG 203"); + FURI_LOG_D(TAG, "Didn't respond. Check NTAG 203"); instance->state = MfUltralightPollerStateDetectNtag203; } iso14443_3a_poller_halt(instance->iso14443_3a_poller); @@ -445,36 +445,105 @@ static NfcCommand mf_ultralight_poller_handler_auth(MfUltralightPoller* instance static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPoller* instance) { NfcCommand command = NfcCommandContinue; FURI_LOG_D(TAG, "MfulC auth"); + if(mf_ultralight_support_feature( + instance->feature_set, MfUltralightFeatureSupportAuthenticate)) { + instance->mfu_event.type = MfUltralightPollerEventTypeAuthRequest; - do { - if(mf_ultralight_support_feature( - instance->feature_set, MfUltralightFeatureSupportAuthenticate)) { - instance->mfu_event.type = MfUltralightPollerEventTypeAuthRequest; - - command = instance->callback(instance->general_event, instance->context); - if(!instance->mfu_event.data->auth_context.skip_auth) { - FURI_LOG_D(TAG, "Trying to authenticate with 3des key"); - instance->auth_context.tdes_key = instance->mfu_event.data->auth_context.tdes_key; - instance->error = - mf_ultralight_poller_auth_tdes(instance, &instance->auth_context); - - if(instance->error == MfUltralightErrorNone && - instance->auth_context.auth_success) { - FURI_LOG_D(TAG, "Auth success"); + command = instance->callback(instance->general_event, instance->context); + if(!instance->mfu_event.data->auth_context.skip_auth) { + FURI_LOG_D(TAG, "Trying to authenticate with 3des key"); + // Only use the key if it was actually provided + if(instance->mfu_event.data->key_request_data.key_provided) { + instance->auth_context.tdes_key = instance->mfu_event.data->key_request_data.key; + } else if(instance->mode == MfUltralightPollerModeDictAttack) { + // TODO: Can logic be rearranged to request this key before reaching mf_ultralight_poller_handler_auth_ultralight_c in poller? + FURI_LOG_D(TAG, "No initial key provided, requesting key from dictionary"); + // Trigger dictionary key request + instance->mfu_event.type = MfUltralightPollerEventTypeRequestKey; + command = instance->callback(instance->general_event, instance->context); + if(!instance->mfu_event.data->key_request_data.key_provided) { + instance->state = MfUltralightPollerStateReadPages; + return command; } else { - FURI_LOG_D(TAG, "Auth failed"); - iso14443_3a_poller_halt(instance->iso14443_3a_poller); + instance->auth_context.tdes_key = + instance->mfu_event.data->key_request_data.key; } } else { - // We assume here that it is card read without explicitly provided key - // So we try to auth with default one - instance->state = MfUltralightPollerStateTryDefaultMfulCKey; - break; + FURI_LOG_D(TAG, "No key provided, skipping auth"); + instance->state = MfUltralightPollerStateReadPages; + return command; + } + instance->auth_context.auth_success = false; + // For debugging + FURI_LOG_D( + "TAG", + "Key data: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", + instance->auth_context.tdes_key.data[0], + instance->auth_context.tdes_key.data[1], + instance->auth_context.tdes_key.data[2], + instance->auth_context.tdes_key.data[3], + instance->auth_context.tdes_key.data[4], + instance->auth_context.tdes_key.data[5], + instance->auth_context.tdes_key.data[6], + instance->auth_context.tdes_key.data[7], + instance->auth_context.tdes_key.data[8], + instance->auth_context.tdes_key.data[9], + instance->auth_context.tdes_key.data[10], + instance->auth_context.tdes_key.data[11], + instance->auth_context.tdes_key.data[12], + instance->auth_context.tdes_key.data[13], + instance->auth_context.tdes_key.data[14], + instance->auth_context.tdes_key.data[15]); + do { + uint8_t output[MF_ULTRALIGHT_C_AUTH_DATA_SIZE]; + uint8_t RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0}; + furi_hal_random_fill_buf(RndA, sizeof(RndA)); + instance->error = mf_ultralight_poller_authenticate_start(instance, RndA, output); + if(instance->error != MfUltralightErrorNone) break; + + uint8_t decoded_shifted_RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0}; + const uint8_t* RndB = output + MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET; + instance->error = mf_ultralight_poller_authenticate_end( + instance, RndB, output, decoded_shifted_RndA); + if(instance->error != MfUltralightErrorNone) break; + + mf_ultralight_3des_shift_data(RndA); + instance->auth_context.auth_success = + (memcmp(RndA, decoded_shifted_RndA, sizeof(decoded_shifted_RndA)) == 0); + if(instance->auth_context.auth_success) { + FURI_LOG_E(TAG, "Auth success"); + if(instance->mode == MfUltralightPollerModeDictAttack) { + memcpy( + &instance->data->page[44], + instance->auth_context.tdes_key.data, + MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE); + // Continue to read pages after successful authentication + instance->state = MfUltralightPollerStateReadPages; + } + } + } while(false); + if(instance->error != MfUltralightErrorNone || !instance->auth_context.auth_success) { + FURI_LOG_E(TAG, "Auth failed"); + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + if(instance->mode == MfUltralightPollerModeDictAttack) { + // Not needed? We already do a callback earlier? + instance->mfu_event.type = MfUltralightPollerEventTypeRequestKey; + command = instance->callback(instance->general_event, instance->context); + if(!instance->mfu_event.data->key_request_data.key_provided) { + instance->state = MfUltralightPollerStateReadPages; + } else { + instance->auth_context.tdes_key = + instance->mfu_event.data->key_request_data.key; + instance->state = MfUltralightPollerStateAuthMfulC; + } + } } } + } + // Regression review + if(instance->mode != MfUltralightPollerModeDictAttack) { instance->state = MfUltralightPollerStateReadPages; - } while(false); - + } return command; } @@ -497,12 +566,16 @@ static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* in instance->error = mf_ultralight_poller_read_page(instance, start_page, &data); } + // Regression review + const uint8_t read_cnt = instance->data->type == MfUltralightTypeMfulC ? 1 : 4; if(instance->error == MfUltralightErrorNone) { - if(start_page < instance->pages_total) { - FURI_LOG_D(TAG, "Read page %d success", start_page); - instance->data->page[start_page] = data.page[0]; - instance->pages_read++; - instance->data->pages_read = instance->pages_read; + for(size_t i = 0; i < read_cnt; i++) { + if(start_page + i < instance->pages_total) { + FURI_LOG_D(TAG, "Read page %d success", start_page + i); + instance->data->page[start_page + i] = data.page[i]; + instance->pages_read++; + instance->data->pages_read = instance->pages_read; + } } if(instance->pages_read == instance->pages_total) { @@ -570,40 +643,6 @@ static NfcCommand mf_ultralight_poller_handler_try_default_pass(MfUltralightPoll return NfcCommandContinue; } -static NfcCommand - mf_ultralight_poller_handler_try_default_ultralight_c_key(MfUltralightPoller* instance) { - do { - if(!mf_ultralight_support_feature( - instance->feature_set, MfUltralightFeatureSupportAuthenticate)) { - break; - } - - if(instance->auth_context.auth_success) { - break; - } - - FURI_LOG_D(TAG, "Trying authentication with default 3DES key"); - - memcpy( - &instance->auth_context.tdes_key.data, - MF_ULTRALIGHT_C_DEFAULT_KEY, - MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE); - - instance->error = mf_ultralight_poller_auth_tdes(instance, &instance->auth_context); - - if(instance->error == MfUltralightErrorNone && instance->auth_context.auth_success) { - FURI_LOG_D(TAG, "Default 3DES key detected"); - } else { - FURI_LOG_D(TAG, "Authentication attempt with default 3DES key failed"); - iso14443_3a_poller_halt(instance->iso14443_3a_poller); - } - - } while(false); - - instance->state = MfUltralightPollerStateReadPages; - return NfcCommandContinue; -} - static NfcCommand mf_ultralight_poller_handler_check_mfuc_auth_status(MfUltralightPoller* instance) { instance->state = MfUltralightPollerStateReadSuccess; @@ -768,8 +807,6 @@ static const MfUltralightPollerReadHandler mf_ultralight_poller_handler_read_tearing_flags, [MfUltralightPollerStateAuth] = mf_ultralight_poller_handler_auth, [MfUltralightPollerStateTryDefaultPass] = mf_ultralight_poller_handler_try_default_pass, - [MfUltralightPollerStateTryDefaultMfulCKey] = - mf_ultralight_poller_handler_try_default_ultralight_c_key, [MfUltralightPollerStateCheckMfulCAuthStatus] = mf_ultralight_poller_handler_check_mfuc_auth_status, [MfUltralightPollerStateAuthMfulC] = mf_ultralight_poller_handler_auth_ultralight_c, @@ -781,7 +818,6 @@ static const MfUltralightPollerReadHandler [MfUltralightPollerStateWritePages] = mf_ultralight_poller_handler_write_pages, [MfUltralightPollerStateWriteFail] = mf_ultralight_poller_handler_write_fail, [MfUltralightPollerStateWriteSuccess] = mf_ultralight_poller_handler_write_success, - }; static NfcCommand mf_ultralight_poller_run(NfcGenericEvent event, void* context) { diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h index 661c6db74..2552abeb5 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h @@ -27,6 +27,7 @@ typedef enum { MfUltralightPollerEventTypeCardLocked, /**< Presented card is locked by password, AUTH0 or lock bytes. */ MfUltralightPollerEventTypeWriteSuccess, /**< Poller wrote card successfully. */ MfUltralightPollerEventTypeWriteFail, /**< Poller failed to write card. */ + MfUltralightPollerEventTypeRequestKey, /**< Poller requests key for dict attack. */ } MfUltralightPollerEventType; /** @@ -35,6 +36,7 @@ typedef enum { typedef enum { MfUltralightPollerModeRead, /**< Poller will only read card. It's a default mode. */ MfUltralightPollerModeWrite, /**< Poller will write already saved card to another presented card. */ + MfUltralightPollerModeDictAttack, /**< Poller will perform dictionary attack against card. */ } MfUltralightPollerMode; /** @@ -42,20 +44,29 @@ typedef enum { */ typedef struct { MfUltralightAuthPassword password; /**< Password to be used for authentication. */ - MfUltralightC3DesAuthKey tdes_key; - MfUltralightAuthPack pack; /**< Pack received on successfull authentication. */ + MfUltralightC3DesAuthKey tdes_key; /**< 3DES key to be used for authentication. */ + MfUltralightAuthPack pack; /**< Pack received on successful authentication. */ bool auth_success; /**< Set to true if authentication succeeded, false otherwise. */ bool skip_auth; /**< Set to true if authentication should be skipped, false otherwise. */ } MfUltralightPollerAuthContext; +/** + * @brief MfUltralight poller key request data. + */ +typedef struct { + MfUltralightC3DesAuthKey key; /**< Key to try. */ + bool key_provided; /**< Set to true if key was provided, false to stop attack. */ +} MfUltralightPollerKeyRequestData; + /** * @brief MfUltralight poller event data. */ typedef union { MfUltralightPollerAuthContext auth_context; /**< Authentication context. */ MfUltralightError error; /**< Error code indicating reading fail reason. */ - const MfUltralightData* write_data; - MfUltralightPollerMode poller_mode; + const MfUltralightData* write_data; /**< Data to be written to card. */ + MfUltralightPollerMode poller_mode; /**< Mode to operate in. */ + MfUltralightPollerKeyRequestData key_request_data; /**< Key request data. */ } MfUltralightPollerEventData; /** @@ -64,7 +75,7 @@ typedef union { * Upon emission of an event, an instance of this struct will be passed to the callback. */ typedef struct { - MfUltralightPollerEventType type; /**< Type of emmitted event. */ + MfUltralightPollerEventType type; /**< Type of emitted event. */ MfUltralightPollerEventData* data; /**< Pointer to event specific data. */ } MfUltralightPollerEvent; @@ -81,19 +92,6 @@ MfUltralightError mf_ultralight_poller_auth_pwd( MfUltralightPoller* instance, MfUltralightPollerAuthContext* data); -/** - * @brief Perform 3DES authentication with key. - * - * Must ONLY be used inside the callback function. - * - * @param[in, out] instance pointer to the instance to be used in the transaction. - * @param[in, out] data pointer to the authentication context. - * @return MfUltralightErrorNone on success, an error code on failure. - */ -MfUltralightError mf_ultralight_poller_auth_tdes( - MfUltralightPoller* instance, - MfUltralightPollerAuthContext* data); - /** * @brief Start authentication procedure. * diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c index fdafaf37d..82e647da8 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c @@ -1,7 +1,6 @@ #include "mf_ultralight_poller_i.h" #include -#include #define TAG "MfUltralightPoller" @@ -63,38 +62,6 @@ MfUltralightError mf_ultralight_poller_auth_pwd( return ret; } -MfUltralightError mf_ultralight_poller_auth_tdes( - MfUltralightPoller* instance, - MfUltralightPollerAuthContext* data) { - furi_check(instance); - furi_check(data); - - MfUltralightError ret = MfUltralightErrorNone; - - uint8_t output[MF_ULTRALIGHT_C_AUTH_DATA_SIZE]; - uint8_t RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0}; - furi_hal_random_fill_buf(RndA, sizeof(RndA)); - - ret = mf_ultralight_poller_authenticate_start(instance, RndA, output); - - if(ret != MfUltralightErrorNone) { - return ret; - } - - uint8_t decoded_shifted_RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0}; - const uint8_t* RndB = output + MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET; - ret = mf_ultralight_poller_authenticate_end(instance, RndB, output, decoded_shifted_RndA); - - if(ret != MfUltralightErrorNone) { - return ret; - } - - mf_ultralight_3des_shift_data(RndA); - data->auth_success = (memcmp(RndA, decoded_shifted_RndA, sizeof(decoded_shifted_RndA)) == 0); - - return ret; -} - static MfUltralightError mf_ultralight_poller_send_authenticate_cmd( MfUltralightPoller* instance, const uint8_t* cmd, diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h index 7db9a77d9..b35c49aea 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h @@ -11,6 +11,8 @@ extern "C" { #define MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC (60000) #define MF_ULTRALIGHT_MAX_BUFF_SIZE (64) +#define MF_ULTRALIGHT_DEFAULT_PASSWORD (0xffffffffUL) + #define MF_ULTRALIGHT_IS_NTAG_I2C(type) \ (((type) == MfUltralightTypeNTAGI2C1K) || ((type) == MfUltralightTypeNTAGI2C2K) || \ ((type) == MfUltralightTypeNTAGI2CPlus1K) || ((type) == MfUltralightTypeNTAGI2CPlus2K)) @@ -59,7 +61,6 @@ typedef enum { MfUltralightPollerStateAuthMfulC, MfUltralightPollerStateReadPages, MfUltralightPollerStateTryDefaultPass, - MfUltralightPollerStateTryDefaultMfulCKey, MfUltralightPollerStateCheckMfulCAuthStatus, MfUltralightPollerStateReadFailed, MfUltralightPollerStateReadSuccess, diff --git a/lib/toolbox/keys_dict.c b/lib/toolbox/keys_dict.c index 602653e8f..c26e9c1e7 100644 --- a/lib/toolbox/keys_dict.c +++ b/lib/toolbox/keys_dict.c @@ -134,22 +134,21 @@ static void keys_dict_int_to_str(KeysDict* instance, const uint8_t* key_int, Fur furi_string_cat_printf(key_str, "%02X", key_int[i]); } -static void keys_dict_str_to_int(KeysDict* instance, FuriString* key_str, uint64_t* key_int) { +static void keys_dict_str_to_int(KeysDict* instance, FuriString* key_str, uint8_t* key_out) { furi_assert(instance); furi_assert(key_str); - furi_assert(key_int); + furi_assert(key_out); uint8_t key_byte_tmp; char h, l; - *key_int = 0ULL; - + // Process two hex characters at a time to create each byte for(size_t i = 0; i < instance->key_size_symbols - 1; i += 2) { h = furi_string_get_char(key_str, i); l = furi_string_get_char(key_str, i + 1); args_char_to_hex(h, l, &key_byte_tmp); - *key_int |= (uint64_t)key_byte_tmp << (8 * (instance->key_size - 1 - i / 2)); + key_out[i / 2] = key_byte_tmp; } } @@ -193,15 +192,7 @@ bool keys_dict_get_next_key(KeysDict* instance, uint8_t* key, size_t key_size) { bool key_read = keys_dict_get_next_key_str(instance, temp_key); if(key_read) { - size_t tmp_len = key_size; - uint64_t key_int = 0; - - keys_dict_str_to_int(instance, temp_key, &key_int); - - while(tmp_len--) { - key[tmp_len] = (uint8_t)key_int; - key_int >>= 8; - } + keys_dict_str_to_int(instance, temp_key, key); } furi_string_free(temp_key); diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 7696a05bc..2f1b0646e 100755 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -2765,7 +2765,6 @@ Function,+,mf_ultralight_is_equal,_Bool,"const MfUltralightData*, const MfUltral Function,+,mf_ultralight_is_page_pwd_or_pack,_Bool,"MfUltralightType, uint16_t" Function,+,mf_ultralight_load,_Bool,"MfUltralightData*, FlipperFormat*, uint32_t" Function,+,mf_ultralight_poller_auth_pwd,MfUltralightError,"MfUltralightPoller*, MfUltralightPollerAuthContext*" -Function,+,mf_ultralight_poller_auth_tdes,MfUltralightError,"MfUltralightPoller*, MfUltralightPollerAuthContext*" Function,+,mf_ultralight_poller_authenticate_end,MfUltralightError,"MfUltralightPoller*, const uint8_t*, const uint8_t*, uint8_t*" Function,+,mf_ultralight_poller_authenticate_start,MfUltralightError,"MfUltralightPoller*, const uint8_t*, uint8_t*" Function,+,mf_ultralight_poller_read_counter,MfUltralightError,"MfUltralightPoller*, uint8_t, MfUltralightCounter*" From d45048fc5d3b4c4902f172bd374011b6f465bf5d Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 21 Sep 2025 16:46:14 +0300 Subject: [PATCH 053/192] upd changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31ea11bf8..d7258caf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * SubGHz: RAW protocol fixes (by @WillyJL) * SubGHz: Add ZKTeco 430.5 MHz add manually support * SubGHz: Add variant of 'Add Manually' menu with manual editing for each value (PR #909 #911 #914 | by @MrLego8-9) +* OFW PR 4271: NFC: Ultralight C NFC App Key Management, Dictionary Attack (by @noproto) * OFW PR 4265: NFC: Fix read crash with unexpectedly large MFC AUTH(0) response (by @WillyJL) * OFW PR 4251: CLI: Fix long delay with quick connect/disconnect (by @WillyJL) * LFRFID: Add additional procotols supported by EM4305 chipset (by @jamisonderek) From 470f6a679dc84e27a7fa5e8c2690b93359fe899e Mon Sep 17 00:00:00 2001 From: WillyJL Date: Mon, 22 Sep 2025 20:08:29 +0200 Subject: [PATCH 054/192] CLI: Fix crash with unexpected plugin files (#4243) --- lib/toolbox/cli/cli_registry.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/toolbox/cli/cli_registry.c b/lib/toolbox/cli/cli_registry.c index 91f7c4046..45661ee90 100644 --- a/lib/toolbox/cli/cli_registry.c +++ b/lib/toolbox/cli/cli_registry.c @@ -136,9 +136,9 @@ void cli_registry_reload_external_commands( FURI_LOG_T(TAG, "Plugin: %s", plugin_filename); furi_string_set_str(plugin_name, plugin_filename); - furi_check(furi_string_end_with_str(plugin_name, ".fal")); + if(!furi_string_end_with_str(plugin_name, ".fal")) continue; furi_string_replace_all_str(plugin_name, ".fal", ""); - furi_check(furi_string_start_with_str(plugin_name, config->fal_prefix)); + if(!furi_string_start_with_str(plugin_name, config->fal_prefix)) continue; furi_string_replace_at(plugin_name, 0, strlen(config->fal_prefix), ""); CliRegistryCommand command = { From 5932dbe70a440bdeef6b1bd40004602dabfcac7b Mon Sep 17 00:00:00 2001 From: Amy Grace Date: Mon, 22 Sep 2025 12:27:59 -0600 Subject: [PATCH 055/192] Add an old JVC model to universal remotes (#4274) Co-authored-by: hedger --- .../resources/infrared/assets/audio.ir | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/applications/main/infrared/resources/infrared/assets/audio.ir b/applications/main/infrared/resources/infrared/assets/audio.ir index efd0382e3..4180ad8b1 100644 --- a/applications/main/infrared/resources/infrared/assets/audio.ir +++ b/applications/main/infrared/resources/infrared/assets/audio.ir @@ -4689,3 +4689,35 @@ protocol: NECext address: 87 7C 00 00 command: 1D E2 00 00 # +# Model: JVC RM-SRVNB1A for JVC RV-NB1 boombox (2004) +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.333333 +data: 8400 4200 526 1578 526 1578 526 1578 526 1578 526 1578 526 526 526 526 526 1578 526 526 526 526 526 526 526 526 526 526 526 526 526 526 526 526 526 25248 526 1578 526 1578 526 1578 526 1578 526 1578 526 526 526 526 526 1578 526 526 526 526 526 526 526 526 526 526 526 526 526 526 526 526 526 33664 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.333333 +data: 8400 4200 526 1578 526 1578 526 1578 526 1578 526 1578 526 526 526 526 526 1578 526 1578 526 526 526 526 526 526 526 526 526 526 526 526 526 526 526 25248 526 1578 526 1578 526 1578 526 1578 526 1578 526 526 526 526 526 1578 526 1578 526 526 526 526 526 526 526 526 526 526 526 526 526 526 526 33664 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.333333 +data: 8400 4200 526 1578 526 1578 526 1578 526 1578 526 1578 526 526 526 526 526 1578 526 526 526 1578 526 526 526 526 526 526 526 526 526 526 526 526 526 25248 526 1578 526 1578 526 1578 526 1578 526 1578 526 526 526 526 526 1578 526 526 526 1578 526 526 526 526 526 526 526 526 526 526 526 526 526 33664 +# +name: Next +type: raw +frequency: 38000 +duty_cycle: 0.333333 +data: 8400 4200 526 1578 526 1578 526 1578 526 1578 526 1578 526 526 526 526 526 1578 526 1578 526 526 526 1578 526 1578 526 1578 526 1578 526 526 526 526 526 25248 526 1578 526 1578 526 1578 526 1578 526 1578 526 526 526 526 526 1578 526 1578 526 526 526 1578 526 1578 526 1578 526 1578 526 526 526 526 526 33664 +# +name: Prev +type: raw +frequency: 38000 +duty_cycle: 0.333333 +data: 8400 4200 526 1578 526 1578 526 1578 526 1578 526 1578 526 526 526 526 526 1578 526 526 526 1578 526 1578 526 1578 526 1578 526 1578 526 526 526 526 526 25248 526 1578 526 1578 526 1578 526 1578 526 1578 526 526 526 526 526 1578 526 526 526 1578 526 1578 526 1578 526 1578 526 1578 526 526 526 526 526 33664 +# From d19ad60c1a6521d26ded1edbb9f2f43b8b4b9b98 Mon Sep 17 00:00:00 2001 From: Zinong Li <131403964+zinongli@users.noreply.github.com> Date: Tue, 23 Sep 2025 17:02:45 +0400 Subject: [PATCH 056/192] GUI Bug Fix: Number Input Save Icon (#4273) * Update KeySaveBlockedSelected_24x11.png * assets: formatted images --------- Co-authored-by: hedger Co-authored-by: hedger --- .../Keyboard/KeySaveBlockedSelected_24x11.png | Bin 110 -> 96 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/icons/Keyboard/KeySaveBlockedSelected_24x11.png b/assets/icons/Keyboard/KeySaveBlockedSelected_24x11.png index fe570a10c3790ca7702dad17af23aea531a7b5bd..477cdcced66dfa1cfea12202b99fd9a1385f003e 100644 GIT binary patch delta 65 zcmc~xn4n^#?&;zfQZXkvBOxKBfzhswY1#R9Rtbq52?FVdQ&MBb@0QfZ)`~Uy| delta 79 zcmYeuo1o%l>gnPbQZXla#sB~PtrI6s>}+go?Cf9I@bYsaePit1jW! h5ramM Date: Tue, 23 Sep 2025 15:43:45 +0200 Subject: [PATCH 057/192] NFC: Fix read crash with unexpectedly large MFC AUTH(0) response (#4265) This was noticeable with Chameleon Ultra NTAG emulation Co-authored-by: hedger --- lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.c b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.c index 2cf66edba..5795c1bfa 100644 --- a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.c +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.c @@ -42,7 +42,13 @@ static Iso14443_3aError iso14443_3a_poller_standard_frame_exchange( break; } + if(bit_buffer_get_capacity_bytes(rx_buffer) < + bit_buffer_get_size_bytes(instance->rx_buffer)) { + ret = Iso14443_3aErrorBufferOverflow; + break; + } bit_buffer_copy(rx_buffer, instance->rx_buffer); + if(!iso14443_crc_check(Iso14443CrcTypeA, instance->rx_buffer)) { ret = Iso14443_3aErrorWrongCrc; break; From 8e0f7cfb725b874baede31e8d7e28cffe04cba13 Mon Sep 17 00:00:00 2001 From: "Aaron Tulino (Aaronjamt)" Date: Tue, 23 Sep 2025 10:15:55 -0700 Subject: [PATCH 058/192] Add usage example app --- .../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 | 81 +++++++++++++++++++ .../services/gui/modules/date_time_input.c | 9 --- .../services/gui/modules/date_time_input.h | 7 -- targets/f7/api_symbols.csv | 3 +- 12 files changed, 328 insertions(+), 18 deletions(-) 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 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..666db14a6 --- /dev/null +++ b/applications/examples/example_date_time_input/scenes/example_date_time_input_scene_show_date_time.c @@ -0,0 +1,81 @@ +#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); + + char buffer[26] = {}; + snprintf( + buffer, + sizeof(buffer), + "%04d-%02d-%02d\n%02d:%02d:%02d", + app->date_time.year, + app->date_time.month, + app->date_time.day, + app->date_time.hour, + app->date_time.minute, + app->date_time.second); + 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/modules/date_time_input.c b/applications/services/gui/modules/date_time_input.c index abcb16887..eb703cda2 100644 --- a/applications/services/gui/modules/date_time_input.c +++ b/applications/services/gui/modules/date_time_input.c @@ -21,7 +21,6 @@ struct DateTimeInput { }; typedef struct { - const char* header; DateTime* datetime; uint8_t row; @@ -335,7 +334,6 @@ DateTimeInput* date_time_input_alloc(void) { date_time_input->view, DateTimeInputModel * model, { - model->header = ""; model->changed_callback = NULL; model->callback_context = NULL; date_time_input_reset_model_input_data(model); @@ -377,13 +375,6 @@ void date_time_input_set_result_callback( true); } -void date_time_input_set_header_text(DateTimeInput* date_time_input, const char* text) { - furi_check(date_time_input); - - with_view_model( - date_time_input->view, DateTimeInputModel * model, { model->header = text; }, true); -} - void date_time_input_set_editable_fields( DateTimeInput* date_time_input, bool year, diff --git a/applications/services/gui/modules/date_time_input.h b/applications/services/gui/modules/date_time_input.h index 2969994fd..1c8cdd779 100644 --- a/applications/services/gui/modules/date_time_input.h +++ b/applications/services/gui/modules/date_time_input.h @@ -58,13 +58,6 @@ void date_time_input_set_result_callback( void* callback_context, DateTime* datetime); -/** Set date/time input header text - * - * @param date_time_input date/time input instance - * @param text text to be shown - */ -void date_time_input_set_header_text(DateTimeInput* date_time_input, const char* text); - /** Set date/time fields which can be edited * * @param date_time_input date/time input instance diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index fcf8388f1..77f6f7e6f 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,86.0,, +Version,+,87.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -930,7 +930,6 @@ 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_header_text,void,"DateTimeInput*, const char*" 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" From f4138e59998cf6911b93e7766c2cad57e40edd76 Mon Sep 17 00:00:00 2001 From: Zinong Li <131403964+zinongli@users.noreply.github.com> Date: Wed, 24 Sep 2025 00:47:22 +0400 Subject: [PATCH 059/192] NFC: MFC 1k Banapass Parser (#4260) * seems to be working * faster reading * clone detection * linter: fixes --------- Co-authored-by: hedger Co-authored-by: hedger --- applications/main/nfc/application.fam | 9 + .../nfc/plugins/supported_cards/banapass.c | 284 ++++++++++++++++++ 2 files changed, 293 insertions(+) create mode 100644 applications/main/nfc/plugins/supported_cards/banapass.c diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index f645033b2..6342d18bf 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -265,3 +265,12 @@ App( requires=["cli"], sources=["nfc_cli.c"], ) + +App( + appid="banapass_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="banapass_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/banapass.c"], +) diff --git a/applications/main/nfc/plugins/supported_cards/banapass.c b/applications/main/nfc/plugins/supported_cards/banapass.c new file mode 100644 index 000000000..ac065c3ac --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/banapass.c @@ -0,0 +1,284 @@ +#include "nfc_supported_card_plugin.h" + +#include + +#include +#include +#include + +#define TAG "Banapass" + +static const uint64_t banapass_key_b_value_block = 0x019761AA8082; +static const uint64_t banapass_key_b_access_code = 0x574343467632; +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +static MfClassicKeyPair banapass_keys_if_value_block[] = { + {.a = 0x6090D00632F5, .b = 0x019761AA8082}, + {.a = 0xA99164400748, .b = 0x62742819AD7C}, + {.a = 0xCC5075E42BA1, .b = 0xB9DF35A0814C}, + {.a = 0x8AF9C718F23D, .b = 0x58CD5C3673CB}, + {.a = 0xFC80E88EB88C, .b = 0x7A3CDAD7C023}, + {.a = 0x30424C029001, .b = 0x024E4E44001F}, + {.a = 0xECBBFA57C6AD, .b = 0x4757698143BD}, + {.a = 0x1D30972E6485, .b = 0xF8526D1A8D6D}, + {.a = 0x1300EC8C7E80, .b = 0xF80A65A87FFA}, + {.a = 0xDEB06ED4AF8E, .b = 0x4AD96BF28190}, + {.a = 0x000390014D41, .b = 0x0800F9917CB0}, + {.a = 0x730050555253, .b = 0x4146D4A956C4}, + {.a = 0x131157FBB126, .b = 0xE69DD9015A43}, + {.a = 0x337237F254D5, .b = 0x9A8389F32FBF}, + {.a = 0x7B8FB4A7100B, .b = 0xC8382A233993}, + {.a = 0x7B304F2A12A6, .b = 0xFC9418BF788B}, +}; + +static MfClassicKeyPair banapass_keys_if_access_code[] = { + {.a = 0x6090D00632F5, .b = 0x574343467632}, + {.a = 0xA99164400748, .b = 0x62742819AD7C}, + {.a = 0xCC5075E42BA1, .b = 0xB9DF35A0814C}, + {.a = 0x8AF9C718F23D, .b = 0x58CD5C3673CB}, + {.a = 0xFC80E88EB88C, .b = 0x7A3CDAD7C023}, + {.a = 0x30424C029001, .b = 0x024E4E44001F}, + {.a = 0xECBBFA57C6AD, .b = 0x4757698143BD}, + {.a = 0x1D30972E6485, .b = 0xF8526D1A8D6D}, + {.a = 0x1300EC8C7E80, .b = 0xF80A65A87FFA}, + {.a = 0xDEB06ED4AF8E, .b = 0x4AD96BF28190}, + {.a = 0x000390014D41, .b = 0x0800F9917CB0}, + {.a = 0x730050555253, .b = 0x4146D4A956C4}, + {.a = 0x131157FBB126, .b = 0xE69DD9015A43}, + {.a = 0x337237F254D5, .b = 0x9A8389F32FBF}, + {.a = 0x7B8FB4A7100B, .b = 0xC8382A233993}, + {.a = 0x7B304F2A12A6, .b = 0xFC9418BF788B}, +}; + +static bool banapass_verify(Nfc* nfc) { + bool verified = true; + + const uint8_t verify_sector = 0; + uint8_t block_num = mf_classic_get_first_block_num_of_sector(verify_sector); + FURI_LOG_D(TAG, "Verifying sector %u", verify_sector); + + MfClassicKey key_a_0 = {}; + bit_lib_num_to_bytes_be( + banapass_keys_if_value_block[0].a, COUNT_OF(key_a_0.data), key_a_0.data); + + MfClassicAuthContext auth_ctx = {}; + MfClassicError error = + mf_classic_poller_sync_auth(nfc, block_num, &key_a_0, MfClassicKeyTypeA, &auth_ctx); + + if(error != MfClassicErrorNone) { + FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error); + verified = false; + } + + return verified; +} + +static bool banapass_read(Nfc* nfc, NfcDevice* device) { + furi_assert(nfc); + furi_assert(device); + + bool is_read = false; + + MfClassicData* data = mf_classic_alloc(); + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + + do { + MfClassicType type = MfClassicType1k; + MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); + if(error != MfClassicErrorNone) break; + if(type != MfClassicType1k) { + FURI_LOG_E(TAG, "Card not MIFARE Classic 1k"); + break; + } + + data->type = type; + MfClassicDeviceKeys keys = {}; + + // Access Code Read Attempt + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + bit_lib_num_to_bytes_be( + banapass_keys_if_access_code[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + bit_lib_num_to_bytes_be( + banapass_keys_if_access_code[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + FURI_BIT_SET(keys.key_b_mask, i); + } + + MfClassicError error_access_code = mf_classic_poller_sync_read(nfc, &keys, data); + + if(error_access_code == MfClassicErrorNone) { + nfc_device_set_data(device, NfcProtocolMfClassic, data); + is_read = true; + break; + } + + // Value Block Read Attempt + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + bit_lib_num_to_bytes_be( + banapass_keys_if_value_block[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + bit_lib_num_to_bytes_be( + banapass_keys_if_value_block[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + FURI_BIT_SET(keys.key_b_mask, i); + } + + MfClassicError error_value_block = mf_classic_poller_sync_read(nfc, &keys, data); + + if(error_value_block == MfClassicErrorNone) { + nfc_device_set_data(device, NfcProtocolMfClassic, data); + is_read = true; + break; + } + FURI_LOG_E(TAG, "Failed to read data. Bad keys?"); + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static bool banapass_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + + do { + // verify key + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 0); + uint64_t key_a = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6); + uint64_t key_b = bit_lib_bytes_to_num_be(sec_tr->key_b.data, 6); + if(key_a != banapass_keys_if_value_block[0].a) break; + + furi_string_set_str(parsed_data, "\e#Banapass\n"); + furi_string_cat_str( + parsed_data, "::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"); + furi_string_cat_str(parsed_data, "\nBandai Namco Passport\n"); + + // banapass Magic is stored at block 1, byte 2-7 + uint8_t magic_bytes[6]; + for(int i = 0; i < 6; i++) { + magic_bytes[i] = data->block[1].data[2 + i]; + } + + // verify banapass magic + if(magic_bytes[0] != 'N' || magic_bytes[1] != 'B' || magic_bytes[2] != 'G' || + magic_bytes[3] != 'I' || magic_bytes[4] != 'C') + break; + + // banapass checksum is stored at block 1, starts from byte 8-15 + uint8_t check_sum[8]; + for(int i = 0; i < 8; i++) { + check_sum[i] = data->block[1].data[8 + i]; + } + + furi_string_cat_str( + parsed_data, "::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"); + + bool is_block_2_null = true; + for(int i = 0; i < 16; i++) { + if(data->block[2].data[i] != 0) { + is_block_2_null = false; + break; + } + } + if(is_block_2_null) { + furi_string_cat_str( + parsed_data, + "\nPlease scan the clone at the\nnearest CHUNITHM or\nmaimai Cabinet for the\nAccess Code.\n"); + furi_string_cat_str( + parsed_data, "::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"); + } else { + switch(key_b) { + case banapass_key_b_value_block: + int32_t value = 0; + uint8_t addr = 0; + bool value_found = mf_classic_block_to_value( + &data->block[2], &value, &addr); // block 2 is value block + if(value_found) { + furi_string_cat_printf(parsed_data, "\nValue: %08lX", value); + } else { + furi_string_cat_str(parsed_data, "\nPotential clone:\nInvalid value block."); + } + furi_string_cat_str( + parsed_data, + "\nPlease check the back of\nyour Bandai Namco Passport\nfor the Access Code.\n"); + furi_string_cat_str( + parsed_data, "::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"); + break; + + case banapass_key_b_access_code: + // banapass access code is stored as decimal hex representation in block 2, starts from byte 6, len 10 bytes + uint8_t access_code[10]; + + furi_string_cat_printf(parsed_data, "\nAccess Code:\n"); + bool access_code_is_bcd = true; + + for(int i = 0; i < 10; i++) { + access_code[i] = data->block[2].data[6 + i]; + furi_string_cat_printf(parsed_data, "%02X", access_code[i]); + if(i % 2 == 1) { + furi_string_cat_str(parsed_data, " "); + } + if((access_code[i] >> 4) > 9) access_code_is_bcd = false; + if((access_code[i] & 0x0F) > 9) access_code_is_bcd = false; + } + furi_string_cat_printf( + parsed_data, "\nBCD valid: %s\n", access_code_is_bcd ? "Yes" : "No"); + if((access_code[0] >> 4) != 3) { + furi_string_cat_printf( + parsed_data, + "Potential clone:\nAccess Code preamble\nexpected 3, got %d\n", + (access_code[0] >> 4)); + } + furi_string_cat_str( + parsed_data, "::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"); + + break; + default: + break; + } + } + furi_string_cat_str(parsed_data, "\nMagic:\n"); + for(int i = 0; i < 6; i++) { + furi_string_cat_printf(parsed_data, "%c", magic_bytes[i]); + } + furi_string_cat_str(parsed_data, "\nChecksum:\n"); + for(int i = 0; i < 8; i++) { + furi_string_cat_printf(parsed_data, "%02X ", check_sum[i]); + } + + furi_string_cat_str(parsed_data, "\n"); + furi_string_cat_str( + parsed_data, "::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"); + + parsed = true; + + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin banapass_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = banapass_verify, + .read = banapass_read, + .parse = banapass_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor banapass_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &banapass_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* banapass_plugin_ep(void) { + return &banapass_plugin_descriptor; +} From d791711b09712470ef275c544e06024942563d33 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 24 Sep 2025 00:44:19 +0300 Subject: [PATCH 060/192] Fix FaacSLH 868 add manually crash --- applications/main/subghz/helpers/subghz_gen_info.c | 4 ++-- applications/main/subghz/scenes/subghz_scene_set_type.c | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_gen_info.c b/applications/main/subghz/helpers/subghz_gen_info.c index 90853cec5..69fa37187 100644 --- a/applications/main/subghz/helpers/subghz_gen_info.c +++ b/applications/main/subghz/helpers/subghz_gen_info.c @@ -233,7 +233,7 @@ void subghz_scene_set_type_fill_generation_infos(GenInfo* infos_dest, SetType ty .faac_slh.serial = ((key & 0x00FFFFF0) | 0xA0000006) >> 4, .faac_slh.btn = 0x06, .faac_slh.cnt = 0x02, - .faac_slh.seed = key, + .faac_slh.seed = (uint32_t)key, .faac_slh.manuf = "FAAC_SLH"}; break; case SetTypeFaacSLH_868: @@ -244,7 +244,7 @@ void subghz_scene_set_type_fill_generation_infos(GenInfo* infos_dest, SetType ty .faac_slh.serial = ((key & 0x00FFFFF0) | 0xA0000006) >> 4, .faac_slh.btn = 0x06, .faac_slh.cnt = 0x02, - .faac_slh.seed = (key & 0x0FFFFFFF), + .faac_slh.seed = (uint32_t)key, .faac_slh.manuf = "FAAC_SLH"}; break; case SetTypeBeninca433: diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index 93419a974..d3bb2482e 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -223,6 +223,8 @@ bool subghz_scene_set_type_generate_protocol_from_infos(SubGhz* subghz) { if(generated_protocol) { subghz_file_name_clear(subghz); + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneSetType, SubGhzCustomEventManagerSet); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); } else { furi_string_set(subghz->error_str, "Function requires\nan SD card with\nfresh databases."); From a0c85ed7374ca6a7c79ceb7332c3e32abfdf641c Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 24 Sep 2025 00:46:35 +0300 Subject: [PATCH 061/192] upd changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7258caf6..1ac4e782c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ * LFRFID: Add additional procotols supported by EM4305 chipset (by @jamisonderek) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes +* SubGHz: Fix crash in add manually menu +* OFW: NFC: MFC 1k Banapass Parser +* OFW: GUI Bug Fix: Number Input Save Icon * Add possibility to use custom buttons when using the SubGHz remote app (by @MrLego8-9) * Input Settings: Add Vibro Trigger option (by @956MB & @WillyJL) * BT Remote: Add Rename Option (by @aaronjamt & @WillyJL) From 735664da9679ba11da15934d7d7bf829c7534328 Mon Sep 17 00:00:00 2001 From: "Aaron Tulino (Aaronjamt)" Date: Tue, 23 Sep 2025 15:07:06 -0700 Subject: [PATCH 062/192] Add F18 target --- targets/f18/api_symbols.csv | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 23c9edb63..165f367c6 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,86.0,, +Version,v,86.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -13,6 +13,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,, @@ -833,6 +834,11 @@ Function,+,crc32_calc_buffer,uint32_t,"uint32_t, const void*, size_t" Function,+,crc32_calc_file,uint32_t,"File*, const FileCrcProgressCb, void*" 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 d35976cf6cf27c2b5a04db873c3a1c7c2069cba8 Mon Sep 17 00:00:00 2001 From: "Aaron Tulino (Aaronjamt)" Date: Tue, 23 Sep 2025 15:13:25 -0700 Subject: [PATCH 063/192] Add 12-hour time support --- ...ple_date_time_input_scene_show_date_time.c | 21 ++++-- .../services/gui/modules/date_time_input.c | 67 ++++++++++++++++++- 2 files changed, 82 insertions(+), 6 deletions(-) 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 index 666db14a6..15e4cce08 100644 --- 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 @@ -13,17 +13,30 @@ static void example_date_time_input_scene_update_view(void* context) { dialog_ex_set_header(dialog_ex, "The date and time are", 64, 0, AlignCenter, AlignTop); - char buffer[26] = {}; + 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", + "%04d-%02d-%02d\n%02d:%02d:%02d%s", app->date_time.year, app->date_time.month, app->date_time.day, - app->date_time.hour, + hour, app->date_time.minute, - app->date_time.second); + 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"); diff --git a/applications/services/gui/modules/date_time_input.c b/applications/services/gui/modules/date_time_input.c index eb703cda2..a80634823 100644 --- a/applications/services/gui/modules/date_time_input.c +++ b/applications/services/gui/modules/date_time_input.c @@ -1,4 +1,5 @@ #include "date_time_input.h" +#include "furi_hal_rtc.h" #include #include @@ -88,9 +89,31 @@ static inline void date_time_input_draw_block( } } -static void date_time_input_draw_time_callback(Canvas* canvas, DateTimeInputModel* model) { - furi_check(model->datetime); +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[64]; canvas_set_font(canvas, FontSecondary); @@ -102,6 +125,46 @@ static void date_time_input_draw_time_callback(Canvas* canvas, DateTimeInputMode 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[64]; + + 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 XX since FontBigNumbers can't draw letters + snprintf(buffer, sizeof(buffer), "%02u XX", hour); + date_time_input_draw_block( + canvas, 2, ROW_1_Y, 56, 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); + + if(model->datetime->hour < 12) { + snprintf(buffer, sizeof(buffer), "AM"); + } else { + snprintf(buffer, sizeof(buffer), "PM"); + } + date_time_input_draw_text( + canvas, 28, ROW_1_Y + 3, 28, ROW_1_H, FontPrimary, get_state(model, 1, 0, hour), buffer); +} + +static void date_time_input_draw_time_callback(Canvas* canvas, DateTimeInputModel* model) { + furi_check(model->datetime); + + char buffer[64]; + + // 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( From 8bd66c99209d896c26fcee2bb2be1c6f6882dcf8 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Wed, 24 Sep 2025 00:16:47 +0200 Subject: [PATCH 064/192] JS: Expose button event type in gui/widget button callback (#4252) * JS: Expose button event type in gui/widget button callback --nobuild * js_sdk: bump major version * unit_tests: fix js test * js: fix gui widget demo --------- Co-authored-by: Anna Antonenko Co-authored-by: hedger --- .../resources/unit_tests/js/basic.js | 6 +-- .../js_app/examples/apps/Scripts/gui.js | 9 ++-- applications/system/js_app/js_modules.c | 4 -- applications/system/js_app/js_modules.h | 4 +- .../system/js_app/modules/js_gui/widget.c | 53 ++++++++++++++----- .../packages/create-fz-app/package.json | 2 +- .../create-fz-app/template/package.json | 2 +- .../system/js_app/packages/fz-sdk/global.d.ts | 6 +-- .../js_app/packages/fz-sdk/gpio/index.d.ts | 4 ++ .../js_app/packages/fz-sdk/gui/icon.d.ts | 2 + .../js_app/packages/fz-sdk/gui/index.d.ts | 7 ++- .../js_app/packages/fz-sdk/gui/widget.d.ts | 25 +++++++-- .../js_app/packages/fz-sdk/package.json | 2 +- .../js_app/packages/fz-sdk/serial/index.d.ts | 1 + documentation/js/js_gui__widget.md | 17 ++++++ 15 files changed, 107 insertions(+), 37 deletions(-) diff --git a/applications/debug/unit_tests/resources/unit_tests/js/basic.js b/applications/debug/unit_tests/resources/unit_tests/js/basic.js index 0ef904ecb..887b47fb3 100644 --- a/applications/debug/unit_tests/resources/unit_tests/js/basic.js +++ b/applications/debug/unit_tests/resources/unit_tests/js/basic.js @@ -4,12 +4,12 @@ let flipper = require("flipper"); tests.assert_eq(1337, 1337); tests.assert_eq("hello", "hello"); -tests.assert_eq("compatible", sdkCompatibilityStatus(0, 1)); +tests.assert_eq("compatible", sdkCompatibilityStatus(1, 0)); tests.assert_eq("firmwareTooOld", sdkCompatibilityStatus(100500, 0)); tests.assert_eq("firmwareTooNew", sdkCompatibilityStatus(-100500, 0)); tests.assert_eq(true, doesSdkSupport(["baseline"])); tests.assert_eq(false, doesSdkSupport(["abobus", "other-nonexistent-feature"])); tests.assert_eq("flipperdevices", flipper.firmwareVendor); -tests.assert_eq(0, flipper.jsSdkVersion[0]); -tests.assert_eq(3, flipper.jsSdkVersion[1]); +tests.assert_eq(1, flipper.jsSdkVersion[0]); +tests.assert_eq(0, flipper.jsSdkVersion[1]); diff --git a/applications/system/js_app/examples/apps/Scripts/gui.js b/applications/system/js_app/examples/apps/Scripts/gui.js index bc63a7ef6..bc569cee0 100644 --- a/applications/system/js_app/examples/apps/Scripts/gui.js +++ b/applications/system/js_app/examples/apps/Scripts/gui.js @@ -12,6 +12,7 @@ let filePicker = require("gui/file_picker"); let widget = require("gui/widget"); let icon = require("gui/icon"); let flipper = require("flipper"); +let math = require("math"); // declare clock widget children let cuteDolphinWithWatch = icon.getBuiltin("DolphinWait_59x54"); @@ -131,14 +132,14 @@ eventLoop.subscribe(gui.viewDispatcher.navigation, function (_sub, _, gui, views }, gui, views, eventLoop); // go to the demo chooser screen when the right key is pressed on the widget screen -eventLoop.subscribe(views.stopwatchWidget.button, function (_sub, buttonId, gui, views) { - if (buttonId === "right") +eventLoop.subscribe(views.stopwatchWidget.button, function (_sub, buttonEvent, gui, views) { + if (buttonEvent.key === "right" && buttonEvent.type === "short") gui.viewDispatcher.switchTo(views.demos); }, gui, views); // count time eventLoop.subscribe(eventLoop.timer("periodic", 500), function (_sub, _item, views, stopwatchWidgetElements, halfSeconds) { - let text = (halfSeconds / 2 / 60).toString(); + let text = math.floor(halfSeconds / 2 / 60).toString(); if (halfSeconds < 10 * 60 * 2) text = "0" + text; @@ -146,7 +147,7 @@ eventLoop.subscribe(eventLoop.timer("periodic", 500), function (_sub, _item, vie if (((halfSeconds / 2) % 60) < 10) text += "0"; - text += ((halfSeconds / 2) % 60).toString(); + text += (math.floor(halfSeconds / 2) % 60).toString(); stopwatchWidgetElements[0].text = text; views.stopwatchWidget.setChildren(stopwatchWidgetElements); diff --git a/applications/system/js_app/js_modules.c b/applications/system/js_app/js_modules.c index f9c08058f..095880f17 100644 --- a/applications/system/js_app/js_modules.c +++ b/applications/system/js_app/js_modules.c @@ -272,10 +272,6 @@ void js_check_sdk_compatibility(struct mjs* mjs) { static const char* extra_features[] = { "baseline", // dummy "feature" - "gpio-pwm", - "gui-widget", - "serial-framing", - "gui-widget-extras", }; /** diff --git a/applications/system/js_app/js_modules.h b/applications/system/js_app/js_modules.h index c6f72bbe2..a1e6d209e 100644 --- a/applications/system/js_app/js_modules.h +++ b/applications/system/js_app/js_modules.h @@ -15,8 +15,8 @@ extern "C" { #define PLUGIN_API_VERSION 1 #define JS_SDK_VENDOR "flipperdevices" -#define JS_SDK_MAJOR 0 -#define JS_SDK_MINOR 3 +#define JS_SDK_MAJOR 1 +#define JS_SDK_MINOR 0 /** * @brief Returns the foreign pointer in `obj["_"]` diff --git a/applications/system/js_app/modules/js_gui/widget.c b/applications/system/js_app/modules/js_gui/widget.c index bb2898030..47149b4ac 100644 --- a/applications/system/js_app/modules/js_gui/widget.c +++ b/applications/system/js_app/modules/js_gui/widget.c @@ -10,6 +10,11 @@ typedef struct { #define QUEUE_LEN 2 +typedef struct { + GuiButtonType key; + InputType type; +} JsWidgetButtonEvent; + /** * @brief Parses position (X and Y) from an element declaration object */ @@ -101,8 +106,11 @@ static bool element_get_text(struct mjs* mjs, mjs_val_t element, mjs_val_t* text * @brief Widget button element callback */ static void js_widget_button_callback(GuiButtonType result, InputType type, JsWidgetCtx* context) { - UNUSED(type); - furi_check(furi_message_queue_put(context->queue, &result, 0) == FuriStatusOk); + JsWidgetButtonEvent event = { + .key = result, + .type = type, + }; + furi_check(furi_message_queue_put(context->queue, &event, 0) == FuriStatusOk); } #define DESTRUCTURE_OR_RETURN(mjs, child_obj, part, ...) \ @@ -263,25 +271,44 @@ static mjs_val_t js_widget_button_event_transformer( FuriMessageQueue* queue, JsWidgetCtx* context) { UNUSED(context); - GuiButtonType btn_type; - furi_check(furi_message_queue_get(queue, &btn_type, 0) == FuriStatusOk); - const char* btn_name; - if(btn_type == GuiButtonTypeLeft) { - btn_name = "left"; - } else if(btn_type == GuiButtonTypeCenter) { - btn_name = "center"; - } else if(btn_type == GuiButtonTypeRight) { - btn_name = "right"; + JsWidgetButtonEvent event; + furi_check(furi_message_queue_get(queue, &event, 0) == FuriStatusOk); + const char* event_key; + if(event.key == GuiButtonTypeLeft) { + event_key = "left"; + } else if(event.key == GuiButtonTypeCenter) { + event_key = "center"; + } else if(event.key == GuiButtonTypeRight) { + event_key = "right"; } else { furi_crash(); } - return mjs_mk_string(mjs, btn_name, ~0, false); + const char* event_type; + if(event.type == InputTypePress) { + event_type = "press"; + } else if(event.type == InputTypeRelease) { + event_type = "release"; + } else if(event.type == InputTypeShort) { + event_type = "short"; + } else if(event.type == InputTypeLong) { + event_type = "long"; + } else if(event.type == InputTypeRepeat) { + event_type = "repeat"; + } else { + furi_crash(); + } + mjs_val_t obj = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, obj) { + JS_FIELD("key", mjs_mk_string(mjs, event_key, ~0, true)); + JS_FIELD("type", mjs_mk_string(mjs, event_type, ~0, true)); + } + return obj; } static void* js_widget_custom_make(struct mjs* mjs, Widget* widget, mjs_val_t view_obj) { UNUSED(widget); JsWidgetCtx* context = malloc(sizeof(JsWidgetCtx)); - context->queue = furi_message_queue_alloc(QUEUE_LEN, sizeof(GuiButtonType)); + context->queue = furi_message_queue_alloc(QUEUE_LEN, sizeof(JsWidgetButtonEvent)); context->contract = (JsEventLoopContract){ .magic = JsForeignMagic_JsEventLoopContract, .object_type = JsEventLoopObjectTypeQueue, diff --git a/applications/system/js_app/packages/create-fz-app/package.json b/applications/system/js_app/packages/create-fz-app/package.json index 7778104e2..b2a8f157a 100644 --- a/applications/system/js_app/packages/create-fz-app/package.json +++ b/applications/system/js_app/packages/create-fz-app/package.json @@ -1,6 +1,6 @@ { "name": "@flipperdevices/create-fz-app", - "version": "0.1.1", + "version": "0.1.2", "description": "Template package for JS apps Flipper Zero", "bin": "index.js", "type": "module", diff --git a/applications/system/js_app/packages/create-fz-app/template/package.json b/applications/system/js_app/packages/create-fz-app/template/package.json index 322a8b58b..a67dc71c3 100644 --- a/applications/system/js_app/packages/create-fz-app/template/package.json +++ b/applications/system/js_app/packages/create-fz-app/template/package.json @@ -6,7 +6,7 @@ "start": "npm run build && node node_modules/@flipperdevices/fz-sdk/sdk.js upload" }, "devDependencies": { - "@flipperdevices/fz-sdk": "^0.3", + "@flipperdevices/fz-sdk": "^1.0", "typescript": "^5.6.3" } } \ No newline at end of file diff --git a/applications/system/js_app/packages/fz-sdk/global.d.ts b/applications/system/js_app/packages/fz-sdk/global.d.ts index 4c7f217d0..71e177823 100644 --- a/applications/system/js_app/packages/fz-sdk/global.d.ts +++ b/applications/system/js_app/packages/fz-sdk/global.d.ts @@ -72,7 +72,7 @@ * @brief Checks compatibility between the script and the JS SDK that the * firmware provides * - * @note You're looking at JS SDK v0.3 + * @note You're looking at JS SDK v1.0 * * @param expectedMajor JS SDK major version expected by the script * @param expectedMinor JS SDK minor version expected by the script @@ -92,7 +92,7 @@ declare function sdkCompatibilityStatus(expectedMajor: number, expectedMinor: nu * @brief Checks compatibility between the script and the JS SDK that the * firmware provides in a boolean fashion * - * @note You're looking at JS SDK v0.3 + * @note You're looking at JS SDK v1.0 * * @param expectedMajor JS SDK major version expected by the script * @param expectedMinor JS SDK minor version expected by the script @@ -105,7 +105,7 @@ declare function isSdkCompatible(expectedMajor: number, expectedMinor: number): * @brief Asks the user whether to continue executing the script if the versions * are not compatible. Does nothing if they are. * - * @note You're looking at JS SDK v0.3 + * @note You're looking at JS SDK v1.0 * * @param expectedMajor JS SDK major version expected by the script * @param expectedMinor JS SDK minor version expected by the script diff --git a/applications/system/js_app/packages/fz-sdk/gpio/index.d.ts b/applications/system/js_app/packages/fz-sdk/gpio/index.d.ts index cd5ce2b60..3a3af12f7 100644 --- a/applications/system/js_app/packages/fz-sdk/gpio/index.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gpio/index.d.ts @@ -80,6 +80,7 @@ export interface Pin { * PWM-related methods on this pin will throw an error when called. * @note On Flipper Zero only pins PA4 and PA7 support PWM * @version Added in JS SDK 0.2, extra feature `"gpio-pwm"` + * @version Baseline since JS SDK 1.0 */ isPwmSupported(): boolean; /** @@ -89,18 +90,21 @@ export interface Pin { * @param freq Frequency in Hz * @param duty Duty cycle in % * @version Added in JS SDK 0.2, extra feature `"gpio-pwm"` + * @version Baseline since JS SDK 1.0 */ pwmWrite(freq: number, duty: number): void; /** * Determines whether PWM is running. Throws an error if PWM is not * supported on this pin. * @version Added in JS SDK 0.2, extra feature `"gpio-pwm"` + * @version Baseline since JS SDK 1.0 */ isPwmRunning(): boolean; /** * Stops PWM. Does not restore previous pin configuration. Throws an error * if PWM is not supported on this pin. * @version Added in JS SDK 0.2, extra feature `"gpio-pwm"` + * @version Baseline since JS SDK 1.0 */ pwmStop(): void; } diff --git a/applications/system/js_app/packages/fz-sdk/gui/icon.d.ts b/applications/system/js_app/packages/fz-sdk/gui/icon.d.ts index 8d2b68bce..03fc8c53b 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/icon.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/icon.d.ts @@ -7,6 +7,7 @@ export type IconData = symbol & { "__tag__": "icon" }; * Gets a built-in firmware icon for use in GUI * @param icon Name of the icon * @version Added in JS SDK 0.2, extra feature `"gui-widget"` + * @version Baseline since JS SDK 1.0 */ export declare function getBuiltin(icon: BuiltinIcon): IconData; @@ -14,5 +15,6 @@ export declare function getBuiltin(icon: BuiltinIcon): IconData; * Loads a .fxbm icon (XBM Flipper sprite, from flipperzero-game-engine) for use in GUI * @param path Path to the .fxbm file * @version Added in JS SDK 0.3, extra feature `"gui-widget-extras"` + * @version Baseline since JS SDK 1.0 */ export declare function loadFxbm(path: string): IconData; diff --git a/applications/system/js_app/packages/fz-sdk/gui/index.d.ts b/applications/system/js_app/packages/fz-sdk/gui/index.d.ts index 969b6934e..77e5891cd 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/index.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/index.d.ts @@ -133,17 +133,20 @@ export declare class View { * Adds a child to the View * @param child Child to add * @version Added in JS SDK 0.2, extra feature `"gui-widget"` + * @version Baseline since JS SDK 1.0 */ addChild(child: C): void; /** * Removes all children from the View * @version Added in JS SDK 0.2, extra feature `"gui-widget"` + * @version Baseline since JS SDK 1.0 */ resetChildren(): void; /** * Removes all previous children from the View and assigns new children * @param children The list of children to assign * @version Added in JS SDK 0.2, extra feature `"gui-widget"` + * @version Baseline since JS SDK 1.0 */ setChildren(children: Child[]): void; } @@ -158,7 +161,9 @@ export declare class ViewFactory, children?: Child[]): V; } diff --git a/applications/system/js_app/packages/fz-sdk/gui/widget.d.ts b/applications/system/js_app/packages/fz-sdk/gui/widget.d.ts index bf4aab22b..3b0bea006 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/widget.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/widget.d.ts @@ -23,6 +23,7 @@ * This view has the elements as its children. * * @version Added in JS SDK 0.2, extra feature `"gui-widget"` + * @version Baseline since JS SDK 1.0 * @module */ @@ -42,9 +43,21 @@ type TextBoxElement = { element: "text_box", stripToDots: boolean } & Position & type TextScrollElement = { element: "text_scroll" } & Position & Size & Text; type ButtonElement = { element: "button", button: "left" | "center" | "right" } & Text; type IconElement = { element: "icon", iconData: IconData } & Position; -type RectElement = { element: "rect", radius: number, fill: boolean } & Position & Size; /** @version Amended in JS SDK 0.3, extra feature `"gui-widget-extras"` */ -type CircleElement = { element: "circle", radius: number, fill: boolean } & Position; /** @version Added in JS SDK 0.3, extra feature `"gui-widget-extras"` */ -type LineElement = { element: "line", x1: number, y1: number, x2: number, y2: number }; /** @version Added in JS SDK 0.3, extra feature `"gui-widget-extras"` */ +/** + * @version Amended in JS SDK 0.3, extra feature `"gui-widget-extras"` + * @version Baseline since JS SDK 1.0 + * */ +type RectElement = { element: "rect", radius: number, fill: boolean } & Position & Size; +/** + * @version Added in JS SDK 0.3, extra feature `"gui-widget-extras"` + * @version Baseline since JS SDK 1.0 + * */ +type CircleElement = { element: "circle", radius: number, fill: boolean } & Position; +/** + * @version Added in JS SDK 0.3, extra feature `"gui-widget-extras"` + * @version Baseline since JS SDK 1.0 + * */ +type LineElement = { element: "line", x1: number, y1: number, x2: number, y2: number }; type Element = StringMultilineElement | StringElement @@ -58,12 +71,16 @@ type Element = StringMultilineElement type Props = {}; type Child = Element; +declare class ButtonEvent { + key: "left" | "center" | "right"; + type: "press" | "release" | "short" | "long" | "repeat"; +} declare class Widget extends View { /** * Event source for buttons. Only gets fired if there's a corresponding * button element. */ - button: Contract<"left" | "center" | "right">; + button: Contract; } declare class WidgetFactory extends ViewFactory { } declare const factory: WidgetFactory; diff --git a/applications/system/js_app/packages/fz-sdk/package.json b/applications/system/js_app/packages/fz-sdk/package.json index 3ab108e48..020f3d45c 100644 --- a/applications/system/js_app/packages/fz-sdk/package.json +++ b/applications/system/js_app/packages/fz-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@flipperdevices/fz-sdk", - "version": "0.3.0", + "version": "1.0.0", "description": "Type declarations and documentation for native JS modules available on Flipper Zero", "keywords": [ "flipper", diff --git a/applications/system/js_app/packages/fz-sdk/serial/index.d.ts b/applications/system/js_app/packages/fz-sdk/serial/index.d.ts index 5064c4213..14247f509 100644 --- a/applications/system/js_app/packages/fz-sdk/serial/index.d.ts +++ b/applications/system/js_app/packages/fz-sdk/serial/index.d.ts @@ -29,6 +29,7 @@ export interface Framing { * @param framing See `Framing` type * @version Added in JS SDK 0.1 * @version Added `framing` parameter in JS SDK 0.3, extra feature `"serial-framing"` + * @version Baseline since JS SDK 1.0 */ export declare function setup(port: "lpuart" | "usart", baudRate: number, framing?: Framing): void; diff --git a/documentation/js/js_gui__widget.md b/documentation/js/js_gui__widget.md index 9ea3e4dfa..4c922a67c 100644 --- a/documentation/js/js_gui__widget.md +++ b/documentation/js/js_gui__widget.md @@ -35,3 +35,20 @@ Elements are objects with properties to define them, in the form `{ element: "ty | `rect` | `x` (number), `y` (number)
`w` (number), `h` (number)
`radius` (number), `fill` (boolean) | Draw a rectangle, optionally rounded and filled. | | `circle` | `x` (number), `y` (number)
`radius` (number), `fill` (boolean) | Draw a circle, optionally filled. | | `line` | `x1` (number), `y1` (number)
`x2` (number), `y2` (number) | Draw a line between 2 points. | + +## Structures + +### ButtonEvent + +Button event information structure. + +**Fields** + +- key: The key that was pressed (`"left" | "center" | "right"`) +- type: The type of the event (`"press" | "release" | "short" | "long" | "repeat"`) + +## View events + +| Item | Type | Description | +|----------|--------|-----------------------------------------------------------------------------| +| `button` | `ButtonEvent`| Fires when the user presses on one of the three possible buttons if there's a corresponding button element. Refer to the `ButtonEvent` structure above for possible values. | From 2dd3673dcd0d4ba9124b79bf1dd9ca4d4428d97f Mon Sep 17 00:00:00 2001 From: "Aaron Tulino (Aaronjamt)" Date: Tue, 23 Sep 2025 15:33:28 -0700 Subject: [PATCH 065/192] Improve 12-hour UI --- .../services/gui/modules/date_time_input.c | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/applications/services/gui/modules/date_time_input.c b/applications/services/gui/modules/date_time_input.c index a80634823..be64ed165 100644 --- a/applications/services/gui/modules/date_time_input.c +++ b/applications/services/gui/modules/date_time_input.c @@ -114,7 +114,7 @@ static inline void date_time_input_draw_text( } static void date_time_input_draw_hour_24hr_callback(Canvas* canvas, DateTimeInputModel* model) { - char buffer[64]; + char buffer[4]; canvas_set_font(canvas, FontSecondary); canvas_draw_str(canvas, 0, ROW_1_Y - 2, " H H M M S S"); @@ -128,36 +128,40 @@ static void date_time_input_draw_hour_24hr_callback(Canvas* canvas, DateTimeInpu } static void date_time_input_draw_hour_12hr_callback(Canvas* canvas, DateTimeInputModel* model) { - char buffer[64]; + char buffer[4]; canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 0, ROW_1_Y - 2, " H H M M S S"); + 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 XX since FontBigNumbers can't draw letters - snprintf(buffer, sizeof(buffer), "%02u XX", hour); + // Placeholder spaces to make room for AM/PM since FontBigNumbers can't draw letters date_time_input_draw_block( - canvas, 2, ROW_1_Y, 56, ROW_1_H, FontBigNumbers, get_state(model, 1, 0, hour), buffer); + 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); - if(model->datetime->hour < 12) { - snprintf(buffer, sizeof(buffer), "AM"); - } else { - snprintf(buffer, sizeof(buffer), "PM"); - } + snprintf(buffer, sizeof(buffer), "%02u", hour); date_time_input_draw_text( - canvas, 28, ROW_1_Y + 3, 28, ROW_1_H, FontPrimary, get_state(model, 1, 0, hour), buffer); + 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[64]; + char buffer[4]; // Draw hour depending on RTC time format if(furi_hal_rtc_get_locale_timeformat() == FuriHalRtcLocaleTimeFormat24h) { @@ -180,7 +184,7 @@ static void date_time_input_draw_time_callback(Canvas* canvas, DateTimeInputMode static void date_time_input_draw_date_callback(Canvas* canvas, DateTimeInputModel* model) { furi_check(model->datetime); - char buffer[64]; + 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"); From ad94694fbd8aa35a72ea53d03420d56281a69f04 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Wed, 24 Sep 2025 11:19:18 +0200 Subject: [PATCH 066/192] CLI: Fix long delay with quick connect/disconnect (#4251) * CLI: Fix long delay with quick connect/disconnect noticeable with qflipper, for some reason it sets DTR on/off/on again so flipper starts CLI, stops it, then starts again but cli shell is trying to print motd, and pipe is already broken so each character of motd times out for 100ms changing pipe timeout to 10ms works too, but is just a workaround it didnt always happen, i think that variable time of loading ext cmds made it so on ofw it usually manages to print motd before pipe is broken on cfw with more ext commands it always hung, on ofw only sometimes important part is bailing early in cli shell in cli vcp i made it cleanup cli shell on disconnect so it doesnt stay around after disconnecting usb, might free a little ram maybe * cli_shell: possibly more robust fix? * Fix use after free crash * cli_shell: waste even less time Co-Authored-By: WillyJL --------- Co-authored-by: Anna Antonenko Co-authored-by: hedger --- applications/services/cli/cli_vcp.c | 13 +++++-------- lib/toolbox/cli/shell/cli_shell.c | 16 +++++++++++----- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/applications/services/cli/cli_vcp.c b/applications/services/cli/cli_vcp.c index 1f9c77b08..9db1bec43 100644 --- a/applications/services/cli/cli_vcp.c +++ b/applications/services/cli/cli_vcp.c @@ -218,6 +218,11 @@ static void cli_vcp_internal_event_happened(FuriEventLoopObject* object, void* c pipe_detach_from_event_loop(cli_vcp->own_pipe); pipe_free(cli_vcp->own_pipe); cli_vcp->own_pipe = NULL; + + // wait for shell to stop + cli_shell_join(cli_vcp->shell); + cli_shell_free(cli_vcp->shell); + pipe_free(cli_vcp->shell_pipe); break; } @@ -226,14 +231,6 @@ static void cli_vcp_internal_event_happened(FuriEventLoopObject* object, void* c FURI_LOG_D(TAG, "Connected"); cli_vcp->is_connected = true; - // wait for previous shell to stop - furi_check(!cli_vcp->own_pipe); - if(cli_vcp->shell) { - cli_shell_join(cli_vcp->shell); - cli_shell_free(cli_vcp->shell); - pipe_free(cli_vcp->shell_pipe); - } - // start shell thread PipeSideBundle bundle = pipe_alloc(VCP_BUF_SIZE, 1); cli_vcp->own_pipe = bundle.alices_side; diff --git a/lib/toolbox/cli/shell/cli_shell.c b/lib/toolbox/cli/shell/cli_shell.c index 7a4c7ec1f..b2648d127 100644 --- a/lib/toolbox/cli/shell/cli_shell.c +++ b/lib/toolbox/cli/shell/cli_shell.c @@ -16,7 +16,8 @@ #define TAG "CliShell" -#define ANSI_TIMEOUT_MS 10 +#define ANSI_TIMEOUT_MS 10 +#define TRANSIENT_SESSION_WINDOW_MS 100 typedef enum { CliShellComponentCompletions, @@ -415,10 +416,15 @@ static void cli_shell_deinit(CliShell* shell) { static int32_t cli_shell_thread(void* context) { CliShell* shell = context; - // Sometimes, the other side closes the pipe even before our thread is started. Although the - // rest of the code will eventually find this out if this check is removed, there's no point in - // wasting time. - if(pipe_state(shell->pipe) == PipeStateBroken) return 0; + // Sometimes, the other side (e.g. qFlipper) closes the pipe even before our thread is started. + // Although the rest of the code will eventually find this out if this check is removed, + // there's no point in wasting time. This gives qFlipper a chance to quickly close and re-open + // the session. + const size_t delay_step = 10; + for(size_t i = 0; i < TRANSIENT_SESSION_WINDOW_MS / delay_step; i++) { + furi_delay_ms(delay_step); + if(pipe_state(shell->pipe) == PipeStateBroken) return 0; + } cli_shell_init(shell); FURI_LOG_D(TAG, "Started"); From dfd753703ad10177aa133154c8125d53720c8d2e Mon Sep 17 00:00:00 2001 From: dogtopus Date: Wed, 24 Sep 2025 19:08:40 +0900 Subject: [PATCH 067/192] FeliCa Emulation: Handle certain Polling commands in firmware (#4204) * FeliCa: Handle non-hardware Polling commands NFC TagInfo and possibly other readers rely on Polling commands with Request Code of 1 (default System Code request) or non-FFFF System Code to detect card type. Since the NFC controller doesn't seem to handle them in hardware and simply bubbles them up, and then the Flipper firmware will just ignore them and refuse to respond afterwards, this causes the reading operation to fail. This commit adds a simple handler for such Polling commands so that readers behaving like NFC TagInfo could read the emulated card without failing. * Only handle cases when System Code is not FFFF The NFC controller should handle Polling commands with the System Code set to FFFF, so it's not necessary for the firmware to handle it. * Remove system code logging * More cleanups * Remove the claim that we need a poller change We already have enough information to determine whether or not the card supports NDEF since SYS_OP register value is included in all current Lite-S card dumps. * Respond to 12FC polling command when needed * Handle Polling with NDEF and Lite-S Service Code This allows the reader to specifically select the service by naming the Service Code. * Introduce API for manual handling of Polling commands Introduce nfc_felica_listener_timer_anticol_start() and nfc_felica_listener_timer_anticol_stop(). These are for now just wrappers around the block_tx timer that can be used to delay the response until the desired Time Slot. Thanks to the loose timing constraints of FeliCa collision resolution protocol, no compensation seems to be necessary. Also enabled the block_tx timer for FeliCa listener, but with both compensation and fdt set to 0 to keep the original behavior of not using the timer during normal data exchange. This API is now being used for handling Polling commands that are not handled by the NFC controller on the hardware side. * Document target_time_slot * Implement changes suggested by @RebornedBrain * api: f18 version sync * nfc: added stubs for `nfc_felica_listener_timer_anticol` for unit tests --------- Co-authored-by: hedger Co-authored-by: hedger --- lib/nfc/nfc.c | 19 ++++ lib/nfc/nfc.h | 18 ++++ lib/nfc/nfc_mock.c | 10 +++ lib/nfc/protocols/felica/felica.h | 3 +- lib/nfc/protocols/felica/felica_listener.c | 93 +++++++++++++++++++- lib/nfc/protocols/felica/felica_listener_i.c | 13 --- lib/nfc/protocols/felica/felica_listener_i.h | 37 +++++++- targets/f18/api_symbols.csv | 2 +- targets/f7/api_symbols.csv | 4 +- targets/f7/furi_hal/furi_hal_nfc_felica.c | 3 +- 10 files changed, 182 insertions(+), 20 deletions(-) diff --git a/lib/nfc/nfc.c b/lib/nfc/nfc.c index 90e65b282..e843ef70c 100644 --- a/lib/nfc/nfc.c +++ b/lib/nfc/nfc.c @@ -9,6 +9,9 @@ #define NFC_MAX_BUFFER_SIZE (256) +#define NFC_FELICA_LISTENER_RESPONSE_TIME_A_FC (512 * 64) +#define NFC_FELICA_LISTENER_RESPONSE_TIME_B_FC (256 * 64) + typedef enum { NfcStateIdle, NfcStateRunning, @@ -661,4 +664,20 @@ NfcError nfc_felica_listener_set_sensf_res_data( return nfc_process_hal_error(error); } +void nfc_felica_listener_timer_anticol_start(Nfc* instance, uint8_t target_time_slot) { + furi_check(instance); + + furi_hal_nfc_timer_block_tx_start( + NFC_FELICA_LISTENER_RESPONSE_TIME_A_FC + + target_time_slot * NFC_FELICA_LISTENER_RESPONSE_TIME_B_FC); +} + +void nfc_felica_listener_timer_anticol_stop(Nfc* instance) { + furi_check(instance); + + if(furi_hal_nfc_timer_block_tx_is_running()) { + furi_hal_nfc_timer_block_tx_stop(); + } +} + #endif // FW_CFG_unit_tests diff --git a/lib/nfc/nfc.h b/lib/nfc/nfc.h index 8fbf90d1f..bbbaf306b 100644 --- a/lib/nfc/nfc.h +++ b/lib/nfc/nfc.h @@ -380,6 +380,24 @@ NfcError nfc_felica_listener_set_sensf_res_data( */ NfcError nfc_iso15693_listener_tx_sof(Nfc* instance); +/** + * @brief Start the timer used for manual FeliCa collision resolution in listener mode. + * + * This blocks TX until the desired Time Slot, and should be called as soon as the listener + * determines that a collision resolution needs to be handled manually. + * + * @param[in, out] instance instance pointer to the instance to be configured. + * @param[in] target_time_slot Target Time Slot number. Should be a value within the range of 0-15 (double-inclusive). + */ +void nfc_felica_listener_timer_anticol_start(Nfc* instance, uint8_t target_time_slot); + +/** + * @brief Cancel the timer used for manual FeliCa collision resolution in listener mode. + * + * @param[in, out] instance instance pointer to the instance to be configured. + */ +void nfc_felica_listener_timer_anticol_stop(Nfc* instance); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/nfc_mock.c b/lib/nfc/nfc_mock.c index ee4bb09cc..717c93519 100644 --- a/lib/nfc/nfc_mock.c +++ b/lib/nfc/nfc_mock.c @@ -518,4 +518,14 @@ NfcError nfc_felica_listener_set_sensf_res_data( return NfcErrorNone; } +void nfc_felica_listener_timer_anticol_start(Nfc* instance, uint8_t target_time_slot) { + furi_check(instance); + + UNUSED(target_time_slot); +} + +void nfc_felica_listener_timer_anticol_stop(Nfc* instance) { + furi_check(instance); +} + #endif diff --git a/lib/nfc/protocols/felica/felica.h b/lib/nfc/protocols/felica/felica.h index 7b9c78e07..b4e7af9d1 100644 --- a/lib/nfc/protocols/felica/felica.h +++ b/lib/nfc/protocols/felica/felica.h @@ -38,7 +38,7 @@ extern "C" { #define FELICA_FDT_POLL_FC (10000U) #define FELICA_POLL_POLL_MIN_US (1280U) -#define FELICA_FDT_LISTEN_FC (1172) +#define FELICA_FDT_LISTEN_FC (0) #define FELICA_SYSTEM_CODE_CODE (0xFFFFU) #define FELICA_TIME_SLOT_1 (0x00U) @@ -58,6 +58,7 @@ typedef enum { FelicaErrorWrongCrc, FelicaErrorProtocol, FelicaErrorTimeout, + FelicaErrorFeatureUnsupported, } FelicaError; typedef struct { diff --git a/lib/nfc/protocols/felica/felica_listener.c b/lib/nfc/protocols/felica/felica_listener.c index 90954de97..3bf109366 100644 --- a/lib/nfc/protocols/felica/felica_listener.c +++ b/lib/nfc/protocols/felica/felica_listener.c @@ -5,9 +5,20 @@ #include #define FELICA_LISTENER_MAX_BUFFER_SIZE (128) +#define FELICA_LISTENER_CMD_POLLING (0x00U) +#define FELICA_LISTENER_RESPONSE_POLLING (0x01U) #define FELICA_LISTENER_RESPONSE_CODE_READ (0x07) #define FELICA_LISTENER_RESPONSE_CODE_WRITE (0x09) +#define FELICA_LISTENER_REQUEST_NONE (0x00U) +#define FELICA_LISTENER_REQUEST_SYSTEM_CODE (0x01U) +#define FELICA_LISTENER_REQUEST_PERFORMANCE (0x02U) + +#define FELICA_LISTENER_SYSTEM_CODE_NDEF (__builtin_bswap16(0x12FCU)) +#define FELICA_LISTENER_SYSTEM_CODE_LITES (__builtin_bswap16(0x88B4U)) + +#define FELICA_LISTENER_PERFORMANCE_VALUE (__builtin_bswap16(0x0083U)) + #define TAG "FelicaListener" FelicaListener* felica_listener_alloc(Nfc* nfc, FelicaData* data) { @@ -151,6 +162,70 @@ static FelicaError felica_listener_process_request( } } +static void felica_listener_populate_polling_response_header( + FelicaListener* instance, + FelicaListenerPollingResponseHeader* resp) { + resp->idm = instance->data->idm; + resp->pmm = instance->data->pmm; + resp->response_code = FELICA_LISTENER_RESPONSE_POLLING; +} + +static bool felica_listener_check_system_code( + const FelicaListenerGenericRequest* const generic_request, + uint16_t code) { + return ( + generic_request->polling.system_code == code || + generic_request->polling.system_code == (code | 0x00FFU) || + generic_request->polling.system_code == (code | 0xFF00U)); +} + +static uint16_t felica_listener_get_response_system_code( + FelicaListener* instance, + const FelicaListenerGenericRequest* const generic_request) { + uint16_t resp_system_code = FELICA_SYSTEM_CODE_CODE; + if(felica_listener_check_system_code(generic_request, FELICA_LISTENER_SYSTEM_CODE_NDEF) && + instance->data->data.fs.mc.data[FELICA_MC_SYS_OP] == 1) { + // NDEF + resp_system_code = FELICA_LISTENER_SYSTEM_CODE_NDEF; + } else if(felica_listener_check_system_code( + generic_request, FELICA_LISTENER_SYSTEM_CODE_LITES)) { + // Lite-S + resp_system_code = FELICA_LISTENER_SYSTEM_CODE_LITES; + } + return resp_system_code; +} + +static FelicaError felica_listener_process_system_code( + FelicaListener* instance, + const FelicaListenerGenericRequest* const generic_request) { + FelicaError result = FelicaErrorFeatureUnsupported; + do { + uint16_t resp_system_code = + felica_listener_get_response_system_code(instance, generic_request); + if(resp_system_code == FELICA_SYSTEM_CODE_CODE) break; + + FelicaListenerPollingResponse* resp = malloc(sizeof(FelicaListenerPollingResponse)); + felica_listener_populate_polling_response_header(instance, &resp->header); + + resp->header.length = sizeof(FelicaListenerPollingResponse); + if(generic_request->polling.request_code == FELICA_LISTENER_REQUEST_SYSTEM_CODE) { + resp->optional_request_data = resp_system_code; + } else if(generic_request->polling.request_code == FELICA_LISTENER_REQUEST_PERFORMANCE) { + resp->optional_request_data = FELICA_LISTENER_PERFORMANCE_VALUE; + } else { + resp->header.length = sizeof(FelicaListenerPollingResponseHeader); + } + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)resp, resp->header.length); + free(resp); + + result = felica_listener_frame_exchange(instance, instance->tx_buffer); + } while(false); + + return result; +} + NfcCommand felica_listener_run(NfcGenericEvent event, void* context) { furi_assert(context); furi_assert(event.protocol == NfcProtocolInvalid); @@ -187,7 +262,23 @@ NfcCommand felica_listener_run(NfcGenericEvent event, void* context) { break; } - if(!felica_listener_check_idm(instance, &request->header.idm)) { + if(request->header.code == FELICA_LISTENER_CMD_POLLING) { + // Will always respond at Time Slot 0 for now. + nfc_felica_listener_timer_anticol_start(instance->nfc, 0); + if(request->polling.system_code != FELICA_SYSTEM_CODE_CODE) { + FelicaError error = felica_listener_process_system_code(instance, request); + if(error == FelicaErrorFeatureUnsupported) { + command = NfcCommandReset; + } else if(error != FelicaErrorNone) { + FURI_LOG_E( + TAG, "Error when handling Polling with System Code: %2X", error); + } + break; + } else { + FURI_LOG_E(TAG, "Hardware Polling command leaking through"); + break; + } + } else if(!felica_listener_check_idm(instance, &request->header.idm)) { FURI_LOG_E(TAG, "Wrong IDm"); break; } diff --git a/lib/nfc/protocols/felica/felica_listener_i.c b/lib/nfc/protocols/felica/felica_listener_i.c index b3ae24635..d69c8c66a 100644 --- a/lib/nfc/protocols/felica/felica_listener_i.c +++ b/lib/nfc/protocols/felica/felica_listener_i.c @@ -7,19 +7,6 @@ #define FELICA_WCNT_MC2_00_WARNING_BEGIN_VALUE (0x00001027U) #define FELICA_WCNT_MC2_00_WARNING_END_VALUE (0x00FFFDFFU) -#define FELICA_MC_SP_REG_ALL_RW_BYTES_0_1 (0U) -#define FELICA_MC_ALL_BYTE (2U) -#define FELICA_MC_SYS_OP (3U) -#define FELICA_MC_RF_PRM (4U) -#define FELICA_MC_CKCKV_W_MAC_A (5U) -#define FELICA_MC_SP_REG_R_RESTR_BYTES_6_7 (6U) -#define FELICA_MC_SP_REG_W_RESTR_BYTES_8_9 (8U) -#define FELICA_MC_SP_REG_W_MAC_A_BYTES_10_11 (10U) -#define FELICA_MC_STATE_W_MAC_A (12U) -#define FELICA_MC_RESERVED_13 (13U) -#define FELICA_MC_RESERVED_14 (14U) -#define FELICA_MC_RESERVED_15 (15U) - #define FELICA_MC_BYTE_GET(data, byte) (data->data.fs.mc.data[byte]) #define FELICA_SYSTEM_BLOCK_RO_ACCESS(data) (FELICA_MC_BYTE_GET(data, FELICA_MC_ALL_BYTE) == 0x00) #define FELICA_SYSTEM_BLOCK_RW_ACCESS(data) (FELICA_MC_BYTE_GET(data, FELICA_MC_ALL_BYTE) == 0xFF) diff --git a/lib/nfc/protocols/felica/felica_listener_i.h b/lib/nfc/protocols/felica/felica_listener_i.h index 0ea144249..f64a598cc 100644 --- a/lib/nfc/protocols/felica/felica_listener_i.h +++ b/lib/nfc/protocols/felica/felica_listener_i.h @@ -7,15 +7,50 @@ #define FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX (2U) #define FELICA_LISTENER_WRITE_BLOCK_COUNT_MIN (1U) +#define FELICA_MC_SP_REG_ALL_RW_BYTES_0_1 (0U) +#define FELICA_MC_ALL_BYTE (2U) +#define FELICA_MC_SYS_OP (3U) +#define FELICA_MC_RF_PRM (4U) +#define FELICA_MC_CKCKV_W_MAC_A (5U) +#define FELICA_MC_SP_REG_R_RESTR_BYTES_6_7 (6U) +#define FELICA_MC_SP_REG_W_RESTR_BYTES_8_9 (8U) +#define FELICA_MC_SP_REG_W_MAC_A_BYTES_10_11 (10U) +#define FELICA_MC_STATE_W_MAC_A (12U) +#define FELICA_MC_RESERVED_13 (13U) +#define FELICA_MC_RESERVED_14 (14U) +#define FELICA_MC_RESERVED_15 (15U) + typedef enum { Felica_ListenerStateIdle, Felica_ListenerStateActivated, } FelicaListenerState; +typedef struct FURI_PACKED { + uint8_t code; + uint16_t system_code; + uint8_t request_code; + uint8_t time_slot; +} FelicaListenerPollingHeader; + +typedef struct { + uint8_t length; + uint8_t response_code; + FelicaIDm idm; + FelicaPMm pmm; +} FelicaListenerPollingResponseHeader; + +typedef struct FURI_PACKED { + FelicaListenerPollingResponseHeader header; + uint16_t optional_request_data; +} FelicaListenerPollingResponse; + /** Generic Felica request same for both read and write operations. */ typedef struct { uint8_t length; - FelicaCommandHeader header; + union { + FelicaCommandHeader header; + FelicaListenerPollingHeader polling; + }; } FelicaListenerGenericRequest; /** Generic request but with list of requested elements. */ diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 23c9edb63..fbbb5d13f 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,86.0,, +Version,+,86.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index d142a6374..f2b13e4a9 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,86.0,, +Version,+,86.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -2859,6 +2859,8 @@ Function,+,nfc_device_set_data,void,"NfcDevice*, NfcProtocol, const NfcDeviceDat Function,+,nfc_device_set_loading_callback,void,"NfcDevice*, NfcLoadingCallback, void*" Function,+,nfc_device_set_uid,_Bool,"NfcDevice*, const uint8_t*, size_t" Function,+,nfc_felica_listener_set_sensf_res_data,NfcError,"Nfc*, const uint8_t*, const uint8_t, const uint8_t*, const uint8_t, const uint16_t" +Function,+,nfc_felica_listener_timer_anticol_start,void,"Nfc*, uint8_t" +Function,+,nfc_felica_listener_timer_anticol_stop,void,Nfc* Function,+,nfc_free,void,Nfc* Function,+,nfc_iso14443a_listener_set_col_res_data,NfcError,"Nfc*, uint8_t*, uint8_t, uint8_t*, uint8_t" Function,+,nfc_iso14443a_listener_tx_custom_parity,NfcError,"Nfc*, const BitBuffer*" diff --git a/targets/f7/furi_hal/furi_hal_nfc_felica.c b/targets/f7/furi_hal/furi_hal_nfc_felica.c index 1267b7b13..49c59eb79 100644 --- a/targets/f7/furi_hal/furi_hal_nfc_felica.c +++ b/targets/f7/furi_hal/furi_hal_nfc_felica.c @@ -1,8 +1,7 @@ #include "furi_hal_nfc_i.h" #include "furi_hal_nfc_tech_i.h" -// Prevent FDT timer from starting -#define FURI_HAL_NFC_FELICA_LISTENER_FDT_COMP_FC (INT32_MAX) +#define FURI_HAL_NFC_FELICA_LISTENER_FDT_COMP_FC (0) #define FURI_HAL_FELICA_COMMUNICATION_PERFORMANCE (0x0083U) #define FURI_HAL_FELICA_RESPONSE_CODE (0x01) From 7f0e8f39d37f1ae858cbdae9e9f1b1c2a4e0d409 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Wed, 24 Sep 2025 12:24:09 +0200 Subject: [PATCH 068/192] FuriHalSerial: Fix RXFNE interrupt hang (#4246) * Expansion: Wake thread on UART error flag * Expansion: Stop UART async rx early * FuriHalSerial: Fix RXFNE interrupt hang --------- Co-authored-by: hedger --- .../services/expansion/expansion_worker.c | 5 +- targets/f7/furi_hal/furi_hal_serial.c | 67 +++++++++++-------- 2 files changed, 43 insertions(+), 29 deletions(-) diff --git a/applications/services/expansion/expansion_worker.c b/applications/services/expansion/expansion_worker.c index ac2a5935b..703a72607 100644 --- a/applications/services/expansion/expansion_worker.c +++ b/applications/services/expansion/expansion_worker.c @@ -35,7 +35,8 @@ typedef enum { ExpansionWorkerFlagError = 1 << 2, } ExpansionWorkerFlag; -#define EXPANSION_ALL_FLAGS (ExpansionWorkerFlagData | ExpansionWorkerFlagStop) +#define EXPANSION_ALL_FLAGS \ + (ExpansionWorkerFlagData | ExpansionWorkerFlagStop | ExpansionWorkerFlagError) struct ExpansionWorker { FuriThread* thread; @@ -360,6 +361,8 @@ static int32_t expansion_worker(void* context) { expansion_worker_state_machine(instance); } + furi_hal_serial_async_rx_stop(instance->serial_handle); + if(instance->state == ExpansionWorkerStateRpcActive) { expansion_worker_rpc_session_close(instance); } diff --git a/targets/f7/furi_hal/furi_hal_serial.c b/targets/f7/furi_hal/furi_hal_serial.c index 8ad9794a8..e5c30a278 100644 --- a/targets/f7/furi_hal/furi_hal_serial.c +++ b/targets/f7/furi_hal/furi_hal_serial.c @@ -817,6 +817,21 @@ static void furi_hal_serial_async_rx_configure( FuriHalSerialHandle* handle, FuriHalSerialAsyncRxCallback callback, void* context) { + // Disable RXFNE interrupts before unsetting the user callback that reads data + // Otherwise interrupt runs without reading data and without clearing RXFNE flag + // This would cause a system hang as the same interrupt runs in loop forever + if(!callback) { + if(handle->id == FuriHalSerialIdUsart) { + LL_USART_DisableIT_RXNE_RXFNE(USART1); + furi_hal_interrupt_set_isr(FuriHalInterruptIdUart1, NULL, NULL); + furi_hal_serial_usart_deinit_dma_rx(); + } else if(handle->id == FuriHalSerialIdLpuart) { + LL_LPUART_DisableIT_RXNE_RXFNE(LPUART1); + furi_hal_interrupt_set_isr(FuriHalInterruptIdLpUart1, NULL, NULL); + furi_hal_serial_lpuart_deinit_dma_rx(); + } + } + // Handle must be configured before enabling RX interrupt // as it might be triggered right away on a misconfigured handle furi_hal_serial[handle->id].rx_byte_callback = callback; @@ -824,27 +839,17 @@ static void furi_hal_serial_async_rx_configure( furi_hal_serial[handle->id].rx_dma_callback = NULL; furi_hal_serial[handle->id].context = context; - if(handle->id == FuriHalSerialIdUsart) { - if(callback) { + if(callback) { + if(handle->id == FuriHalSerialIdUsart) { furi_hal_serial_usart_deinit_dma_rx(); furi_hal_interrupt_set_isr( FuriHalInterruptIdUart1, furi_hal_serial_usart_irq_callback, NULL); LL_USART_EnableIT_RXNE_RXFNE(USART1); - } else { - furi_hal_interrupt_set_isr(FuriHalInterruptIdUart1, NULL, NULL); - furi_hal_serial_usart_deinit_dma_rx(); - LL_USART_DisableIT_RXNE_RXFNE(USART1); - } - } else if(handle->id == FuriHalSerialIdLpuart) { - if(callback) { + } else if(handle->id == FuriHalSerialIdLpuart) { furi_hal_serial_lpuart_deinit_dma_rx(); furi_hal_interrupt_set_isr( FuriHalInterruptIdLpUart1, furi_hal_serial_lpuart_irq_callback, NULL); LL_LPUART_EnableIT_RXNE_RXFNE(LPUART1); - } else { - furi_hal_interrupt_set_isr(FuriHalInterruptIdLpUart1, NULL, NULL); - furi_hal_serial_lpuart_deinit_dma_rx(); - LL_LPUART_DisableIT_RXNE_RXFNE(LPUART1); } } } @@ -944,33 +949,39 @@ static void furi_hal_serial_dma_configure( FuriHalSerialHandle* handle, FuriHalSerialDmaRxCallback callback, void* context) { - furi_check(handle); - - if(handle->id == FuriHalSerialIdUsart) { - if(callback) { - furi_hal_serial_usart_init_dma_rx(); - furi_hal_interrupt_set_isr( - FuriHalInterruptIdUart1, furi_hal_serial_usart_irq_callback, NULL); - } else { + // Disable RXFNE interrupts before unsetting the user callback that reads data + // Otherwise interrupt runs without reading data and without clearing RXFNE flag + // This would cause a system hang as the same interrupt runs in loop forever + if(!callback) { + if(handle->id == FuriHalSerialIdUsart) { LL_USART_DisableIT_RXNE_RXFNE(USART1); furi_hal_interrupt_set_isr(FuriHalInterruptIdUart1, NULL, NULL); furi_hal_serial_usart_deinit_dma_rx(); - } - } else if(handle->id == FuriHalSerialIdLpuart) { - if(callback) { - furi_hal_serial_lpuart_init_dma_rx(); - furi_hal_interrupt_set_isr( - FuriHalInterruptIdLpUart1, furi_hal_serial_lpuart_irq_callback, NULL); - } else { + } else if(handle->id == FuriHalSerialIdLpuart) { LL_LPUART_DisableIT_RXNE_RXFNE(LPUART1); furi_hal_interrupt_set_isr(FuriHalInterruptIdLpUart1, NULL, NULL); furi_hal_serial_lpuart_deinit_dma_rx(); } } + + // Handle must be configured before enabling RX interrupt + // as it might be triggered right away on a misconfigured handle furi_hal_serial[handle->id].rx_byte_callback = NULL; furi_hal_serial[handle->id].handle = handle; furi_hal_serial[handle->id].rx_dma_callback = callback; furi_hal_serial[handle->id].context = context; + + if(callback) { + if(handle->id == FuriHalSerialIdUsart) { + furi_hal_serial_usart_init_dma_rx(); + furi_hal_interrupt_set_isr( + FuriHalInterruptIdUart1, furi_hal_serial_usart_irq_callback, NULL); + } else if(handle->id == FuriHalSerialIdLpuart) { + furi_hal_serial_lpuart_init_dma_rx(); + furi_hal_interrupt_set_isr( + FuriHalInterruptIdLpUart1, furi_hal_serial_lpuart_irq_callback, NULL); + } + } } void furi_hal_serial_dma_rx_start( From 6a49131ae96d48fd4581e9851911c40269192f44 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Wed, 24 Sep 2025 14:34:28 +0400 Subject: [PATCH 069/192] js_gpio: stop pwm on exit (#4206) Co-authored-by: hedger --- applications/system/js_app/modules/js_gpio.c | 33 ++++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/applications/system/js_app/modules/js_gpio.c b/applications/system/js_app/modules/js_gpio.c index 63de6900a..df7d494a6 100644 --- a/applications/system/js_app/modules/js_gpio.c +++ b/applications/system/js_app/modules/js_gpio.c @@ -23,6 +23,7 @@ typedef struct { } JsGpioPinInst; ARRAY_DEF(ManagedPinsArray, JsGpioPinInst*, M_PTR_OPLIST); //-V575 +#define M_OPL_ManagedPinsArray_t() ARRAY_OPLIST(ManagedPinsArray) /** * Per-module instance control structure @@ -444,20 +445,26 @@ static void js_gpio_destroy(void* inst) { JsGpioInst* module = (JsGpioInst*)inst; // reset pins - ManagedPinsArray_it_t iterator; - for(ManagedPinsArray_it(iterator, module->managed_pins); !ManagedPinsArray_end_p(iterator); - ManagedPinsArray_next(iterator)) { - JsGpioPinInst* manager_data = *ManagedPinsArray_cref(iterator); - if(manager_data->had_interrupt) { - furi_hal_gpio_disable_int_callback(manager_data->pin); - furi_hal_gpio_remove_int_callback(manager_data->pin); + for + M_EACH(item, module->managed_pins, ManagedPinsArray_t) { + JsGpioPinInst* manager_data = *item; + + if(manager_data->had_interrupt) { + furi_hal_gpio_disable_int_callback(manager_data->pin); + furi_hal_gpio_remove_int_callback(manager_data->pin); + } + if(manager_data->pwm_output != FuriHalPwmOutputIdNone) { + if(furi_hal_pwm_is_running(manager_data->pwm_output)) + furi_hal_pwm_stop(manager_data->pwm_output); + } + furi_hal_gpio_init(manager_data->pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + + furi_event_loop_maybe_unsubscribe(module->loop, manager_data->interrupt_semaphore); + furi_semaphore_free(manager_data->interrupt_semaphore); + + free(manager_data->interrupt_contract); + free(manager_data); } - furi_hal_gpio_init(manager_data->pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_event_loop_maybe_unsubscribe(module->loop, manager_data->interrupt_semaphore); - furi_semaphore_free(manager_data->interrupt_semaphore); - free(manager_data->interrupt_contract); - free(manager_data); - } // free buffers furi_hal_adc_release(module->adc_handle); From 7554b325382628eac47972e232397e461c7fe362 Mon Sep 17 00:00:00 2001 From: MMX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 24 Sep 2025 13:45:03 +0300 Subject: [PATCH 070/192] SubGHz: Linear and Dickert MAHS protocols fixes & improvements (#4267) * Linear and dickert mahs fixes * fix dip pattern * proper fix by WillyJL * Fix unit tests Linear Decoder now decodes more linear signals --------- Co-authored-by: hedger --- .../unit_tests/tests/subghz/subghz_test.c | 2 +- lib/subghz/protocols/dickert_mahs.c | 5 ++-- lib/subghz/protocols/linear.c | 29 ++++++++++--------- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/applications/debug/unit_tests/tests/subghz/subghz_test.c b/applications/debug/unit_tests/tests/subghz/subghz_test.c index ac14bce6a..a58875fe8 100644 --- a/applications/debug/unit_tests/tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/tests/subghz/subghz_test.c @@ -17,7 +17,7 @@ #define NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s") #define ALUTECH_AT_4N_DIR_NAME EXT_PATH("subghz/assets/alutech_at_4n") #define TEST_RANDOM_DIR_NAME EXT_PATH("unit_tests/subghz/test_random_raw.sub") -#define TEST_RANDOM_COUNT_PARSE 329 +#define TEST_RANDOM_COUNT_PARSE 331 #define TEST_TIMEOUT 10000 static SubGhzEnvironment* environment_handler; diff --git a/lib/subghz/protocols/dickert_mahs.c b/lib/subghz/protocols/dickert_mahs.c index 4691e3423..3f614412b 100644 --- a/lib/subghz/protocols/dickert_mahs.c +++ b/lib/subghz/protocols/dickert_mahs.c @@ -288,8 +288,9 @@ void subghz_protocol_decoder_dickert_mahs_feed(void* context, bool level, uint32 instance->decoder.decode_count_bit = 0; } - if((!level) && (duration > 10 * subghz_protocol_dickert_mahs_const.te_short)) { - //Found header DICKERT_MAHS + if((!level) && (DURATION_DIFF(duration, subghz_protocol_dickert_mahs_const.te_long * 50) < + subghz_protocol_dickert_mahs_const.te_delta * 70)) { + //Found header DICKERT_MAHS 44k us instance->decoder.parser_step = DickertMAHSDecoderStepInitial; } break; diff --git a/lib/subghz/protocols/linear.c b/lib/subghz/protocols/linear.c index f9d7aaca8..7671dc428 100644 --- a/lib/subghz/protocols/linear.c +++ b/lib/subghz/protocols/linear.c @@ -18,7 +18,7 @@ static const SubGhzBlockConst subghz_protocol_linear_const = { .te_short = 500, .te_long = 1500, - .te_delta = 150, + .te_delta = 350, .min_count_bit_for_found = 10, }; @@ -116,7 +116,7 @@ static bool subghz_protocol_encoder_linear_get_upload(SubGhzProtocolEncoderLinea if(bit_read(instance->generic.data, i - 1)) { //send bit 1 instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_short * 3); + level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_long); instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)subghz_protocol_linear_const.te_short); } else { @@ -124,22 +124,22 @@ static bool subghz_protocol_encoder_linear_get_upload(SubGhzProtocolEncoderLinea instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_short); instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)subghz_protocol_linear_const.te_short * 3); + level_duration_make(false, (uint32_t)subghz_protocol_linear_const.te_long); } } //Send end bit if(bit_read(instance->generic.data, 0)) { //send bit 1 instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_short * 3); - //Send PT_GUARD + level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_long); + //Send gap instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)subghz_protocol_linear_const.te_short * 42); } else { //send bit 0 instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)subghz_protocol_linear_const.te_short); - //Send PT_GUARD + //Send gap instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)subghz_protocol_linear_const.te_short * 44); } @@ -223,7 +223,7 @@ void subghz_protocol_decoder_linear_feed(void* context, bool level, uint32_t dur switch(instance->decoder.parser_step) { case LinearDecoderStepReset: if((!level) && (DURATION_DIFF(duration, subghz_protocol_linear_const.te_short * 42) < - subghz_protocol_linear_const.te_delta * 20)) { + subghz_protocol_linear_const.te_delta * 15)) { //Found header Linear instance->decoder.decode_data = 0; instance->decoder.decode_count_bit = 0; @@ -244,7 +244,7 @@ void subghz_protocol_decoder_linear_feed(void* context, bool level, uint32_t dur instance->decoder.parser_step = LinearDecoderStepReset; //checking that the duration matches the guardtime if(DURATION_DIFF(duration, subghz_protocol_linear_const.te_short * 42) > - subghz_protocol_linear_const.te_delta * 20) { + subghz_protocol_linear_const.te_delta * 15) { break; } if(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_linear_const.te_short) < @@ -321,18 +321,21 @@ void subghz_protocol_decoder_linear_get_string(void* context, FuriString* output furi_assert(context); SubGhzProtocolDecoderLinear* instance = context; - uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + // Protocol is actually implemented wrong way around, bits are inverted. + // Instead of fixing it and breaking old saved remotes, + // only the display here is inverted (~) to show correct values. + uint32_t code_found_lo = ~instance->generic.data & 0x00000000000003ff; uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( - instance->generic.data, instance->generic.data_count_bit); + ~instance->generic.data, instance->generic.data_count_bit); - uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; + uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000000003ff; furi_string_cat_printf( output, "%s %dbit\r\n" - "Key:0x%08lX\r\n" - "Yek:0x%08lX\r\n" + "Key:0x%03lX\r\n" + "Yek:0x%03lX\r\n" "DIP:" DIP_PATTERN "\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, From 7483069d1049d1208d624fed254b34123a59ee95 Mon Sep 17 00:00:00 2001 From: LordMZTE Date: Wed, 24 Sep 2025 16:52:06 +0200 Subject: [PATCH 071/192] hid_app mouse clicker: make mouse button selectable (#4270) * hid_app mouse clicker: make mouse button selectable * hid_app: fixed uninit var warning --------- Co-authored-by: hedger Co-authored-by: hedger --- .../system/hid_app/views/hid_mouse_clicker.c | 58 ++++++++++++++++++- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/applications/system/hid_app/views/hid_mouse_clicker.c b/applications/system/hid_app/views/hid_mouse_clicker.c index 491f9962b..e9fd9170d 100644 --- a/applications/system/hid_app/views/hid_mouse_clicker.c +++ b/applications/system/hid_app/views/hid_mouse_clicker.c @@ -19,6 +19,7 @@ typedef struct { bool connected; bool running; int rate; + enum HidMouseButtons btn; } HidMouseClickerModel; static void hid_mouse_clicker_start_or_restart_timer(void* context) { @@ -61,6 +62,26 @@ static void hid_mouse_clicker_draw_callback(Canvas* canvas, void* context) { // Ok canvas_draw_icon(canvas, 58, 25, &I_Space_65x18); + canvas_draw_icon(canvas, 61, 50, &I_ButtonLeft_4x7); + canvas_draw_icon(canvas, 117, 50, &I_ButtonRight_4x7); + + const char* btn_label; + switch(model->btn) { + case HID_MOUSE_BTN_LEFT: + btn_label = "Left"; + break; + case HID_MOUSE_BTN_WHEEL: + btn_label = "Middle"; + break; + case HID_MOUSE_BTN_RIGHT: + btn_label = "Right"; + break; + default: + furi_crash(); + } + + elements_multiline_text_aligned(canvas, 89, 57, AlignCenter, AlignBottom, btn_label); + if(model->running) { elements_slightly_rounded_box(canvas, 61, 27, 60, 13); canvas_set_color(canvas, ColorWhite); @@ -100,8 +121,8 @@ static void hid_mouse_clicker_timer_callback(void* context) { HidMouseClickerModel * model, { if(model->running) { - hid_hal_mouse_press(hid_mouse_clicker->hid, HID_MOUSE_BTN_LEFT); - hid_hal_mouse_release(hid_mouse_clicker->hid, HID_MOUSE_BTN_LEFT); + hid_hal_mouse_press(hid_mouse_clicker->hid, model->btn); + hid_hal_mouse_release(hid_mouse_clicker->hid, model->btn); } }, false); @@ -154,6 +175,34 @@ static bool hid_mouse_clicker_input_callback(InputEvent* event, void* context) { case InputKeyBack: model->running = false; break; + case InputKeyLeft: + switch(model->btn) { + case HID_MOUSE_BTN_LEFT: + model->btn = HID_MOUSE_BTN_RIGHT; + break; + case HID_MOUSE_BTN_WHEEL: + model->btn = HID_MOUSE_BTN_LEFT; + break; + case HID_MOUSE_BTN_RIGHT: + model->btn = HID_MOUSE_BTN_WHEEL; + break; + } + consumed = true; + break; + case InputKeyRight: + switch(model->btn) { + case HID_MOUSE_BTN_LEFT: + model->btn = HID_MOUSE_BTN_WHEEL; + break; + case HID_MOUSE_BTN_WHEEL: + model->btn = HID_MOUSE_BTN_RIGHT; + break; + case HID_MOUSE_BTN_RIGHT: + model->btn = HID_MOUSE_BTN_LEFT; + break; + } + consumed = true; + break; default: consumed = true; break; @@ -188,7 +237,10 @@ HidMouseClicker* hid_mouse_clicker_alloc(Hid* hid) { with_view_model( hid_mouse_clicker->view, HidMouseClickerModel * model, - { model->rate = DEFAULT_CLICK_RATE; }, + { + model->rate = DEFAULT_CLICK_RATE; + model->btn = HID_MOUSE_BTN_LEFT; + }, true); return hid_mouse_clicker; From f0d4210d19f3637c79aa0a010b098d7aa390202a Mon Sep 17 00:00:00 2001 From: Zinong Li <131403964+zinongli@users.noreply.github.com> Date: Wed, 24 Sep 2025 19:14:43 +0400 Subject: [PATCH 072/192] NFC: Amusement IC Card Parser (FeliCa Lite & Lite-S) (#4259) * type/brand parse * debug * decryptions * corrections * comments * AIC access code decryption and CRC check * easier than we thought! only acc_code[0] == 5 needs parsing * finishing touches * can't support serial number decode * Revert "can't support serial number decode" This reverts commit 7761b31bcf7effb97e608578ab1e090625078784. * serial number CAN be decrypted * format * print fix * fix CRC check * delete unnecessary line * linter fixes --------- Co-authored-by: hedger Co-authored-by: hedger --- applications/main/nfc/application.fam | 9 + .../main/nfc/plugins/supported_cards/aic.c | 678 ++++++++++++++++++ 2 files changed, 687 insertions(+) create mode 100644 applications/main/nfc/plugins/supported_cards/aic.c diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 6342d18bf..a9c8808a8 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -274,3 +274,12 @@ App( requires=["nfc"], sources=["plugins/supported_cards/banapass.c"], ) + +App( + appid="aic_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="aic_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/aic.c"], +) diff --git a/applications/main/nfc/plugins/supported_cards/aic.c b/applications/main/nfc/plugins/supported_cards/aic.c new file mode 100644 index 000000000..c8f303eb9 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/aic.c @@ -0,0 +1,678 @@ +// https://sega.bsnk.me/allnet/amusement_ic/ + +#include "nfc_supported_card_plugin.h" +#include +#include +#include +#include +#include +#include + +#define TAG "Amusement IC" + +#define N_TABLES 8 +#define ITERATION_ADD 5 + +static const uint8_t s_box[9][256] = { + {0x24, 0x3c, 0xba, 0x36, 0xe3, 0x85, 0xa4, 0xd0, 0x93, 0x43, 0x73, 0xb9, 0x70, 0x6e, 0xc9, + 0xf1, 0x10, 0x0e, 0x9b, 0x2c, 0x97, 0xe7, 0x0b, 0x63, 0x6c, 0x29, 0x20, 0xfe, 0x86, 0xf3, + 0xe1, 0xf5, 0xf6, 0x9f, 0xb6, 0x16, 0x04, 0x7b, 0x8f, 0xab, 0xb1, 0x39, 0x2a, 0x1b, 0xeb, + 0x5c, 0xa8, 0xac, 0x38, 0x11, 0x12, 0x5f, 0x89, 0x3e, 0x7d, 0xca, 0xec, 0x53, 0xdb, 0x6d, + 0x1e, 0xd2, 0x81, 0x78, 0x96, 0x46, 0xff, 0xf9, 0x54, 0x1c, 0x28, 0x7a, 0x4f, 0xd3, 0xc0, + 0xdc, 0xc1, 0x6a, 0xf2, 0xbc, 0xcb, 0x57, 0xfd, 0x4a, 0xe4, 0xf0, 0xb2, 0xc7, 0x95, 0x40, + 0x62, 0x52, 0x41, 0xe2, 0xad, 0x49, 0xa6, 0xb5, 0x1f, 0x02, 0xc8, 0xda, 0x92, 0xe5, 0xb0, + 0xc5, 0x64, 0x76, 0x48, 0x21, 0xde, 0x0f, 0x45, 0x58, 0x4c, 0xdf, 0xa7, 0x84, 0xcf, 0xd5, + 0x15, 0x4e, 0x27, 0x80, 0x6b, 0x7f, 0xfc, 0x44, 0x71, 0x47, 0x22, 0xdd, 0x30, 0x0c, 0xa1, + 0x3b, 0xe0, 0x37, 0xa0, 0x35, 0x23, 0x90, 0x32, 0x74, 0xbf, 0x8d, 0xc2, 0xea, 0xd6, 0x50, + 0xbb, 0xd9, 0xc4, 0x83, 0xb4, 0x31, 0x68, 0x55, 0x8b, 0x5d, 0xf4, 0x72, 0x18, 0xb7, 0xef, + 0xf7, 0x98, 0x2d, 0x01, 0x03, 0x61, 0xcc, 0x0a, 0x8e, 0x13, 0x00, 0xe8, 0x14, 0xaf, 0x09, + 0x51, 0x75, 0xa5, 0x2f, 0x1d, 0x0d, 0x65, 0x8a, 0xcd, 0x66, 0x07, 0x3d, 0x05, 0xd8, 0x4b, + 0xe9, 0x9d, 0x99, 0x7c, 0x91, 0xd1, 0xb8, 0x19, 0xc3, 0x2b, 0x42, 0x69, 0x88, 0xc6, 0x79, + 0x17, 0x3a, 0x4d, 0x5b, 0xa2, 0x5a, 0xfb, 0x25, 0x3f, 0xbd, 0xf8, 0xed, 0xce, 0xe6, 0x87, + 0xa3, 0x26, 0xa9, 0x2e, 0xbe, 0x94, 0x08, 0x7e, 0x67, 0x60, 0x8c, 0x9c, 0x5e, 0x6f, 0xb3, + 0x9e, 0x06, 0xfa, 0x82, 0xae, 0xee, 0x59, 0x77, 0xd7, 0x1a, 0x9a, 0x34, 0xd4, 0x56, 0xaa, + 0x33}, + {0xf9, 0x81, 0x7c, 0x00, 0xb9, 0x30, 0x37, 0xd5, 0x90, 0x51, 0x6e, 0xf0, 0xb2, 0x06, 0xfb, + 0xcd, 0x39, 0x14, 0x5f, 0xf8, 0x1b, 0xc7, 0x4a, 0x82, 0x70, 0x8c, 0x92, 0x1d, 0xea, 0xc3, + 0x4e, 0xc8, 0x12, 0xe6, 0xb6, 0x10, 0xd3, 0x2f, 0x95, 0x84, 0x25, 0x42, 0xa5, 0x72, 0xe5, + 0x8f, 0x55, 0xef, 0x86, 0xa2, 0x53, 0xae, 0xed, 0x26, 0x20, 0x47, 0xde, 0x78, 0x68, 0x28, + 0xe4, 0x45, 0xcc, 0x35, 0xbc, 0x3e, 0xbb, 0x8e, 0xc0, 0xbe, 0x34, 0xaf, 0xa6, 0x09, 0x64, + 0x01, 0x7b, 0x44, 0xf5, 0xf3, 0xb1, 0x3f, 0xa4, 0xb3, 0x32, 0x80, 0xc9, 0x4f, 0x6f, 0x40, + 0xbf, 0x21, 0x4c, 0x74, 0xe1, 0x11, 0x5e, 0xeb, 0x3d, 0x71, 0x2e, 0x9d, 0x62, 0x75, 0xd4, + 0x16, 0x48, 0x77, 0x13, 0x67, 0xad, 0x6b, 0x5d, 0x07, 0x87, 0xf7, 0xa8, 0x9a, 0x59, 0x76, + 0x8b, 0x9b, 0x1e, 0xd8, 0xee, 0xaa, 0xe9, 0x99, 0x2d, 0xc5, 0x97, 0xf1, 0xa7, 0x83, 0xfc, + 0xca, 0xdc, 0xba, 0xb8, 0x4b, 0xe8, 0x89, 0x17, 0x05, 0x0b, 0xa0, 0x65, 0x23, 0xd1, 0xce, + 0x36, 0x03, 0xd7, 0xe0, 0xf2, 0x91, 0xdf, 0x0e, 0x9c, 0x2c, 0x0d, 0x66, 0xd6, 0x73, 0x58, + 0x5c, 0xb4, 0x0a, 0x4d, 0xc2, 0x3b, 0xd2, 0xb7, 0xe2, 0xac, 0x33, 0xdb, 0x60, 0x27, 0xbd, + 0x56, 0x43, 0x24, 0x04, 0x02, 0x8d, 0x7d, 0x7f, 0xf6, 0xb5, 0xf4, 0xfd, 0xdd, 0x5b, 0xec, + 0x79, 0xc4, 0x22, 0x1f, 0xa1, 0x88, 0x54, 0xe7, 0x19, 0x98, 0x94, 0x7e, 0x31, 0xa3, 0x29, + 0xe3, 0x5a, 0xcb, 0x6a, 0xb0, 0xcf, 0x6d, 0x93, 0x6c, 0x7a, 0x08, 0xa9, 0x3c, 0x1c, 0xd0, + 0x63, 0x50, 0xc6, 0x85, 0x38, 0xc1, 0x41, 0x49, 0xab, 0x61, 0x52, 0x2a, 0x9f, 0xd9, 0x18, + 0x1a, 0x57, 0x46, 0x2b, 0x69, 0xda, 0xfa, 0xff, 0x0f, 0x15, 0x96, 0x3a, 0x8a, 0xfe, 0x9e, + 0x0c}, + {0x11, 0xa2, 0x9a, 0xc3, 0x94, 0xa0, 0x51, 0x01, 0x5b, 0xf7, 0xd1, 0x30, 0xee, 0x1f, 0x06, + 0xd5, 0x77, 0x67, 0xd3, 0xc1, 0x6e, 0xe7, 0x6a, 0x1a, 0xa4, 0x8e, 0x9f, 0x65, 0x66, 0xd6, + 0x0c, 0x2d, 0xc6, 0xe4, 0x69, 0xb7, 0x56, 0x5a, 0x57, 0x60, 0xfc, 0x79, 0x1e, 0xe1, 0x16, + 0x52, 0xf0, 0x07, 0xd0, 0xcd, 0xca, 0x78, 0xc9, 0x8b, 0xb3, 0x88, 0x27, 0xf6, 0xe0, 0xc0, + 0x84, 0xcf, 0x2f, 0xa3, 0x04, 0x00, 0x80, 0x40, 0xa7, 0xfa, 0x75, 0x9d, 0x4b, 0x89, 0x46, + 0x22, 0x28, 0x37, 0x34, 0x13, 0xff, 0x20, 0xb4, 0xda, 0x08, 0x26, 0x6c, 0x8f, 0xf2, 0x5d, + 0x7b, 0x73, 0x5c, 0x4c, 0x90, 0x71, 0x41, 0x8a, 0x9e, 0xbb, 0x12, 0xe8, 0x55, 0x6d, 0x2a, + 0x25, 0x4a, 0xaf, 0xbd, 0x95, 0x19, 0xa5, 0x31, 0xba, 0xad, 0xd9, 0xc5, 0xa6, 0x6b, 0x83, + 0xc4, 0xb6, 0xa9, 0xcc, 0xf9, 0xa1, 0xb1, 0x7a, 0xdc, 0xc7, 0xdb, 0x2e, 0x7e, 0x7f, 0x8d, + 0x4e, 0x62, 0x0f, 0x03, 0x39, 0xfd, 0xb8, 0xd4, 0x18, 0xc2, 0xec, 0x61, 0x02, 0x53, 0x1c, + 0x3b, 0x7d, 0xbc, 0x1d, 0x59, 0xf5, 0x8c, 0x98, 0xf1, 0x6f, 0x93, 0x85, 0xf8, 0x2c, 0x74, + 0xd8, 0x5e, 0x4d, 0x97, 0xab, 0x87, 0x82, 0x0a, 0x21, 0x42, 0x5f, 0x0d, 0x58, 0x33, 0x44, + 0x3a, 0xdd, 0xe9, 0xfb, 0x50, 0x47, 0xc8, 0xd7, 0x81, 0x9b, 0xe5, 0x1b, 0x17, 0x54, 0x86, + 0x2b, 0xef, 0xf4, 0x29, 0x32, 0x0e, 0x7c, 0x23, 0x15, 0xeb, 0xe6, 0x3c, 0x35, 0xe2, 0x96, + 0x68, 0x3f, 0x0b, 0x43, 0xce, 0xde, 0x9c, 0xbe, 0x4f, 0x45, 0x72, 0x76, 0xb5, 0x10, 0xe3, + 0xdf, 0x92, 0xd2, 0xa8, 0x48, 0x91, 0xb9, 0xac, 0xbf, 0x49, 0xb2, 0x99, 0x64, 0x70, 0xea, + 0xf3, 0x3e, 0x63, 0x14, 0xae, 0xed, 0xaa, 0x24, 0xfe, 0x3d, 0xcb, 0xb0, 0x09, 0x38, 0x36, + 0x05}, + {0xc4, 0xf0, 0x28, 0x49, 0x55, 0x4a, 0xf5, 0xfd, 0x75, 0xaf, 0x20, 0x69, 0xc8, 0x43, 0x86, + 0x6b, 0xc9, 0xa8, 0xc6, 0x54, 0x4c, 0xdd, 0x02, 0x5b, 0xe8, 0x9b, 0x59, 0x77, 0x34, 0xd7, + 0xc0, 0x51, 0x5f, 0xf3, 0x7e, 0xd4, 0xf1, 0x90, 0x81, 0xce, 0x19, 0x8a, 0x78, 0x33, 0xcd, + 0x97, 0x8c, 0xd6, 0x6a, 0x4b, 0x8f, 0xa4, 0x80, 0xdc, 0x1a, 0x17, 0x14, 0xc5, 0x07, 0x87, + 0x66, 0xad, 0x9d, 0x85, 0xb2, 0xf8, 0x8d, 0x98, 0xdf, 0x3e, 0x1f, 0x3f, 0x64, 0x58, 0x40, + 0xac, 0x6c, 0x5c, 0x08, 0x5d, 0x53, 0xba, 0xff, 0xd2, 0xa9, 0x2c, 0x25, 0xab, 0xe4, 0x32, + 0x38, 0x9a, 0xf9, 0xcb, 0xc2, 0x91, 0x67, 0xd0, 0xe3, 0x22, 0x29, 0x2d, 0x92, 0x48, 0xb4, + 0x0e, 0x99, 0x05, 0xa3, 0x6f, 0x0a, 0x15, 0xef, 0xd5, 0x10, 0x4f, 0xd8, 0xf6, 0x00, 0xe6, + 0x46, 0x2b, 0x31, 0x57, 0x83, 0x94, 0xae, 0x03, 0xeb, 0x8e, 0x13, 0x24, 0xa1, 0x1e, 0x5e, + 0xd1, 0x26, 0xd9, 0xa7, 0xca, 0x36, 0x6e, 0x71, 0x37, 0xee, 0xb1, 0xfa, 0x7c, 0x76, 0x06, + 0xb8, 0x23, 0xbe, 0x21, 0xbc, 0x9e, 0xc1, 0xda, 0x7a, 0x3b, 0x62, 0x27, 0x7b, 0xb0, 0xe5, + 0x63, 0x18, 0x82, 0x7f, 0x0f, 0xed, 0xa0, 0x2a, 0x9c, 0xbf, 0x11, 0xbd, 0xe0, 0x88, 0x0d, + 0x3a, 0x79, 0x52, 0x56, 0xf7, 0xb6, 0xd3, 0x09, 0x16, 0x1b, 0x70, 0xb3, 0x42, 0x60, 0x2f, + 0x1c, 0xa2, 0x9f, 0x72, 0x12, 0x45, 0x47, 0xbb, 0xe1, 0x0b, 0x01, 0x8b, 0x1d, 0xde, 0xec, + 0x0c, 0x4e, 0xe9, 0xcf, 0xcc, 0x95, 0x74, 0xf4, 0xa5, 0x93, 0xc7, 0xdb, 0x4d, 0xfb, 0x35, + 0x65, 0xfe, 0xe7, 0x39, 0xb5, 0xea, 0x96, 0xc3, 0x04, 0x41, 0x44, 0x84, 0xfc, 0x6d, 0x30, + 0xe2, 0xf2, 0x68, 0xb7, 0x89, 0x7d, 0x73, 0x3d, 0xb9, 0x5a, 0x50, 0xa6, 0x3c, 0x61, 0xaa, + 0x2e}, + {0x1a, 0x59, 0x9c, 0xad, 0xc8, 0xe4, 0x11, 0x54, 0xed, 0x37, 0x0f, 0x3a, 0xe6, 0x5f, 0x3c, + 0x4b, 0xb8, 0x15, 0x89, 0xb1, 0xe8, 0xda, 0x69, 0x77, 0x91, 0x56, 0x8b, 0xdb, 0x06, 0x24, + 0xcf, 0x18, 0xf8, 0xb0, 0x87, 0xdf, 0x8c, 0x35, 0xcb, 0x86, 0x53, 0x9d, 0xa4, 0x66, 0x4a, + 0x7a, 0x71, 0x0a, 0x48, 0x38, 0xff, 0xdc, 0x83, 0x20, 0xce, 0x98, 0x32, 0xf6, 0xd7, 0xaf, + 0x70, 0x5e, 0x73, 0x8a, 0x14, 0x72, 0x1e, 0xc1, 0x29, 0x79, 0x07, 0x08, 0xe9, 0x43, 0x46, + 0xd0, 0x2f, 0xde, 0x2a, 0x4f, 0x3d, 0x2c, 0x50, 0xb3, 0x75, 0xfc, 0x0b, 0x64, 0xae, 0x31, + 0x7b, 0x61, 0xa5, 0x30, 0xa0, 0x93, 0xd2, 0xbe, 0xc2, 0x55, 0xba, 0x6c, 0xf3, 0x62, 0x68, + 0xfd, 0xac, 0x3b, 0x95, 0x49, 0x1f, 0x6b, 0xb4, 0x85, 0xf7, 0xa6, 0x03, 0xec, 0x6e, 0x9a, + 0x81, 0x09, 0x0c, 0x6a, 0xee, 0x9e, 0x4e, 0xf0, 0xab, 0x2d, 0x7e, 0xa1, 0xe1, 0xbb, 0xc9, + 0xbc, 0x41, 0xf2, 0xfa, 0x2e, 0x1b, 0xdd, 0x27, 0x34, 0xd3, 0x60, 0x04, 0xb5, 0x01, 0xd6, + 0x40, 0xf9, 0xd5, 0x02, 0x47, 0xd9, 0xd8, 0xe0, 0x8f, 0x2b, 0xfb, 0xb7, 0xc5, 0xeb, 0x57, + 0x16, 0xe5, 0x78, 0x8d, 0xf4, 0x00, 0xcd, 0x82, 0x39, 0xc6, 0x96, 0xbd, 0xbf, 0xe3, 0x36, + 0x45, 0x0e, 0x26, 0x1d, 0x63, 0xe7, 0x84, 0x3e, 0xb6, 0x9b, 0x22, 0xa3, 0xf5, 0x8e, 0x23, + 0x6d, 0xc3, 0x99, 0x7d, 0x90, 0x97, 0x10, 0x25, 0x80, 0xd4, 0x4c, 0xe2, 0x74, 0xcc, 0xb2, + 0x5c, 0x33, 0xc7, 0xea, 0x05, 0x12, 0x3f, 0x51, 0xa8, 0xfe, 0xf1, 0x1c, 0x42, 0xca, 0xd1, + 0x76, 0x28, 0x6f, 0x92, 0xa7, 0x67, 0xa9, 0x19, 0xa2, 0x44, 0x5b, 0xaa, 0xef, 0x58, 0x7f, + 0x21, 0xc0, 0x88, 0x65, 0xb9, 0x5d, 0x4d, 0x0d, 0x5a, 0xc4, 0x13, 0x52, 0x7c, 0x9f, 0x94, + 0x17}, + {0xc7, 0x2b, 0x82, 0x61, 0x5b, 0xd0, 0x96, 0x84, 0xd3, 0x4a, 0x70, 0xa1, 0x9b, 0x59, 0x33, + 0x9f, 0xc0, 0x20, 0x14, 0x53, 0x29, 0x17, 0xc5, 0x0b, 0xc9, 0x7b, 0x97, 0x02, 0x0d, 0x3a, + 0x1e, 0x7c, 0x3f, 0x6b, 0x52, 0xe8, 0x75, 0x3d, 0xf6, 0xe4, 0x0c, 0x8a, 0x4f, 0xc3, 0x5f, + 0x26, 0x65, 0x73, 0x31, 0x23, 0x28, 0x48, 0x74, 0xaa, 0xa7, 0x36, 0x09, 0xb1, 0xe2, 0x91, + 0x04, 0x51, 0x22, 0xfc, 0x08, 0xa6, 0x05, 0xa4, 0xf1, 0x12, 0x1c, 0x19, 0xeb, 0x40, 0x37, + 0xc2, 0xa0, 0x41, 0x1d, 0xd4, 0xdc, 0x07, 0x43, 0x8f, 0x47, 0xaf, 0xd1, 0x2e, 0x98, 0xab, + 0x01, 0xba, 0xf0, 0x66, 0x68, 0xac, 0xf9, 0xe7, 0x69, 0xb6, 0xcb, 0x8d, 0x78, 0x87, 0x15, + 0x03, 0xd5, 0xdf, 0xa3, 0x1a, 0x9d, 0x6a, 0xea, 0x2f, 0x94, 0x4e, 0x9e, 0x42, 0xd7, 0xb8, + 0x38, 0x92, 0xd8, 0xbb, 0xde, 0xdd, 0x9a, 0xbc, 0xb0, 0x4c, 0x79, 0xf4, 0x58, 0x3e, 0xe9, + 0x83, 0x81, 0xff, 0xe3, 0x55, 0xfd, 0x5d, 0xb2, 0xef, 0x9c, 0x6d, 0x54, 0x99, 0x60, 0xda, + 0x3b, 0xec, 0xfa, 0x11, 0xd6, 0xc4, 0x2a, 0xed, 0x4b, 0xae, 0x13, 0xbf, 0xb9, 0x06, 0x8b, + 0xe0, 0x1f, 0x7f, 0x5a, 0xad, 0x90, 0x39, 0x0f, 0xf3, 0xbd, 0x46, 0x6c, 0x2c, 0xf2, 0xf8, + 0xfe, 0xd9, 0xe6, 0x72, 0x0e, 0x89, 0xbe, 0x5e, 0xc6, 0xa8, 0xcf, 0xf5, 0x57, 0x7e, 0x8c, + 0xb7, 0xe1, 0x88, 0x7a, 0xc8, 0x1b, 0x18, 0xdb, 0x6f, 0x35, 0xd2, 0x16, 0x32, 0x64, 0x63, + 0xa5, 0x8e, 0xf7, 0xb4, 0x76, 0x95, 0x86, 0x7d, 0xe5, 0xa9, 0x5c, 0x85, 0x00, 0xc1, 0x93, + 0x4d, 0xfb, 0x30, 0xa2, 0xb5, 0x25, 0x34, 0x10, 0x77, 0x3c, 0x67, 0x71, 0x2d, 0x44, 0x45, + 0x56, 0x0a, 0x50, 0xb3, 0xcd, 0x62, 0x80, 0xce, 0x21, 0x49, 0x24, 0xca, 0xee, 0x27, 0x6e, + 0xcc}, + {0xa5, 0xb0, 0x6d, 0xb2, 0x51, 0x55, 0x1f, 0xbf, 0x3e, 0x8d, 0xdd, 0x19, 0x92, 0xea, 0x1b, + 0xbd, 0x32, 0x65, 0x29, 0xa4, 0x89, 0x67, 0xb1, 0x90, 0x68, 0x00, 0xda, 0x0d, 0xa6, 0x59, + 0x54, 0xf2, 0x10, 0x14, 0x2f, 0x45, 0xe0, 0x3b, 0x23, 0xfa, 0xd7, 0x50, 0xac, 0x93, 0xe6, + 0x43, 0x01, 0x0a, 0x5c, 0x78, 0x70, 0x98, 0x46, 0xa9, 0x7b, 0xf3, 0x95, 0x07, 0x2a, 0xd3, + 0x74, 0xb3, 0x3f, 0xa3, 0x60, 0x82, 0x39, 0x4e, 0x34, 0x48, 0xc0, 0x1c, 0xf6, 0xc2, 0x91, + 0x64, 0x4d, 0x3c, 0xf8, 0x9d, 0x35, 0x9a, 0x94, 0xe3, 0x7a, 0xf0, 0xf9, 0x6e, 0xb9, 0x12, + 0xb8, 0x04, 0x2d, 0x02, 0x28, 0x13, 0x85, 0x72, 0x80, 0x87, 0xdb, 0x2b, 0xf1, 0x4f, 0x26, + 0xa2, 0xe1, 0x49, 0x7e, 0x9c, 0xcc, 0xa7, 0xb6, 0xa0, 0xd0, 0x9b, 0x36, 0x77, 0xad, 0x8b, + 0x6b, 0x4a, 0x03, 0x1d, 0x05, 0x8a, 0x06, 0x4b, 0xaf, 0xe5, 0x31, 0xb5, 0xd4, 0xc6, 0x0c, + 0x66, 0xba, 0x83, 0xfd, 0x09, 0x0f, 0xae, 0x71, 0xb7, 0x6f, 0xdc, 0x41, 0xe8, 0x17, 0x8e, + 0x40, 0x7f, 0x62, 0x30, 0xff, 0xa8, 0x84, 0x25, 0xfb, 0x16, 0xce, 0x37, 0x44, 0xab, 0x99, + 0x1e, 0xeb, 0x18, 0x3a, 0x47, 0xf7, 0x5f, 0x81, 0xcf, 0xed, 0x58, 0x2c, 0x6c, 0xfe, 0x9e, + 0x57, 0x53, 0x97, 0x20, 0xca, 0x79, 0x1a, 0x5a, 0x88, 0xf5, 0x69, 0x9f, 0xe7, 0xd9, 0x0e, + 0xbe, 0x42, 0xdf, 0x56, 0xe4, 0x4c, 0x22, 0xaa, 0x73, 0x0b, 0x15, 0xc5, 0xee, 0xfc, 0xc7, + 0xd6, 0xcb, 0xcd, 0x8c, 0xe2, 0x76, 0x21, 0xe9, 0xd1, 0xec, 0xc8, 0x7d, 0xd8, 0x8f, 0x61, + 0x7c, 0x2e, 0xbc, 0xde, 0xb4, 0x75, 0xd2, 0xc4, 0x63, 0x3d, 0xa1, 0x5e, 0x5d, 0x6a, 0x08, + 0x24, 0xc9, 0x27, 0xbb, 0xef, 0x33, 0x86, 0x5b, 0xd5, 0x38, 0x52, 0x11, 0xf4, 0xc3, 0x96, + 0xc1}, + {0x34, 0x5a, 0xdb, 0x2c, 0x59, 0x57, 0x12, 0x2b, 0x30, 0xc2, 0xa0, 0x92, 0xbf, 0xed, 0xbc, + 0x45, 0xde, 0x27, 0x9b, 0x96, 0xd3, 0xe6, 0xc5, 0xeb, 0xd8, 0x24, 0x4b, 0xa4, 0x21, 0xcc, + 0xa8, 0xd6, 0xce, 0x3c, 0xba, 0xb1, 0x09, 0xe0, 0xd7, 0x32, 0x66, 0x4a, 0x83, 0x1d, 0x19, + 0xca, 0x89, 0x67, 0x0f, 0x42, 0x07, 0x71, 0xe9, 0xbb, 0x44, 0x5e, 0x85, 0x17, 0xc4, 0xae, + 0x9c, 0x3f, 0x6b, 0x78, 0xe7, 0x6d, 0x02, 0xa7, 0xfe, 0x33, 0xe3, 0xd9, 0x0a, 0x7d, 0xea, + 0xf1, 0x18, 0x87, 0x7b, 0x62, 0x03, 0x79, 0x52, 0x23, 0x00, 0x3e, 0x25, 0xb9, 0xdd, 0x16, + 0x68, 0x3b, 0x1f, 0x90, 0xa6, 0x2a, 0xc6, 0x29, 0x91, 0x8a, 0x9d, 0xef, 0x1e, 0x84, 0x76, + 0xee, 0x4f, 0x39, 0xfb, 0x11, 0x1b, 0x0b, 0x93, 0xad, 0x49, 0xb7, 0x05, 0xe1, 0x4d, 0xcb, + 0xe8, 0x38, 0xd0, 0x55, 0xf2, 0xf7, 0x0d, 0x8b, 0x65, 0xcd, 0xfa, 0xd4, 0x9e, 0xc9, 0x81, + 0x4e, 0x14, 0xc8, 0x26, 0xb6, 0xf9, 0xaa, 0xf5, 0x80, 0xf8, 0x6a, 0x98, 0xab, 0x58, 0xf3, + 0xb8, 0x8e, 0xb5, 0x97, 0x43, 0x72, 0xa2, 0xda, 0x64, 0xc1, 0x40, 0x5c, 0x13, 0xb0, 0xe5, + 0xbd, 0x08, 0x9f, 0x1c, 0x7e, 0x8f, 0x06, 0xbe, 0x6e, 0x50, 0x1a, 0x2f, 0x37, 0xa1, 0xfc, + 0x10, 0x2e, 0x70, 0x53, 0x04, 0x20, 0x63, 0x48, 0xe2, 0xfd, 0x6f, 0x7a, 0x75, 0x61, 0xb3, + 0x35, 0x3a, 0xcf, 0x5b, 0x88, 0x82, 0x51, 0xd2, 0x47, 0xa5, 0xa9, 0x74, 0x6c, 0x31, 0x94, + 0x7c, 0x9a, 0xc0, 0xec, 0x15, 0x0e, 0x28, 0x01, 0x2d, 0xc3, 0xf0, 0xb4, 0xe4, 0x8d, 0xdc, + 0xac, 0x3d, 0x5f, 0x86, 0xc7, 0x95, 0x22, 0xf4, 0xb2, 0xa3, 0x54, 0x46, 0xd1, 0x77, 0x8c, + 0x0c, 0xff, 0xaf, 0x56, 0x5d, 0x36, 0x69, 0x7f, 0x99, 0x4c, 0xd5, 0x60, 0x73, 0xdf, 0x41, + 0xf6}, + {0x04, 0xda, 0x79, 0x63, 0x1e, 0xbd, 0xea, 0xe3, 0x0b, 0x65, 0x25, 0x01, 0xcd, 0xa9, 0x92, + 0xed, 0x18, 0x75, 0x57, 0x30, 0x89, 0x03, 0x09, 0x6a, 0xdc, 0xc7, 0x98, 0xa4, 0x50, 0x91, + 0x1f, 0xb1, 0x0c, 0x77, 0x85, 0x66, 0xca, 0x12, 0x1c, 0x67, 0xc2, 0xe0, 0x17, 0x83, 0x59, + 0x9d, 0xd5, 0x22, 0x82, 0x56, 0xa8, 0x9f, 0xc6, 0x60, 0x48, 0xb3, 0x11, 0xfc, 0xa3, 0x28, + 0xfb, 0x06, 0xdd, 0x5d, 0xba, 0x29, 0x7a, 0x4f, 0xe8, 0xfe, 0xe9, 0x10, 0xcb, 0xf3, 0x93, + 0x7b, 0x6c, 0x69, 0x54, 0xe7, 0x44, 0xa2, 0x84, 0x1d, 0x8d, 0xce, 0xff, 0xfa, 0x1a, 0x87, + 0x90, 0x74, 0xa1, 0xf8, 0x14, 0xaa, 0xbc, 0xc0, 0xcf, 0x31, 0xb4, 0xd9, 0xbf, 0xd8, 0x5e, + 0x26, 0x2d, 0xd0, 0xe4, 0x3f, 0x19, 0xd6, 0x8c, 0x2f, 0xab, 0x39, 0x58, 0x72, 0x6e, 0xdf, + 0x3b, 0xe6, 0x3c, 0xb6, 0x62, 0x88, 0xde, 0x40, 0xb2, 0x8f, 0x9b, 0x7c, 0x95, 0x43, 0xd1, + 0x9e, 0xac, 0x9c, 0xd4, 0x23, 0xe1, 0x0e, 0x4b, 0x53, 0x33, 0x46, 0x20, 0x13, 0x34, 0x1b, + 0x97, 0xf7, 0xf1, 0xc3, 0x61, 0x4a, 0x6f, 0x5a, 0x21, 0x7f, 0x70, 0x2e, 0x55, 0x41, 0x05, + 0xc5, 0xd7, 0x76, 0xe5, 0x27, 0x15, 0xec, 0x42, 0x5c, 0x4d, 0x78, 0x35, 0x8b, 0xef, 0xd2, + 0xee, 0xb5, 0xbe, 0xae, 0x02, 0x3a, 0xd3, 0x5f, 0xc4, 0x24, 0xf0, 0xf9, 0x51, 0x4e, 0xeb, + 0x00, 0x0f, 0xfd, 0xaf, 0x3e, 0xc1, 0x9a, 0x52, 0x86, 0x81, 0x80, 0x7e, 0xf6, 0x2b, 0xcc, + 0xb9, 0x7d, 0x68, 0xf2, 0xad, 0x99, 0xa7, 0x07, 0x2c, 0x73, 0x38, 0xb0, 0x6b, 0xb7, 0x8e, + 0x71, 0xa0, 0xf4, 0x3d, 0xa6, 0x0d, 0x37, 0xdb, 0x0a, 0x47, 0x36, 0x16, 0x96, 0x6d, 0x32, + 0x2a, 0x5b, 0xe2, 0x45, 0x94, 0xf5, 0xa5, 0x4c, 0xc8, 0x8a, 0x49, 0x64, 0xbb, 0x08, 0xc9, + 0xb8}, +}; + +static const uint8_t access_code_table_1[5][10][100] = { + { + {11, 38, 3, 63, 36, 39, 79, 19, 87, 68, 76, 51, 20, 56, 77, 61, 52, 73, 74, 94, + 45, 31, 99, 28, 29, 90, 81, 96, 65, 75, 13, 32, 70, 21, 55, 33, 9, 15, 92, 14, + 82, 97, 78, 37, 49, 0, 80, 57, 58, 7, 1, 89, 98, 53, 64, 5, 6, 66, 43, 83, + 10, 50, 47, 84, 62, 71, 4, 67, 86, 42, 35, 34, 91, 12, 17, 69, 44, 48, 85, 30, + 16, 95, 54, 23, 46, 18, 88, 59, 40, 60, 41, 25, 27, 22, 2, 24, 72, 93, 26, 8}, + {28, 58, 74, 34, 6, 14, 16, 46, 87, 24, 59, 57, 23, 88, 75, 65, 79, 38, 82, 13, + 49, 99, 8, 94, 15, 19, 0, 96, 41, 95, 45, 35, 1, 91, 2, 52, 42, 76, 90, 84, + 20, 54, 4, 63, 25, 5, 47, 85, 93, 12, 51, 81, 71, 69, 22, 17, 36, 64, 77, 80, + 98, 53, 44, 18, 10, 68, 97, 61, 56, 70, 92, 27, 40, 9, 60, 7, 72, 21, 43, 3, + 33, 32, 50, 26, 67, 62, 39, 48, 73, 86, 29, 89, 37, 78, 55, 66, 31, 30, 11, 83}, + {4, 16, 22, 66, 37, 14, 47, 89, 59, 60, 51, 92, 71, 70, 96, 13, 17, 85, 58, 93, + 81, 69, 28, 90, 78, 39, 63, 88, 0, 75, 2, 53, 23, 99, 94, 68, 54, 74, 41, 29, + 8, 26, 50, 98, 82, 95, 42, 7, 24, 77, 30, 9, 21, 1, 87, 62, 46, 15, 43, 38, + 36, 55, 49, 57, 52, 56, 91, 65, 11, 84, 6, 73, 97, 33, 83, 27, 35, 25, 40, 32, + 18, 31, 86, 48, 45, 3, 79, 76, 72, 64, 44, 20, 80, 19, 10, 5, 61, 34, 67, 12}, + {80, 91, 71, 26, 75, 8, 22, 92, 7, 70, 65, 51, 89, 31, 81, 30, 83, 47, 44, 85, + 55, 19, 57, 87, 74, 6, 73, 40, 49, 32, 52, 78, 0, 12, 54, 15, 64, 28, 4, 24, + 68, 17, 72, 53, 1, 94, 58, 88, 11, 82, 43, 86, 60, 63, 99, 96, 37, 77, 67, 33, + 18, 5, 41, 59, 16, 36, 79, 97, 61, 23, 56, 48, 98, 38, 66, 21, 34, 29, 10, 3, + 93, 46, 50, 39, 14, 9, 2, 13, 69, 42, 62, 35, 27, 90, 25, 76, 20, 95, 45, 84}, + {48, 16, 54, 47, 9, 39, 81, 91, 33, 53, 63, 21, 50, 85, 97, 90, 49, 36, 84, 74, + 51, 76, 37, 67, 59, 58, 23, 20, 65, 82, 7, 77, 12, 31, 46, 1, 5, 14, 3, 30, + 94, 95, 2, 34, 70, 89, 40, 87, 43, 79, 64, 57, 68, 26, 56, 44, 88, 61, 0, 24, + 71, 18, 60, 4, 80, 96, 27, 15, 6, 13, 98, 29, 28, 25, 72, 93, 55, 75, 52, 66, + 11, 8, 86, 45, 22, 83, 17, 10, 35, 38, 73, 99, 62, 41, 19, 69, 78, 92, 42, 32}, + {23, 19, 88, 44, 5, 39, 75, 81, 30, 14, 49, 54, 62, 55, 18, 83, 77, 76, 74, 92, + 6, 95, 52, 36, 15, 27, 53, 38, 28, 50, 48, 99, 78, 67, 90, 87, 16, 41, 58, 65, + 79, 11, 68, 84, 24, 97, 64, 47, 9, 22, 43, 86, 37, 8, 33, 17, 93, 61, 25, 72, + 57, 98, 42, 80, 13, 89, 2, 12, 31, 56, 26, 85, 3, 29, 66, 63, 82, 10, 20, 94, + 51, 0, 1, 4, 21, 60, 34, 35, 32, 45, 59, 71, 46, 69, 7, 96, 91, 40, 70, 73}, + {89, 62, 33, 19, 21, 97, 15, 2, 83, 22, 65, 30, 55, 7, 63, 73, 39, 61, 44, 37, + 11, 26, 31, 79, 91, 0, 72, 68, 23, 87, 28, 24, 45, 74, 98, 14, 70, 54, 88, 42, + 35, 59, 46, 16, 27, 92, 69, 66, 94, 32, 75, 77, 64, 57, 50, 71, 10, 3, 67, 81, + 90, 58, 99, 5, 82, 85, 25, 52, 12, 8, 48, 49, 76, 96, 80, 20, 29, 34, 53, 51, + 86, 93, 38, 84, 9, 36, 6, 60, 1, 78, 13, 95, 41, 17, 47, 18, 43, 40, 4, 56}, + {40, 68, 88, 47, 43, 28, 67, 23, 42, 99, 48, 3, 12, 27, 79, 25, 6, 55, 19, 80, + 81, 62, 83, 69, 39, 73, 36, 30, 44, 82, 70, 52, 31, 34, 58, 74, 54, 2, 13, 45, + 93, 49, 75, 72, 29, 87, 5, 7, 17, 76, 90, 63, 84, 51, 59, 35, 20, 97, 22, 65, + 57, 66, 78, 86, 11, 9, 71, 98, 53, 56, 15, 46, 92, 0, 26, 21, 32, 38, 77, 91, + 4, 24, 61, 85, 94, 1, 96, 64, 8, 41, 33, 50, 18, 60, 10, 16, 37, 95, 14, 89}, + {96, 23, 99, 42, 94, 91, 19, 53, 41, 25, 39, 73, 70, 72, 33, 35, 88, 2, 63, 49, + 95, 60, 48, 97, 5, 54, 47, 40, 98, 18, 57, 28, 67, 92, 10, 79, 13, 76, 8, 55, + 90, 62, 50, 87, 65, 82, 84, 21, 0, 14, 45, 44, 20, 38, 17, 3, 81, 16, 58, 4, + 31, 29, 86, 15, 43, 64, 26, 52, 46, 69, 37, 68, 6, 22, 61, 7, 83, 71, 30, 75, + 85, 78, 24, 27, 89, 9, 80, 34, 66, 77, 36, 93, 56, 59, 1, 12, 51, 32, 11, 74}, + {75, 99, 97, 8, 15, 48, 6, 25, 60, 90, 91, 95, 23, 3, 89, 77, 22, 78, 94, 55, + 98, 35, 53, 96, 18, 52, 12, 16, 50, 20, 49, 9, 17, 13, 57, 44, 14, 47, 86, 56, + 88, 41, 29, 32, 70, 1, 62, 37, 85, 38, 59, 36, 5, 68, 71, 69, 67, 27, 39, 4, + 54, 0, 84, 45, 61, 73, 79, 93, 58, 66, 10, 76, 51, 92, 19, 87, 24, 7, 28, 2, + 11, 80, 81, 33, 64, 74, 46, 65, 26, 72, 40, 82, 34, 42, 21, 83, 31, 30, 43, 63}, + }, + { + {98, 58, 34, 65, 83, 74, 88, 5, 89, 46, 80, 43, 18, 87, 9, 28, 71, 90, 4, 36, + 40, 11, 39, 31, 76, 92, 82, 48, 66, 41, 49, 99, 78, 79, 97, 86, 75, 68, 3, 7, + 62, 64, 26, 22, 13, 77, 54, 50, 10, 96, 15, 35, 94, 73, 57, 93, 16, 61, 37, 38, + 53, 2, 63, 23, 67, 72, 95, 70, 29, 33, 45, 47, 27, 24, 69, 44, 55, 85, 14, 8, + 91, 12, 42, 6, 84, 81, 19, 25, 32, 51, 1, 17, 52, 56, 60, 0, 59, 30, 21, 20}, + {26, 63, 99, 57, 77, 53, 9, 75, 76, 80, 46, 97, 37, 14, 1, 12, 35, 20, 0, 15, + 28, 19, 48, 41, 67, 30, 60, 69, 29, 34, 73, 10, 51, 47, 66, 82, 7, 98, 39, 96, + 8, 62, 42, 3, 95, 5, 11, 13, 90, 94, 32, 31, 83, 64, 36, 88, 72, 52, 38, 54, + 45, 17, 61, 81, 78, 44, 79, 55, 56, 86, 2, 40, 16, 24, 4, 71, 49, 33, 23, 92, + 84, 74, 65, 43, 87, 59, 89, 68, 22, 70, 25, 58, 21, 85, 6, 50, 27, 93, 91, 18}, + {22, 31, 11, 72, 37, 70, 90, 60, 24, 56, 28, 69, 54, 85, 5, 98, 1, 76, 59, 20, + 86, 32, 61, 49, 63, 42, 74, 53, 84, 3, 95, 23, 15, 36, 4, 81, 6, 21, 19, 27, + 17, 83, 30, 45, 25, 65, 10, 50, 80, 66, 46, 75, 93, 82, 78, 99, 97, 68, 8, 13, + 58, 94, 41, 14, 48, 52, 29, 79, 91, 89, 9, 55, 57, 2, 39, 92, 0, 44, 18, 62, + 73, 96, 34, 88, 16, 77, 71, 43, 33, 40, 7, 12, 87, 51, 26, 47, 64, 35, 67, 38}, + {0, 80, 63, 52, 49, 69, 15, 87, 55, 60, 22, 91, 31, 89, 78, 28, 48, 12, 10, 42, + 82, 23, 29, 18, 21, 2, 19, 7, 68, 37, 54, 90, 81, 43, 57, 94, 26, 95, 96, 88, + 14, 51, 79, 71, 66, 76, 34, 24, 30, 9, 62, 44, 77, 65, 6, 27, 4, 97, 38, 36, + 59, 3, 1, 50, 72, 46, 64, 35, 20, 11, 53, 13, 58, 39, 74, 8, 83, 75, 40, 98, + 17, 99, 85, 32, 92, 67, 84, 5, 25, 56, 16, 47, 70, 93, 86, 61, 73, 41, 45, 33}, + {64, 57, 56, 7, 86, 9, 65, 16, 1, 63, 71, 26, 53, 98, 69, 5, 84, 28, 36, 58, + 54, 99, 42, 74, 68, 3, 88, 22, 82, 97, 85, 45, 21, 66, 43, 59, 83, 51, 89, 87, + 31, 12, 95, 52, 60, 17, 80, 27, 72, 93, 10, 39, 91, 30, 75, 61, 24, 8, 23, 2, + 18, 73, 94, 6, 38, 44, 13, 40, 48, 4, 81, 37, 96, 46, 19, 49, 34, 50, 0, 20, + 14, 15, 25, 92, 33, 29, 77, 90, 78, 67, 62, 41, 35, 47, 79, 32, 11, 70, 76, 55}, + {24, 99, 28, 52, 64, 3, 63, 16, 15, 87, 47, 6, 59, 98, 13, 78, 57, 81, 56, 94, + 0, 37, 70, 73, 65, 83, 90, 92, 60, 82, 67, 40, 74, 34, 91, 2, 33, 85, 21, 54, + 12, 31, 84, 55, 53, 49, 36, 25, 39, 20, 29, 93, 51, 76, 42, 96, 71, 86, 95, 66, + 45, 19, 8, 58, 22, 69, 80, 79, 89, 17, 61, 18, 88, 38, 72, 75, 48, 30, 7, 10, + 11, 35, 23, 9, 26, 4, 62, 44, 1, 43, 46, 14, 41, 77, 97, 27, 68, 32, 5, 50}, + {90, 80, 22, 27, 16, 23, 11, 31, 8, 75, 54, 82, 68, 66, 10, 40, 95, 99, 14, 42, + 56, 88, 12, 55, 38, 6, 79, 84, 26, 92, 4, 67, 48, 5, 46, 77, 19, 53, 97, 34, + 64, 73, 58, 47, 18, 39, 81, 59, 74, 62, 87, 98, 50, 1, 25, 9, 72, 69, 61, 60, + 86, 3, 24, 28, 45, 30, 96, 36, 57, 83, 89, 63, 0, 33, 17, 7, 94, 15, 43, 2, + 44, 41, 20, 85, 32, 65, 93, 49, 91, 52, 71, 37, 35, 13, 21, 51, 70, 29, 78, 76}, + {95, 50, 72, 0, 68, 98, 49, 53, 56, 41, 54, 22, 57, 78, 66, 31, 52, 29, 86, 30, + 62, 26, 32, 46, 42, 48, 14, 21, 88, 94, 77, 92, 7, 63, 91, 28, 93, 38, 90, 71, + 34, 25, 11, 83, 55, 70, 97, 82, 89, 76, 10, 99, 9, 45, 60, 39, 44, 16, 17, 37, + 47, 79, 33, 36, 59, 8, 80, 23, 1, 18, 64, 51, 61, 81, 75, 40, 35, 73, 13, 65, + 12, 4, 19, 85, 96, 6, 2, 58, 27, 43, 5, 87, 20, 15, 3, 24, 84, 67, 74, 69}, + {38, 33, 29, 88, 49, 47, 60, 69, 75, 12, 44, 89, 35, 45, 23, 30, 21, 28, 65, 86, + 81, 40, 93, 97, 67, 2, 94, 9, 61, 92, 90, 32, 62, 36, 58, 51, 37, 73, 50, 63, + 39, 43, 16, 71, 46, 91, 13, 7, 20, 72, 41, 59, 70, 48, 10, 52, 5, 34, 76, 15, + 87, 6, 98, 18, 57, 77, 24, 79, 27, 19, 3, 0, 8, 95, 74, 64, 42, 4, 68, 31, + 84, 54, 99, 25, 1, 80, 85, 26, 83, 11, 56, 55, 78, 66, 82, 14, 53, 22, 17, 96}, + {26, 12, 62, 5, 78, 53, 31, 46, 51, 8, 69, 80, 32, 68, 76, 42, 29, 87, 28, 75, + 40, 91, 58, 7, 88, 92, 82, 96, 70, 50, 47, 65, 85, 3, 48, 73, 90, 89, 97, 84, + 81, 39, 43, 79, 64, 54, 2, 41, 38, 10, 24, 22, 25, 93, 44, 23, 49, 37, 14, 95, + 72, 74, 15, 11, 55, 98, 99, 0, 9, 16, 18, 20, 63, 30, 57, 19, 4, 17, 67, 35, + 60, 45, 59, 56, 36, 77, 66, 33, 27, 71, 86, 1, 13, 34, 21, 61, 6, 94, 52, 83}, + }, + { + {10, 87, 64, 11, 25, 97, 93, 18, 99, 38, 17, 72, 50, 23, 90, 57, 98, 12, 29, 46, + 19, 77, 42, 15, 73, 52, 53, 89, 35, 20, 9, 41, 79, 13, 95, 48, 85, 54, 61, 94, + 0, 74, 62, 40, 78, 68, 24, 28, 86, 21, 59, 14, 82, 84, 92, 58, 34, 31, 6, 16, + 75, 81, 43, 47, 7, 3, 2, 37, 67, 8, 66, 22, 30, 32, 71, 44, 70, 1, 36, 26, + 33, 63, 80, 83, 91, 76, 56, 39, 69, 55, 96, 60, 27, 49, 65, 88, 5, 4, 51, 45}, + {73, 87, 48, 43, 71, 40, 88, 23, 60, 35, 30, 5, 75, 25, 59, 95, 58, 79, 4, 89, + 96, 17, 38, 6, 39, 9, 68, 81, 3, 90, 93, 99, 16, 42, 21, 45, 74, 84, 77, 65, + 63, 98, 11, 51, 0, 33, 91, 22, 69, 34, 31, 20, 46, 2, 76, 54, 15, 62, 13, 70, + 92, 55, 82, 47, 78, 7, 24, 27, 86, 1, 56, 61, 12, 44, 85, 57, 49, 19, 18, 67, + 66, 52, 72, 53, 83, 41, 64, 50, 26, 94, 32, 80, 28, 10, 37, 14, 8, 97, 29, 36}, + {60, 50, 53, 78, 63, 90, 11, 33, 21, 24, 20, 55, 8, 25, 6, 58, 3, 98, 44, 82, + 96, 14, 1, 59, 92, 75, 51, 36, 30, 64, 72, 40, 16, 94, 2, 74, 93, 85, 12, 10, + 43, 32, 42, 39, 79, 97, 17, 38, 89, 29, 68, 52, 49, 0, 34, 19, 70, 84, 54, 9, + 23, 71, 65, 28, 83, 56, 67, 57, 13, 18, 77, 5, 4, 88, 41, 22, 46, 76, 48, 61, + 81, 45, 86, 35, 47, 87, 91, 99, 73, 66, 27, 37, 31, 15, 7, 80, 62, 95, 69, 26}, + {49, 73, 63, 66, 38, 35, 32, 96, 87, 26, 36, 92, 58, 6, 17, 47, 29, 84, 11, 18, + 67, 45, 78, 4, 52, 81, 59, 30, 91, 54, 56, 89, 48, 46, 20, 79, 1, 62, 9, 53, + 10, 95, 88, 34, 40, 80, 41, 64, 83, 55, 43, 7, 24, 61, 77, 5, 14, 93, 16, 21, + 85, 39, 76, 50, 57, 65, 37, 60, 28, 19, 2, 97, 42, 33, 13, 68, 90, 25, 69, 99, + 98, 74, 3, 8, 12, 15, 82, 71, 31, 23, 75, 27, 51, 94, 86, 0, 44, 22, 72, 70}, + {30, 32, 52, 21, 15, 79, 53, 40, 56, 44, 55, 6, 63, 39, 61, 4, 93, 43, 23, 99, + 38, 66, 88, 18, 1, 86, 33, 9, 82, 76, 70, 51, 48, 69, 85, 11, 5, 91, 59, 77, + 49, 75, 22, 87, 65, 41, 26, 50, 47, 98, 57, 10, 58, 62, 46, 28, 20, 71, 78, 95, + 0, 72, 80, 73, 92, 36, 96, 37, 68, 2, 35, 83, 90, 25, 13, 24, 34, 42, 45, 74, + 67, 16, 84, 97, 19, 64, 27, 14, 31, 12, 54, 60, 3, 8, 17, 89, 81, 94, 7, 29}, + {1, 18, 25, 13, 57, 53, 92, 95, 94, 88, 10, 82, 34, 83, 7, 28, 55, 11, 5, 58, + 8, 9, 74, 99, 52, 32, 62, 45, 65, 50, 41, 56, 96, 44, 38, 47, 3, 39, 81, 19, + 2, 22, 49, 12, 40, 67, 89, 76, 64, 70, 61, 21, 31, 54, 51, 17, 46, 30, 24, 60, + 66, 26, 77, 63, 37, 35, 23, 6, 4, 87, 79, 97, 20, 48, 36, 85, 73, 71, 27, 43, + 0, 15, 69, 78, 59, 86, 75, 33, 42, 84, 91, 93, 68, 80, 16, 98, 90, 72, 14, 29}, + {30, 16, 10, 67, 83, 81, 42, 94, 35, 59, 22, 5, 53, 61, 1, 96, 68, 45, 95, 66, + 36, 37, 97, 87, 77, 93, 86, 33, 49, 13, 43, 14, 0, 34, 7, 2, 41, 21, 31, 32, + 76, 55, 62, 72, 90, 71, 38, 23, 46, 12, 51, 19, 6, 44, 52, 48, 73, 24, 29, 74, + 85, 99, 80, 28, 47, 9, 39, 84, 64, 57, 75, 98, 88, 26, 17, 63, 27, 56, 69, 8, + 54, 25, 91, 78, 3, 50, 65, 70, 11, 4, 92, 60, 40, 58, 15, 89, 82, 18, 79, 20}, + {60, 88, 79, 35, 40, 43, 52, 4, 17, 31, 84, 44, 50, 94, 91, 38, 87, 25, 92, 62, + 14, 97, 37, 18, 86, 28, 65, 90, 19, 29, 30, 75, 68, 49, 69, 11, 59, 41, 89, 33, + 47, 54, 98, 42, 80, 56, 78, 99, 23, 82, 9, 48, 21, 1, 63, 22, 58, 95, 10, 12, + 26, 74, 85, 13, 8, 27, 24, 53, 61, 34, 64, 71, 66, 32, 83, 73, 15, 67, 0, 20, + 16, 6, 77, 45, 5, 93, 76, 39, 57, 2, 96, 46, 51, 3, 55, 7, 81, 70, 36, 72}, + {35, 56, 77, 58, 23, 83, 91, 81, 53, 70, 20, 74, 84, 50, 79, 51, 88, 44, 45, 71, + 37, 26, 54, 48, 27, 43, 0, 12, 95, 97, 1, 63, 67, 98, 59, 40, 13, 19, 31, 87, + 99, 16, 57, 30, 17, 92, 55, 68, 5, 46, 64, 8, 7, 42, 90, 33, 29, 9, 85, 24, + 2, 69, 32, 10, 21, 65, 39, 86, 52, 62, 76, 89, 82, 6, 78, 4, 47, 28, 3, 75, + 41, 15, 18, 14, 66, 73, 38, 80, 61, 49, 11, 22, 34, 93, 36, 25, 94, 96, 60, 72}, + {6, 90, 16, 43, 20, 55, 94, 8, 62, 59, 47, 32, 81, 67, 42, 35, 71, 19, 54, 49, + 2, 88, 38, 5, 53, 77, 85, 11, 73, 34, 3, 72, 95, 58, 82, 64, 44, 29, 93, 14, + 56, 98, 4, 37, 45, 75, 86, 60, 87, 7, 31, 17, 89, 97, 10, 70, 36, 99, 46, 63, + 76, 41, 48, 84, 9, 52, 1, 24, 83, 25, 68, 18, 50, 26, 30, 27, 61, 0, 91, 33, + 51, 40, 79, 39, 15, 74, 28, 78, 69, 96, 57, 65, 21, 80, 22, 23, 12, 13, 92, 66}, + }, + { + {93, 42, 18, 65, 28, 87, 39, 35, 71, 23, 96, 90, 15, 58, 78, 33, 92, 88, 49, 64, + 24, 84, 77, 8, 97, 50, 4, 53, 27, 83, 95, 31, 80, 47, 86, 55, 11, 40, 37, 14, + 34, 60, 54, 12, 94, 9, 41, 2, 26, 10, 6, 57, 75, 63, 32, 43, 13, 52, 82, 73, + 20, 30, 17, 5, 3, 68, 51, 89, 62, 79, 69, 70, 66, 45, 25, 22, 85, 0, 91, 76, + 38, 7, 99, 61, 29, 98, 16, 36, 44, 67, 59, 56, 48, 81, 21, 46, 74, 72, 19, 1}, + {78, 91, 97, 77, 75, 85, 32, 2, 40, 86, 38, 18, 56, 47, 50, 87, 27, 92, 64, 39, + 98, 23, 33, 72, 84, 58, 16, 99, 19, 65, 55, 73, 74, 42, 4, 48, 95, 8, 51, 3, + 37, 82, 96, 69, 14, 44, 12, 21, 76, 70, 36, 15, 71, 41, 20, 31, 54, 13, 0, 9, + 94, 67, 1, 60, 81, 61, 11, 6, 90, 5, 49, 10, 34, 7, 35, 30, 25, 88, 79, 93, + 66, 62, 22, 24, 52, 17, 89, 45, 83, 57, 28, 46, 63, 68, 26, 29, 80, 59, 43, 53}, + {78, 91, 97, 77, 75, 85, 32, 2, 40, 86, 38, 18, 56, 47, 50, 87, 27, 92, 64, 39, + 98, 23, 33, 72, 84, 58, 16, 99, 19, 65, 55, 73, 74, 42, 4, 48, 95, 8, 51, 3, + 37, 82, 96, 69, 14, 44, 12, 21, 76, 70, 36, 15, 71, 41, 20, 31, 54, 13, 0, 9, + 94, 67, 1, 60, 81, 61, 11, 6, 90, 5, 49, 10, 34, 7, 35, 30, 25, 88, 79, 93, + 66, 62, 22, 24, 52, 17, 89, 45, 83, 57, 28, 46, 63, 68, 26, 29, 80, 59, 43, 53}, + {41, 65, 16, 86, 77, 67, 51, 81, 50, 25, 90, 87, 98, 99, 23, 9, 4, 97, 39, 36, + 26, 42, 13, 66, 82, 22, 47, 88, 49, 46, 59, 78, 20, 19, 7, 93, 56, 84, 40, 61, + 89, 21, 74, 1, 54, 44, 34, 6, 28, 8, 43, 76, 48, 71, 85, 38, 83, 18, 24, 79, + 68, 80, 45, 33, 91, 27, 32, 70, 95, 3, 58, 60, 73, 10, 75, 52, 64, 2, 94, 30, + 17, 55, 62, 35, 11, 12, 53, 63, 14, 31, 57, 0, 37, 72, 92, 15, 5, 29, 69, 96}, + {74, 50, 44, 80, 11, 46, 39, 54, 24, 3, 7, 62, 41, 8, 75, 23, 57, 82, 51, 86, + 97, 79, 22, 60, 42, 14, 28, 67, 32, 73, 48, 56, 89, 26, 25, 77, 21, 68, 10, 2, + 58, 76, 92, 95, 38, 29, 43, 35, 12, 16, 55, 17, 63, 91, 45, 64, 59, 13, 47, 31, + 83, 5, 99, 49, 78, 71, 96, 53, 36, 90, 1, 66, 15, 0, 85, 65, 27, 4, 98, 94, + 6, 84, 72, 30, 70, 20, 61, 69, 37, 93, 34, 87, 9, 52, 88, 81, 33, 19, 40, 18}, + {35, 67, 27, 53, 26, 79, 20, 46, 38, 4, 72, 84, 70, 58, 65, 91, 92, 21, 32, 71, + 54, 24, 89, 0, 37, 8, 22, 81, 12, 87, 43, 18, 82, 17, 33, 76, 25, 13, 74, 51, + 99, 3, 55, 60, 19, 73, 86, 62, 93, 52, 31, 64, 96, 66, 14, 40, 42, 69, 45, 6, + 61, 34, 41, 59, 49, 10, 39, 2, 75, 80, 15, 94, 7, 23, 95, 78, 90, 5, 1, 28, + 56, 30, 9, 98, 44, 48, 68, 77, 47, 11, 63, 83, 88, 36, 97, 29, 57, 50, 16, 85}, + {8, 51, 32, 96, 34, 24, 88, 54, 87, 83, 45, 99, 49, 30, 55, 61, 95, 60, 59, 76, + 57, 5, 19, 56, 89, 66, 27, 94, 14, 6, 21, 58, 43, 74, 10, 2, 17, 62, 47, 16, + 69, 84, 63, 39, 4, 92, 35, 48, 53, 64, 71, 50, 72, 28, 42, 46, 91, 23, 15, 40, + 11, 78, 29, 86, 13, 90, 85, 20, 65, 93, 73, 80, 41, 37, 82, 79, 52, 9, 97, 38, + 75, 25, 44, 7, 26, 12, 22, 0, 33, 1, 31, 68, 70, 67, 98, 18, 77, 36, 3, 81}, + {34, 17, 91, 45, 63, 64, 1, 71, 98, 78, 94, 3, 99, 12, 15, 93, 69, 19, 61, 80, + 92, 81, 33, 51, 40, 10, 75, 24, 5, 87, 73, 46, 52, 86, 4, 70, 89, 56, 2, 67, + 72, 58, 49, 84, 29, 18, 22, 38, 95, 36, 43, 35, 37, 85, 57, 8, 76, 7, 27, 28, + 55, 74, 42, 13, 77, 31, 60, 39, 48, 68, 97, 79, 44, 54, 88, 82, 21, 66, 9, 23, + 59, 50, 47, 16, 83, 25, 6, 30, 41, 96, 90, 14, 62, 20, 65, 11, 26, 32, 53, 0}, + {7, 51, 77, 80, 0, 53, 56, 27, 2, 78, 28, 32, 26, 22, 73, 93, 36, 54, 64, 99, + 60, 50, 21, 11, 39, 42, 81, 8, 1, 79, 61, 5, 15, 31, 59, 33, 20, 94, 23, 88, + 69, 17, 35, 95, 40, 75, 85, 87, 43, 47, 89, 12, 71, 72, 9, 83, 67, 38, 90, 82, + 29, 10, 37, 91, 97, 74, 6, 86, 30, 63, 24, 49, 3, 13, 4, 57, 76, 70, 98, 34, + 58, 52, 96, 84, 25, 55, 44, 46, 18, 65, 62, 68, 48, 66, 41, 19, 14, 45, 16, 92}, + {96, 2, 76, 15, 31, 22, 71, 38, 88, 26, 67, 0, 21, 10, 58, 39, 63, 77, 23, 50, + 48, 19, 78, 30, 93, 91, 14, 12, 28, 11, 53, 73, 60, 61, 35, 79, 57, 70, 40, 20, + 56, 32, 65, 64, 68, 55, 82, 94, 45, 95, 66, 7, 43, 16, 37, 99, 72, 62, 13, 4, + 47, 8, 98, 89, 46, 29, 51, 85, 74, 17, 97, 84, 33, 18, 25, 83, 69, 44, 87, 49, + 92, 9, 90, 36, 41, 81, 59, 54, 34, 6, 42, 5, 80, 3, 1, 86, 27, 52, 24, 75}, + }, + { + {61, 85, 41, 37, 12, 25, 27, 17, 92, 52, 44, 66, 13, 15, 19, 78, 77, 9, 40, 20, + 58, 43, 76, 24, 59, 42, 28, 30, 96, 86, 62, 89, 67, 35, 45, 6, 36, 50, 74, 26, + 84, 95, 7, 68, 97, 79, 51, 16, 80, 3, 82, 2, 93, 21, 31, 98, 18, 60, 39, 72, + 54, 65, 73, 32, 5, 70, 99, 10, 14, 71, 34, 4, 64, 33, 55, 48, 75, 87, 57, 81, + 22, 46, 69, 63, 90, 23, 38, 56, 8, 29, 83, 88, 11, 0, 47, 53, 91, 94, 49, 1}, + {35, 22, 67, 24, 94, 82, 12, 68, 30, 43, 58, 96, 27, 69, 65, 18, 7, 79, 14, 2, + 44, 60, 54, 47, 51, 83, 89, 3, 20, 11, 40, 85, 37, 48, 49, 8, 73, 15, 90, 93, + 70, 84, 34, 4, 86, 98, 17, 46, 72, 63, 26, 56, 97, 74, 91, 99, 36, 80, 81, 41, + 53, 88, 77, 21, 0, 23, 64, 76, 95, 33, 62, 1, 75, 87, 10, 66, 57, 6, 28, 25, + 42, 9, 50, 39, 29, 78, 13, 19, 31, 45, 55, 61, 38, 71, 59, 5, 92, 52, 32, 16}, + {70, 29, 50, 57, 31, 9, 59, 45, 87, 71, 98, 89, 77, 97, 86, 5, 21, 61, 47, 41, + 76, 37, 65, 12, 58, 75, 78, 91, 27, 63, 64, 83, 55, 95, 4, 11, 82, 23, 39, 40, + 42, 68, 69, 48, 54, 13, 99, 33, 26, 0, 66, 79, 46, 72, 49, 44, 25, 74, 73, 28, + 38, 10, 2, 85, 15, 19, 52, 94, 7, 36, 17, 32, 96, 3, 20, 30, 92, 51, 14, 90, + 24, 67, 43, 34, 93, 84, 62, 81, 1, 56, 53, 88, 80, 35, 22, 6, 18, 16, 8, 60}, + {62, 75, 38, 50, 52, 86, 53, 73, 99, 59, 26, 46, 48, 90, 64, 1, 65, 13, 30, 36, + 57, 16, 97, 91, 51, 33, 80, 67, 5, 25, 8, 96, 94, 84, 6, 89, 88, 20, 10, 85, + 41, 56, 9, 34, 63, 0, 18, 12, 42, 15, 87, 4, 61, 43, 82, 17, 98, 60, 92, 70, + 49, 71, 47, 54, 79, 83, 27, 74, 58, 2, 76, 93, 22, 69, 78, 44, 28, 95, 72, 68, + 7, 55, 29, 77, 14, 23, 31, 32, 3, 37, 11, 66, 35, 39, 24, 81, 19, 21, 40, 45}, + {65, 78, 7, 83, 80, 77, 71, 72, 22, 85, 63, 73, 44, 87, 86, 88, 41, 30, 95, 67, + 58, 21, 4, 5, 26, 27, 75, 31, 99, 52, 92, 64, 28, 8, 19, 98, 84, 34, 9, 48, + 60, 0, 56, 1, 62, 16, 91, 32, 89, 79, 40, 11, 54, 51, 43, 12, 45, 66, 23, 13, + 96, 81, 47, 36, 46, 93, 2, 69, 33, 3, 20, 6, 35, 18, 74, 68, 53, 37, 29, 61, + 90, 57, 14, 42, 24, 76, 10, 38, 97, 39, 25, 50, 49, 70, 55, 82, 15, 17, 59, 94}, + {11, 17, 21, 48, 63, 0, 73, 14, 10, 88, 2, 28, 6, 9, 19, 69, 81, 13, 35, 95, + 60, 56, 30, 47, 91, 92, 86, 29, 76, 50, 16, 25, 82, 93, 8, 5, 58, 94, 98, 20, + 74, 77, 4, 52, 97, 79, 27, 42, 36, 12, 26, 72, 71, 45, 24, 87, 1, 39, 22, 89, + 53, 31, 61, 37, 33, 18, 59, 43, 80, 15, 54, 38, 78, 90, 32, 83, 23, 44, 55, 66, + 64, 3, 67, 49, 84, 62, 51, 41, 46, 85, 96, 34, 99, 75, 7, 40, 70, 68, 65, 57}, + {43, 38, 48, 47, 74, 88, 64, 54, 59, 35, 29, 18, 53, 91, 68, 66, 12, 79, 55, 41, + 34, 36, 33, 94, 56, 87, 25, 6, 1, 8, 63, 96, 93, 44, 40, 65, 13, 77, 61, 85, + 99, 80, 11, 51, 14, 46, 5, 39, 92, 27, 75, 52, 72, 83, 19, 62, 32, 24, 82, 95, + 17, 37, 0, 15, 97, 4, 42, 86, 84, 3, 26, 81, 98, 9, 73, 22, 28, 78, 60, 76, + 16, 23, 71, 89, 31, 57, 58, 20, 50, 10, 49, 45, 21, 67, 90, 30, 7, 2, 70, 69}, + {78, 35, 94, 59, 8, 23, 10, 76, 96, 80, 93, 9, 27, 95, 61, 84, 88, 91, 2, 33, + 1, 85, 3, 36, 79, 18, 92, 5, 34, 81, 98, 40, 38, 68, 90, 70, 89, 65, 48, 6, + 77, 30, 72, 62, 16, 83, 32, 54, 99, 82, 75, 41, 53, 17, 67, 43, 28, 21, 46, 7, + 25, 64, 13, 14, 87, 58, 63, 31, 39, 47, 24, 71, 74, 55, 20, 11, 22, 45, 44, 51, + 37, 15, 42, 56, 66, 29, 86, 49, 69, 57, 73, 50, 60, 0, 4, 19, 52, 97, 12, 26}, + {88, 86, 41, 1, 70, 10, 19, 2, 11, 44, 87, 26, 53, 59, 40, 18, 61, 13, 21, 56, + 85, 75, 25, 5, 97, 7, 36, 67, 76, 79, 90, 65, 91, 0, 98, 89, 20, 55, 93, 58, + 28, 63, 6, 83, 47, 71, 73, 49, 39, 31, 48, 69, 82, 50, 78, 62, 14, 74, 29, 54, + 57, 80, 32, 94, 3, 66, 30, 35, 8, 46, 16, 51, 24, 17, 4, 84, 45, 60, 34, 15, + 72, 77, 42, 12, 64, 9, 33, 95, 81, 96, 38, 43, 52, 99, 22, 92, 27, 23, 37, 68}, + {90, 93, 83, 21, 96, 71, 67, 28, 37, 75, 55, 58, 56, 39, 16, 46, 91, 41, 77, 92, + 99, 65, 27, 5, 24, 1, 69, 7, 31, 35, 33, 64, 12, 14, 45, 11, 98, 0, 57, 72, + 74, 13, 61, 40, 63, 89, 53, 85, 18, 76, 2, 70, 20, 60, 84, 52, 25, 43, 54, 17, + 36, 19, 97, 73, 94, 62, 3, 23, 34, 88, 29, 80, 50, 22, 81, 87, 6, 30, 78, 48, + 79, 44, 66, 8, 51, 86, 4, 15, 42, 10, 82, 32, 68, 26, 38, 47, 49, 9, 95, 59}, + }, +}; + +static const uint8_t access_code_table_2[10][10] = { + {4, 7, 8, 0, 9, 1, 3, 6, 2, 5}, + {5, 0, 4, 1, 3, 8, 9, 7, 6, 2}, + {0, 2, 7, 8, 9, 1, 6, 3, 5, 4}, + {5, 9, 8, 4, 1, 7, 0, 2, 3, 6}, + {7, 3, 0, 1, 8, 9, 6, 5, 2, 4}, + {7, 2, 4, 0, 1, 9, 6, 3, 5, 8}, + {4, 1, 6, 3, 5, 8, 0, 7, 2, 9}, + {6, 8, 4, 1, 5, 3, 7, 2, 9, 0}, + {4, 1, 8, 3, 7, 2, 0, 6, 5, 9}, + {7, 4, 5, 6, 2, 3, 9, 1, 8, 0}}; + +static void rotate_right(uint8_t* data, int n_bytes, int n_bits) { + uint8_t prior = data[n_bytes - 1]; + for(int i = 0; i < n_bytes; i++) { + uint8_t tmp = data[i]; + data[i] = (data[i] >> n_bits) | ((prior & ((1 << n_bits) - 1)) << (8 - n_bits)); + prior = tmp; + } +} + +static void decrypt_spad_0(const uint8_t* spad, uint8_t* decrypted) { + for(int i = 0; i < 16; i++) { + decrypted[i] = s_box[N_TABLES][spad[i]]; + } + + int count = (decrypted[15] >> 4) + 7; + int table = decrypted[15] + ITERATION_ADD * count; + + for(int iter = 0; iter < count; iter++) { + table -= ITERATION_ADD; + rotate_right(decrypted, 15, 5); // only the first 15 bytes + for(int i = 0; i < 15; i++) { + decrypted[i] = s_box[table % N_TABLES][decrypted[i]]; + } + } +} + +static uint16_t crc16(uint64_t data, int bits, uint16_t init, uint16_t poly) { + uint16_t v = init; + for(int i = 0; i < bits; ++i) { + v = (v >> 1) ^ (((v ^ data) & 1ULL) ? poly : 0); + data >>= 1; + } + return v; +} + +static bool + check_access_code_crc(const uint8_t ac[3], const uint8_t body[6], const uint8_t crc[5]) { + uint64_t msg = 0; + for(int i = 0; i < 3; ++i) { + if(ac[i] > 0xF) return false; + msg = (msg << 4) | ac[i]; + } + for(int i = 0; i < 6; ++i) { + uint8_t v = body[i]; + if(v > 99) return false; + msg = (msg << 4) | (v / 10); + msg = (msg << 4) | (v % 10); + } + uint16_t calculated_crc = crc16(msg, 60, 0xFFFF, 0x8408); + uint16_t expected_crc = crc[0] * 10000 + crc[1] * 1000 + crc[2] * 100 + crc[3] * 10 + crc[4]; + return (calculated_crc == expected_crc); +} + +static void parse_access_code(const uint8_t* access_code, FuriString* parsed_data) { + furi_assert(access_code); + furi_assert(parsed_data); + + uint8_t decrypted[6]; + // decrypted contains the decoded serial bytes (as 6 BCD bytes) + for(int i = 0, j = 3; i < 6; ++i, j += 2) { + decrypted[i] = (access_code[j]) * 10 + (access_code[j + 1]); + } + + uint8_t crc[5] = {0}; + memcpy(crc, access_code + 15, 5); + + int boxes1[6] = { + (crc[3] + crc[2]) % 10, crc[2], crc[3], crc[4], (crc[4] + crc[0]) % 10, crc[1]}; + + for(int n = 0; n < 6; ++n) { + decrypted[n] = access_code_table_1[4][boxes1[n]][decrypted[n]]; + } + + int rv = decrypted[0] / 10; + + int boxes2[6] = { + (crc[1] + crc[0]) % 10, crc[1], crc[2], crc[3], crc[4], (crc[4] + crc[1]) % 10}; + for(int n = 0; n < 6; ++n) { + if(n == 0) { + decrypted[n] = (decrypted[n] & 0xF0) | + access_code_table_2[boxes2[n]][decrypted[n] & 0x0F]; + } else { + decrypted[n] = access_code_table_1[rv][boxes2[n]][decrypted[n]]; + } + } + + furi_string_cat_printf( + parsed_data, + "Decrypted serial number:\n%02d%02d%02d%02d%02d%02d\n", + decrypted[0], + decrypted[1], + decrypted[2], + decrypted[3], + decrypted[4], + decrypted[5]); + + furi_string_cat_printf( + parsed_data, + "CRC check: %s\n", + check_access_code_crc(access_code, decrypted, crc) ? "Passed" : "Invalid"); +} + +bool aic_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + bool parsed = false; + + if(nfc_device_get_protocol(device) != NfcProtocolFelica) return false; + + const FelicaData* data = nfc_device_get_data(device, NfcProtocolFelica); + + const uint8_t ic_type = data->pmm.data[1]; + if(ic_type != 0xF0 && ic_type != 0xF1) { + // Must be Felica Lite (0xF0) or Lite-S (0xF1) to parse + return false; + } + + const uint8_t data_format_code_1 = data->data.fs.id.data[8]; + if(data_format_code_1 != 0) { + // We only know Data Format Code {0x00, 0xXX} + return false; + } + + parsed = true; + furi_string_printf(parsed_data, "\e#Amusement IC Card\n"); + furi_string_cat_str( + parsed_data, "::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"); + furi_string_cat_str(parsed_data, "\nType:\n"); + + // Determine card brand and type + const uint8_t data_format_code_2 = data->data.fs.id.data[9]; + switch(data_format_code_2) { + case 0x2A: + furi_string_cat_str(parsed_data, "Bandai Namco Passport\n"); + break; + case 0x3A: + furi_string_cat_str(parsed_data, "Bandai Namco Passport (Old)\n"); + break; + case 0x68: + furi_string_cat_str(parsed_data, "Konami e-amusement pass\n"); + break; + case 0x78: + furi_string_cat_str(parsed_data, "SEGA Aime\n"); + break; + case 0x79: + furi_string_cat_str(parsed_data, "Taito NESiCA\n"); + break; + default: + parsed = false; + return parsed; // Unknown vendor + } + furi_string_cat_str( + parsed_data, "::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"); + + // decrypt_spad_0 S-PAD 0 + uint8_t decrypted[16] = {0}; + decrypt_spad_0(data->data.fs.spad[0].data, decrypted); + + // Get Access Code + uint8_t access_code[20] = {0}; + for(int i = 0; i < 10; i++) { + access_code[i * 2] = (decrypted[i + 6] & 0xF0) >> 4; // Get upper nibble + access_code[i * 2 + 1] = decrypted[i + 6] & 0x0F; // Get lower nibble + } + furi_string_cat_str(parsed_data, "\nAccess Code:\n"); + bool access_code_is_bcd = true; + for(int i = 0; i < 20; i++) { + furi_string_cat_printf(parsed_data, "%d", access_code[i]); + if(i % 4 == 3) { + furi_string_cat_str(parsed_data, " "); + } + if(access_code[i] > 9) access_code_is_bcd = false; + } + furi_string_cat_str(parsed_data, "\n"); + + furi_string_cat_printf(parsed_data, "BCD valid: %s\n", access_code_is_bcd ? "Yes" : "No"); + furi_string_cat_str( + parsed_data, "::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"); + + // Parse Access Code + if(access_code_is_bcd && access_code[0] == 5) { + furi_string_cat_str(parsed_data, "\n"); + parse_access_code(access_code, parsed_data); + } else { + furi_string_cat_printf( + parsed_data, "\nAccess code preamble wrong: expected 5, got %d\n", access_code[0]); + } + + furi_string_cat_str( + parsed_data, "::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"); + furi_string_cat_str(parsed_data, "\nDecrypted S-PAD 0:\n"); + for(int i = 0; i < 16; i++) { + furi_string_cat_printf(parsed_data, "%02X ", decrypted[i]); + if(i == 7) { + furi_string_cat_str(parsed_data, "\n"); + } + } + furi_string_cat_str(parsed_data, "\n"); + furi_string_cat_str( + parsed_data, "::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin aic_plugin = { + .protocol = NfcProtocolFelica, + .verify = NULL, + .read = NULL, + .parse = aic_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor aic_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &aic_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* aic_plugin_ep(void) { + return &aic_plugin_descriptor; +} From 973fb26f575b70083f328bfc56e409d15a972fd0 Mon Sep 17 00:00:00 2001 From: minchogaydarov <134236905+minchogaydarov@users.noreply.github.com> Date: Wed, 24 Sep 2025 18:23:01 +0300 Subject: [PATCH 073/192] Add Daikin FTXN25LV1B9 and Toyotomi KTN22-12R32 (#4232) Co-authored-by: hedger --- .../infrared/resources/infrared/assets/ac.ir | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/applications/main/infrared/resources/infrared/assets/ac.ir b/applications/main/infrared/resources/infrared/assets/ac.ir index 96b4eb1c8..a7555e007 100644 --- a/applications/main/infrared/resources/infrared/assets/ac.ir +++ b/applications/main/infrared/resources/infrared/assets/ac.ir @@ -1935,3 +1935,79 @@ frequency: 38000 duty_cycle: 0.330000 data: 39670 99106 3227 1570 424 406 422 407 422 1183 424 405 424 1184 448 380 423 407 421 406 424 1183 424 1185 421 406 423 404 424 405 424 1184 423 1182 425 407 448 380 423 407 422 405 423 406 422 406 424 405 423 407 421 406 423 407 422 405 424 405 423 406 423 1184 422 408 421 408 422 405 424 406 421 407 422 406 423 405 423 1183 424 406 423 405 423 405 423 405 423 1186 421 1184 422 1184 422 1185 422 1184 447 1159 423 1184 422 1184 422 408 421 407 423 1184 421 407 448 381 422 405 423 409 421 406 422 406 422 407 422 406 423 1183 423 1185 422 406 423 405 424 1184 423 408 421 405 424 405 424 1184 422 1185 422 1184 422 407 423 408 420 409 420 1185 447 382 423 405 423 408 421 406 423 407 422 406 423 406 423 408 421 406 423 1183 424 407 422 406 424 405 424 406 423 407 423 406 423 408 422 407 422 405 424 408 421 407 422 407 422 406 423 406 423 407 422 406 423 406 422 408 421 407 422 408 421 407 422 406 423 408 422 406 423 405 423 409 422 406 422 406 423 406 423 407 422 407 423 405 424 1184 423 407 421 406 424 1184 423 1184 422 407 423 1183 423 405 424 1184 423 409 420 407 422 # +# Model: Daikin FTXN25LV1B9 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9876 9703 9956 9681 4738 2366 489 310 423 822 488 822 488 311 422 823 487 311 423 310 424 310 423 310 424 823 487 311 422 310 424 823 487 310 424 311 422 310 424 311 422 823 487 311 422 310 423 310 424 823 488 311 423 310 423 310 423 823 488 310 423 310 424 310 424 823 487 310 424 311 423 824 487 823 487 823 487 310 423 823 487 311 423 311 423 310 424 311 422 824 487 310 423 311 422 824 487 311 422 310 424 310 423 824 486 824 487 310 424 310 423 311 423 824 487 311 422 310 423 310 424 310 423 824 486 824 486 310 424 824 486 824 486 824 486 20121 4685 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9907 9648 9958 9677 4742 2362 492 311 422 820 490 820 435 312 477 821 434 312 421 312 422 312 422 876 434 312 422 312 422 312 422 876 434 312 422 312 422 312 421 312 422 876 434 312 422 312 421 312 422 876 434 312 422 311 422 312 477 821 434 312 422 312 421 312 477 821 434 312 422 312 422 876 434 877 434 876 434 312 422 876 434 312 422 311 422 312 422 312 422 876 434 312 422 312 422 876 434 312 422 312 421 312 422 877 433 876 434 312 422 312 422 312 422 876 434 312 422 312 421 312 422 312 422 877 433 312 422 877 433 312 422 877 433 312 422 20174 4740 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9827 9757 9876 9732 4687 2418 435 311 422 876 434 876 434 311 423 877 433 312 421 311 423 311 422 311 423 311 423 311 423 877 433 877 433 311 423 311 423 312 422 877 433 877 433 311 423 311 423 311 423 877 434 311 422 311 423 311 422 877 433 311 423 312 421 312 422 877 433 311 423 312 421 877 433 877 433 877 434 311 423 877 433 311 423 311 423 311 422 311 423 877 433 312 422 311 423 877 433 311 423 312 422 311 423 877 433 878 433 311 423 312 422 312 422 877 433 311 423 311 422 311 423 312 422 878 432 312 422 877 433 311 423 877 433 878 433 20174 4685 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9898 9680 9901 9734 4685 2420 434 312 421 876 434 876 434 312 422 876 434 312 422 312 422 312 422 312 421 312 422 312 422 877 433 877 433 312 422 312 421 312 422 877 433 877 433 312 422 312 422 312 422 876 434 312 422 312 422 312 422 877 434 312 422 312 421 312 422 877 433 312 421 312 422 877 433 877 434 877 433 312 422 877 433 312 422 312 421 312 422 312 422 877 433 312 421 312 422 877 433 312 422 312 422 312 421 312 422 312 422 312 422 312 421 877 433 877 434 312 421 312 422 312 422 312 422 877 433 312 422 877 433 877 433 312 421 877 433 20174 4684 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9932 9649 9959 9677 4742 2362 492 311 422 819 491 819 491 311 422 820 490 311 423 311 423 311 423 311 422 820 490 310 423 310 424 820 490 311 423 311 423 310 424 820 490 311 422 820 491 311 423 311 422 820 491 311 422 311 423 311 423 820 490 311 423 311 423 311 423 820 490 310 424 311 423 820 490 820 490 820 490 310 424 820 490 311 423 310 424 310 424 311 422 820 491 311 422 310 424 820 490 310 424 311 423 310 423 311 423 820 490 820 490 311 423 820 490 311 422 311 423 311 423 311 423 311 422 821 490 311 422 821 490 820 490 311 422 821 490 20118 4740 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9878 9704 9903 9733 4687 2418 435 311 422 875 435 875 435 311 423 875 435 311 423 311 422 311 423 311 423 875 435 311 423 311 423 875 435 311 423 311 423 311 422 876 434 311 423 876 434 311 423 311 423 876 434 311 423 311 423 311 423 876 434 311 423 311 423 311 423 875 435 311 422 311 423 876 434 876 435 876 434 311 423 876 434 311 423 311 423 311 422 311 423 876 434 311 422 311 423 876 434 311 423 311 422 311 423 876 434 876 434 311 423 311 422 311 423 876 435 311 422 311 422 311 423 311 423 876 434 311 423 876 434 311 423 311 423 876 434 20174 4685 +# +# Model: Toyotomi KTN22-12R32 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9067 4414 724 1584 753 452 754 453 753 478 728 478 727 479 726 480 726 481 725 1584 725 1585 724 1584 725 482 724 482 725 482 725 482 724 483 724 483 723 482 725 483 723 483 723 482 724 1585 724 482 724 482 724 483 724 482 725 483 723 483 723 1584 724 482 724 1585 723 482 724 483 723 1585 723 483 723 19925 724 483 724 1585 724 482 724 482 724 482 724 482 724 483 724 482 724 482 724 1585 724 483 724 483 724 482 725 483 724 1585 723 1585 724 483 723 483 724 483 724 483 724 482 723 483 724 482 724 482 724 483 723 483 723 483 724 483 723 483 724 1585 724 1585 723 1585 724 39949 9071 4415 725 482 725 481 725 482 725 483 723 481 725 482 724 483 724 482 724 482 724 482 724 482 724 482 725 483 723 483 723 482 725 483 722 483 724 483 723 482 724 483 724 483 723 482 724 483 723 483 724 483 723 483 723 483 723 483 724 482 724 1585 723 483 724 1585 725 482 724 1585 724 483 724 19926 724 483 723 482 725 482 724 482 724 482 725 482 724 482 725 483 724 482 723 482 725 483 723 482 725 482 725 483 723 483 724 483 723 483 724 483 723 482 724 483 723 483 724 482 725 482 724 483 724 483 723 482 724 483 723 483 723 482 725 1585 723 483 724 1585 724 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9042 4416 751 454 753 1555 753 453 754 1556 753 1558 750 479 726 480 726 482 724 1584 725 481 724 482 725 1585 725 482 725 482 724 482 724 482 725 482 725 483 723 483 724 482 724 482 724 1586 723 1585 723 482 724 483 724 483 724 483 724 482 725 1585 724 482 724 1585 724 484 723 482 724 1585 723 483 724 19928 723 482 725 482 724 482 725 483 724 483 724 482 725 482 724 483 724 483 723 1585 723 482 724 483 723 482 724 483 724 1585 724 1585 724 482 724 482 724 482 725 482 725 482 724 483 724 483 723 483 724 482 724 483 724 483 724 483 725 1585 724 483 723 482 724 1585 723 39946 9070 4414 725 481 725 481 725 482 724 482 724 482 724 481 725 482 724 482 724 482 725 483 723 482 725 483 724 482 725 482 724 482 724 482 724 483 723 483 724 483 724 482 725 483 724 482 724 482 725 483 723 482 725 483 723 482 724 483 724 482 724 1584 724 482 724 1585 724 483 724 1586 723 482 725 19926 724 482 724 482 724 482 725 483 723 482 725 482 725 482 724 483 724 482 724 482 725 483 724 482 725 483 723 483 723 483 723 482 724 483 724 483 723 482 724 483 724 482 724 483 723 483 723 483 724 483 723 483 723 482 724 483 723 483 724 1585 724 483 723 1586 723 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9040 4431 702 503 702 503 702 1604 758 1548 756 448 729 475 730 475 729 477 728 1579 726 1579 727 1580 726 479 726 481 724 480 724 481 724 482 724 482 723 482 723 482 723 482 723 483 722 1583 724 1584 722 483 723 483 722 482 723 483 722 483 723 1584 722 482 724 1584 722 482 723 483 722 1585 721 483 723 19907 724 481 723 482 723 482 723 482 723 482 723 482 723 483 723 483 722 482 723 1584 722 482 722 482 723 483 723 482 723 1584 723 1584 721 483 722 483 722 483 722 484 721 482 723 483 723 483 722 483 722 483 723 483 722 484 721 484 721 1607 699 483 722 483 722 1608 698 39905 9065 4406 726 479 726 480 724 480 725 481 725 481 724 482 723 482 723 482 724 482 723 483 723 482 723 482 723 482 723 482 724 483 722 482 723 483 723 483 723 483 722 483 723 483 722 483 722 483 722 507 699 482 723 484 722 483 722 483 722 483 722 1607 699 483 722 1585 722 483 722 1584 723 506 698 19908 723 481 724 482 723 482 724 482 722 482 724 482 724 482 723 483 722 482 723 483 722 482 723 483 722 483 722 483 722 482 723 506 699 483 722 484 722 482 723 507 700 484 721 483 722 484 722 483 722 507 698 506 699 507 699 507 698 507 698 1608 698 507 698 1607 699 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9074 4437 701 506 700 506 700 1607 702 1607 702 505 701 505 701 505 726 480 727 480 726 1582 726 1584 725 1583 724 481 726 481 725 482 724 483 724 482 724 482 725 482 724 482 725 483 724 1584 725 1584 725 482 724 482 726 482 724 482 725 483 724 1584 724 483 723 1585 723 483 724 482 725 1584 725 483 723 19925 724 482 724 482 724 482 725 482 725 482 725 483 724 482 724 482 724 482 724 1584 724 483 724 482 724 481 724 482 724 1585 724 1585 724 482 724 482 724 482 725 483 723 483 724 483 724 482 724 482 724 483 723 482 725 482 724 482 724 483 724 482 724 483 724 483 723 39945 9072 4414 725 481 726 481 725 482 724 482 725 481 725 482 724 482 724 482 725 482 724 483 724 483 724 483 723 482 725 482 724 482 724 483 723 482 724 483 724 483 723 483 724 483 724 482 725 482 725 483 724 483 724 482 725 483 724 482 724 482 725 1585 724 483 724 1584 724 482 724 1585 724 483 723 19929 723 482 724 482 725 482 724 483 724 483 723 483 723 482 724 482 724 482 724 482 724 482 725 482 724 482 724 483 724 483 723 482 724 482 724 483 724 483 723 482 725 483 724 482 724 482 725 482 724 483 724 483 724 483 724 483 724 482 724 1585 723 483 724 1585 724 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9150 4356 783 1525 815 419 787 419 759 1549 760 447 758 447 758 449 756 451 755 451 755 451 754 452 754 453 754 452 754 453 753 453 753 454 753 454 752 453 753 454 752 454 753 455 751 1557 725 1585 725 481 725 481 725 482 725 482 725 482 724 1584 725 482 724 1584 725 482 724 481 726 1584 724 482 724 19927 724 481 726 482 724 482 725 481 725 481 725 482 725 482 724 481 726 481 725 1584 724 481 725 481 725 482 725 481 725 1584 725 1585 724 482 725 482 724 482 725 482 724 482 725 482 724 481 725 482 725 482 724 482 725 482 724 482 724 1584 724 1584 724 1584 724 1585 725 39945 9071 4412 726 480 726 480 726 481 725 481 725 481 725 481 725 481 725 482 725 481 724 481 726 482 725 481 725 482 724 482 724 482 725 482 724 481 725 482 725 482 725 482 724 482 724 482 725 481 725 482 725 482 725 481 726 482 724 482 724 481 726 1584 725 482 724 1584 724 482 724 1585 724 482 725 19924 725 481 725 481 726 481 725 481 725 481 726 481 724 482 725 482 724 481 725 481 725 481 725 481 726 482 725 481 725 481 725 482 724 481 752 455 752 454 753 455 751 454 752 454 753 454 751 453 753 454 752 454 753 454 753 454 752 455 752 1556 753 453 754 1557 752 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9049 4436 702 1606 703 504 730 476 759 1548 758 448 730 476 730 476 730 477 729 1580 727 1581 727 1581 727 480 726 481 725 481 725 481 724 482 724 507 699 483 724 483 723 507 699 506 701 1609 699 1609 699 507 700 507 699 507 700 507 699 507 700 1610 699 507 700 1609 700 507 699 507 700 1610 699 507 700 19928 723 482 724 483 723 483 723 484 722 507 699 507 700 507 699 507 699 507 700 1609 700 508 699 507 700 507 699 507 699 1609 700 1609 699 507 699 508 699 507 699 507 700 507 700 507 700 507 699 507 699 507 700 507 699 507 700 507 699 507 700 1610 699 1610 699 507 700 39944 9074 4410 727 479 727 480 726 481 726 481 725 482 725 483 723 483 724 507 699 507 700 484 722 507 699 507 699 507 699 506 700 507 699 507 700 507 699 507 698 507 699 508 699 507 699 507 699 507 700 507 700 507 698 507 699 508 699 507 699 507 699 1610 699 507 699 1610 699 507 699 1609 699 507 699 19926 724 483 723 506 700 507 699 507 700 507 700 507 700 507 699 507 700 507 700 507 699 507 700 507 699 507 699 507 700 506 700 508 699 507 700 507 700 507 699 507 699 507 700 507 700 507 700 508 699 507 699 507 699 508 699 507 699 507 700 1610 698 507 699 1609 699 +# \ No newline at end of file From 5c54be4d9c241b1244da8487944f7b13c96bae95 Mon Sep 17 00:00:00 2001 From: Alexander Bays Date: Wed, 24 Sep 2025 10:45:40 -0500 Subject: [PATCH 074/192] Infrared: Add text scroll to remote buttons (#4210) * Infrared: Add text scroll to universal remote buttons Replaces center aligned text in the infrared universal remote with scrollable text if wider than the button and is cut off. Allows long descriptive button functions to be seen in some remotes. * linter fixes --------- Co-authored-by: hedger Co-authored-by: hedger --- .../services/gui/modules/button_menu.c | 116 +++++++++++++----- 1 file changed, 88 insertions(+), 28 deletions(-) diff --git a/applications/services/gui/modules/button_menu.c b/applications/services/gui/modules/button_menu.c index d9c178dd2..5204c6f22 100644 --- a/applications/services/gui/modules/button_menu.c +++ b/applications/services/gui/modules/button_menu.c @@ -10,6 +10,7 @@ #include #include +#define SCROLL_INTERVAL (333) #define ITEM_FIRST_OFFSET 17 #define ITEM_NEXT_OFFSET 4 #define ITEM_HEIGHT 14 @@ -35,13 +36,56 @@ typedef struct { ButtonMenuItemArray_t items; size_t position; const char* header; + size_t scroll_counter; + FuriTimer* scroll_timer; } ButtonMenuModel; +static void button_menu_draw_text( + Canvas* canvas, + uint8_t item_x, + uint8_t item_y, + const char* text, + bool selected, + ButtonMenuModel* model) { + FuriString* disp_str; + disp_str = furi_string_alloc_set(text); + bool draw_static = true; + + if(selected) { + size_t text_width = canvas_string_width(canvas, furi_string_get_cstr(disp_str)); + if(text_width >= ITEM_WIDTH - 8) { + elements_scrollable_text_line( + canvas, + item_x + 4, + item_y + ITEM_HEIGHT - 4, + ITEM_WIDTH - 8, + disp_str, + model->scroll_counter, + false); + draw_static = false; + } + } + + if(draw_static) { + elements_string_fit_width(canvas, disp_str, ITEM_WIDTH - 6); + canvas_draw_str_aligned( + canvas, + item_x + (ITEM_WIDTH / 2), + item_y + (ITEM_HEIGHT / 2), + AlignCenter, + AlignCenter, + furi_string_get_cstr(disp_str)); + } + + furi_string_free(disp_str); +} + static void button_menu_draw_control_button( Canvas* canvas, uint8_t item_position, const char* text, - bool selected) { + bool selected, + ButtonMenuModel* model) { furi_assert(canvas); furi_assert(text); @@ -54,20 +98,16 @@ static void button_menu_draw_control_button( elements_slightly_rounded_box(canvas, item_x, item_y, ITEM_WIDTH, ITEM_HEIGHT); canvas_set_color(canvas, ColorWhite); } - canvas_draw_str_aligned( - canvas, - item_x + (ITEM_WIDTH / 2), - item_y + (ITEM_HEIGHT / 2), - AlignCenter, - AlignCenter, - text); + + button_menu_draw_text(canvas, item_x, item_y, text, selected, model); } static void button_menu_draw_common_button( Canvas* canvas, uint8_t item_position, const char* text, - bool selected) { + bool selected, + ButtonMenuModel* model) { furi_assert(canvas); furi_assert(text); @@ -83,19 +123,7 @@ static void button_menu_draw_common_button( canvas_draw_rframe(canvas, item_x, item_y, ITEM_WIDTH, ITEM_HEIGHT, 5); } - FuriString* disp_str; - disp_str = furi_string_alloc_set(text); - elements_string_fit_width(canvas, disp_str, ITEM_WIDTH - 6); - - canvas_draw_str_aligned( - canvas, - item_x + (ITEM_WIDTH / 2), - item_y + (ITEM_HEIGHT / 2), - AlignCenter, - AlignCenter, - furi_string_get_cstr(disp_str)); - - furi_string_free(disp_str); + button_menu_draw_text(canvas, item_x, item_y, text, selected, model); } static void button_menu_view_draw_callback(Canvas* canvas, void* _model) { @@ -120,9 +148,17 @@ static void button_menu_view_draw_callback(Canvas* canvas, void* _model) { if(model->header) { FuriString* disp_str; disp_str = furi_string_alloc_set(model->header); - elements_string_fit_width(canvas, disp_str, ITEM_WIDTH - 6); - canvas_draw_str_aligned( - canvas, 32, 10, AlignCenter, AlignCenter, furi_string_get_cstr(disp_str)); + size_t header_width = canvas_string_width(canvas, furi_string_get_cstr(disp_str)); + + if(header_width >= ITEM_WIDTH - 8) { + elements_scrollable_text_line( + canvas, 3, 13, ITEM_WIDTH - 8, disp_str, model->scroll_counter, false); + } else { + elements_string_fit_width(canvas, disp_str, ITEM_WIDTH - 8); + canvas_draw_str_aligned( + canvas, 32, 10, AlignCenter, AlignCenter, furi_string_get_cstr(disp_str)); + } + furi_string_free(disp_str); } @@ -137,13 +173,15 @@ static void button_menu_view_draw_callback(Canvas* canvas, void* _model) { canvas, item_position % BUTTONS_PER_SCREEN, ButtonMenuItemArray_cref(it)->label, - (item_position == model->position)); + (item_position == model->position), + model); } else if(ButtonMenuItemArray_cref(it)->type == ButtonMenuItemTypeCommon) { button_menu_draw_common_button( canvas, item_position % BUTTONS_PER_SCREEN, ButtonMenuItemArray_cref(it)->label, - (item_position == model->position)); + (item_position == model->position), + model); } } } @@ -158,8 +196,10 @@ static void button_menu_process_up(ButtonMenu* button_menu) { { if(model->position > 0) { model->position--; + model->scroll_counter = 0; } else { model->position = ButtonMenuItemArray_size(model->items) - 1; + model->scroll_counter = 0; } }, true); @@ -174,8 +214,10 @@ static void button_menu_process_down(ButtonMenu* button_menu) { { if(model->position < (ButtonMenuItemArray_size(model->items) - 1)) { model->position++; + model->scroll_counter = 0; } else { model->position = 0; + model->scroll_counter = 0; } }, true); @@ -193,8 +235,10 @@ static void button_menu_process_right(ButtonMenu* button_menu) { position_candidate -= position_candidate % BUTTONS_PER_SCREEN; if(position_candidate < (ButtonMenuItemArray_size(model->items))) { model->position = position_candidate; + model->scroll_counter = 0; } else { model->position = 0; + model->scroll_counter = 0; } } }, @@ -217,6 +261,7 @@ static void button_menu_process_left(ButtonMenu* button_menu) { }; position_candidate -= position_candidate % BUTTONS_PER_SCREEN; model->position = position_candidate; + model->scroll_counter = 0; } }, true); @@ -314,6 +359,7 @@ void button_menu_reset(ButtonMenu* button_menu) { ButtonMenuItemArray_reset(model->items); model->position = 0; model->header = NULL; + model->scroll_counter = 0; }, true); } @@ -351,6 +397,12 @@ ButtonMenuItem* button_menu_add_item( return item; } +static void button_menu_process_timer_callback(void* context) { + ButtonMenu* button_menu = context; + with_view_model( + button_menu->view, ButtonMenuModel * model, { model->scroll_counter++; }, true); +} + ButtonMenu* button_menu_alloc(void) { ButtonMenu* button_menu = malloc(sizeof(ButtonMenu)); button_menu->view = view_alloc(); @@ -367,6 +419,10 @@ ButtonMenu* button_menu_alloc(void) { ButtonMenuItemArray_init(model->items); model->position = 0; model->header = NULL; + model->scroll_counter = 0; + model->scroll_timer = furi_timer_alloc( + button_menu_process_timer_callback, FuriTimerTypePeriodic, button_menu); + furi_timer_start(model->scroll_timer, SCROLL_INTERVAL); }, true); @@ -380,7 +436,11 @@ void button_menu_free(ButtonMenu* button_menu) { with_view_model( button_menu->view, ButtonMenuModel * model, - { ButtonMenuItemArray_clear(model->items); }, + { + ButtonMenuItemArray_clear(model->items); + furi_timer_stop(model->scroll_timer); + furi_timer_free(model->scroll_timer); + }, true); view_free(button_menu->view); free(button_menu); From 30077dd512deb6e0c8033025e4413e755507296e Mon Sep 17 00:00:00 2001 From: MMX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 24 Sep 2025 20:00:39 +0300 Subject: [PATCH 075/192] Fix PVS warnings (#4277) * Fixing PVS warns * pvs: additional fixes --------- Co-authored-by: hedger --- applications/debug/bt_debug_app/views/bt_test.c | 2 +- applications/main/infrared/infrared_brute_force.c | 2 +- applications/main/nfc/helpers/mfkey32_logger.c | 2 +- applications/main/nfc/helpers/nfc_supported_cards.c | 2 +- applications/main/onewire/onewire_cli.c | 2 +- .../subghz_frequency_analyzer_log_item_array.h | 2 +- applications/main/subghz/subghz_history.c | 2 +- applications/main/subghz/views/receiver.c | 2 +- .../desktop/animations/views/bubble_animation_view.c | 11 ++++++++--- applications/services/gui/canvas_i.h | 2 +- applications/services/gui/elements.c | 2 +- applications/services/gui/modules/button_menu.c | 2 +- applications/services/gui/modules/button_panel.c | 2 +- .../services/gui/modules/file_browser_worker.c | 4 ++-- applications/services/gui/modules/menu.c | 2 +- .../services/gui/modules/variable_item_list.c | 2 +- .../widget_elements/widget_element_text_scroll.c | 2 +- applications/services/gui/scene_manager_i.h | 2 +- applications/services/loader/loader.c | 10 ++++++++-- applications/services/rpc/rpc_storage.c | 2 +- applications/services/rpc/rpc_system.c | 3 ++- .../scenes/desktop_settings_scene_favorite.c | 4 ++-- applications/system/js_app/js_modules.c | 2 +- applications/system/js_app/modules/js_serial.c | 2 +- lib/drivers/lp5562.c | 3 +++ lib/music_worker/music_worker.c | 2 +- lib/print/printf_tiny.c | 2 +- lib/subghz/protocols/bin_raw.c | 4 ++-- lib/subghz/receiver.c | 2 +- lib/subghz/subghz_keystore.h | 2 +- lib/subghz/subghz_setting.c | 2 +- lib/toolbox/cli/shell/cli_shell_completions.c | 2 +- lib/toolbox/strint.c | 2 +- targets/f7/src/stm32wb55_startup.c | 2 +- 34 files changed, 54 insertions(+), 39 deletions(-) diff --git a/applications/debug/bt_debug_app/views/bt_test.c b/applications/debug/bt_debug_app/views/bt_test.c index 58b65e194..c15c6a5e4 100644 --- a/applications/debug/bt_debug_app/views/bt_test.c +++ b/applications/debug/bt_debug_app/views/bt_test.c @@ -18,7 +18,7 @@ struct BtTestParam { void* context; }; -ARRAY_DEF(BtTestParamArray, BtTestParam, M_POD_OPLIST); +ARRAY_DEF(BtTestParamArray, BtTestParam, M_POD_OPLIST); //-V658 struct BtTest { View* view; diff --git a/applications/main/infrared/infrared_brute_force.c b/applications/main/infrared/infrared_brute_force.c index 1ec4645e9..519bbbb4b 100644 --- a/applications/main/infrared/infrared_brute_force.c +++ b/applications/main/infrared/infrared_brute_force.c @@ -7,7 +7,7 @@ #include "infrared_signal.h" -ARRAY_DEF(SignalPositionArray, size_t, M_DEFAULT_OPLIST); +ARRAY_DEF(SignalPositionArray, size_t, M_DEFAULT_OPLIST); //-V658 typedef struct { size_t index; diff --git a/applications/main/nfc/helpers/mfkey32_logger.c b/applications/main/nfc/helpers/mfkey32_logger.c index bdd899a36..ce3b875cf 100644 --- a/applications/main/nfc/helpers/mfkey32_logger.c +++ b/applications/main/nfc/helpers/mfkey32_logger.c @@ -21,7 +21,7 @@ typedef struct { uint32_t ar1; } Mfkey32LoggerParams; -ARRAY_DEF(Mfkey32LoggerParams, Mfkey32LoggerParams, M_POD_OPLIST); +ARRAY_DEF(Mfkey32LoggerParams, Mfkey32LoggerParams, M_POD_OPLIST); //-V658 struct Mfkey32Logger { uint32_t cuid; diff --git a/applications/main/nfc/helpers/nfc_supported_cards.c b/applications/main/nfc/helpers/nfc_supported_cards.c index 6513eef5f..77c33b142 100644 --- a/applications/main/nfc/helpers/nfc_supported_cards.c +++ b/applications/main/nfc/helpers/nfc_supported_cards.c @@ -29,7 +29,7 @@ typedef struct { NfcSupportedCardsPluginFeature feature; } NfcSupportedCardsPluginCache; -ARRAY_DEF(NfcSupportedCardsPluginCache, NfcSupportedCardsPluginCache, M_POD_OPLIST); +ARRAY_DEF(NfcSupportedCardsPluginCache, NfcSupportedCardsPluginCache, M_POD_OPLIST); //-V658 typedef enum { NfcSupportedCardsLoadStateIdle, diff --git a/applications/main/onewire/onewire_cli.c b/applications/main/onewire/onewire_cli.c index 193de76e4..17e5aaf55 100644 --- a/applications/main/onewire/onewire_cli.c +++ b/applications/main/onewire/onewire_cli.c @@ -25,7 +25,7 @@ static void onewire_cli_search(PipeSide* pipe) { onewire_host_start(onewire); power_enable_otg(power, true); - while(!done) { + while(!done) { //-V1044 if(onewire_host_search(onewire, address, OneWireHostSearchModeNormal) != 1) { printf("Search finished\r\n"); onewire_host_reset_search(onewire); diff --git a/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h b/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h index 6e6b553e0..a3086c508 100644 --- a/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h +++ b/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h @@ -35,7 +35,7 @@ TUPLE_DEF2( //-V1048 M_DEFAULT_OPLIST) /* Define the array, register the oplist and define further algorithms on it */ -ARRAY_DEF(SubGhzFrequencyAnalyzerLogItemArray, SubGhzFrequencyAnalyzerLogItem_t) //-V779 +ARRAY_DEF(SubGhzFrequencyAnalyzerLogItemArray, SubGhzFrequencyAnalyzerLogItem_t) //-V779 //-V658 #define M_OPL_SubGhzFrequencyAnalyzerLogItemArray_t() \ ARRAY_OPLIST(SubGhzFrequencyAnalyzerLogItemArray, M_OPL_SubGhzFrequencyAnalyzerLogItem_t()) ALGO_DEF(SubGhzFrequencyAnalyzerLogItemArray, SubGhzFrequencyAnalyzerLogItemArray_t) diff --git a/applications/main/subghz/subghz_history.c b/applications/main/subghz/subghz_history.c index 85e59ae07..8043b5752 100644 --- a/applications/main/subghz/subghz_history.c +++ b/applications/main/subghz/subghz_history.c @@ -16,7 +16,7 @@ typedef struct { SubGhzRadioPreset* preset; } SubGhzHistoryItem; -ARRAY_DEF(SubGhzHistoryItemArray, SubGhzHistoryItem, M_POD_OPLIST) +ARRAY_DEF(SubGhzHistoryItemArray, SubGhzHistoryItem, M_POD_OPLIST) //-V658 #define M_OPL_SubGhzHistoryItemArray_t() ARRAY_OPLIST(SubGhzHistoryItemArray, M_POD_OPLIST) diff --git a/applications/main/subghz/views/receiver.c b/applications/main/subghz/views/receiver.c index ea74f8c6c..cec68abbf 100644 --- a/applications/main/subghz/views/receiver.c +++ b/applications/main/subghz/views/receiver.c @@ -18,7 +18,7 @@ typedef struct { uint8_t type; } SubGhzReceiverMenuItem; -ARRAY_DEF(SubGhzReceiverMenuItemArray, SubGhzReceiverMenuItem, M_POD_OPLIST) +ARRAY_DEF(SubGhzReceiverMenuItemArray, SubGhzReceiverMenuItem, M_POD_OPLIST) //-V658 #define M_OPL_SubGhzReceiverMenuItemArray_t() \ ARRAY_OPLIST(SubGhzReceiverMenuItemArray, M_POD_OPLIST) diff --git a/applications/services/desktop/animations/views/bubble_animation_view.c b/applications/services/desktop/animations/views/bubble_animation_view.c index cea039671..82740237c 100644 --- a/applications/services/desktop/animations/views/bubble_animation_view.c +++ b/applications/services/desktop/animations/views/bubble_animation_view.c @@ -93,12 +93,17 @@ static const FrameBubble* bubble_animation_pick_bubble(BubbleAnimationViewModel* model, bool active) { const FrameBubble* bubble = NULL; - if((model->active_bubbles == 0) && (model->passive_bubbles == 0)) { + // Check for division by zero based on the active parameter + if((active && model->active_bubbles == 0) || (!active && model->passive_bubbles == 0)) { return NULL; } - uint8_t index = - furi_hal_random_get() % (active ? model->active_bubbles : model->passive_bubbles); + uint8_t random_value = furi_hal_random_get(); + // In case random generator return zero lets set it to 3 + if(random_value == 0) { + random_value = 3; + } + uint8_t index = random_value % (active ? model->active_bubbles : model->passive_bubbles); const BubbleAnimation* animation = model->current; for(int i = 0; i < animation->frame_bubble_sequences_count; ++i) { diff --git a/applications/services/gui/canvas_i.h b/applications/services/gui/canvas_i.h index 449db71db..cc918f8df 100644 --- a/applications/services/gui/canvas_i.h +++ b/applications/services/gui/canvas_i.h @@ -29,7 +29,7 @@ typedef struct { void* context; } CanvasCallbackPair; -ARRAY_DEF(CanvasCallbackPairArray, CanvasCallbackPair, M_POD_OPLIST); +ARRAY_DEF(CanvasCallbackPairArray, CanvasCallbackPair, M_POD_OPLIST); //-V658 #define M_OPL_CanvasCallbackPairArray_t() ARRAY_OPLIST(CanvasCallbackPairArray, M_POD_OPLIST) diff --git a/applications/services/gui/elements.c b/applications/services/gui/elements.c index 3e12c09cc..f4caf7da7 100644 --- a/applications/services/gui/elements.c +++ b/applications/services/gui/elements.c @@ -854,7 +854,7 @@ void elements_text_box( for(size_t i = 0; i < line_num; i++) { for(size_t j = 0; j < line[i].len; j++) { // Process format symbols - if(line[i].text[j] == '\e' && j < line[i].len - 1) { + if(line[i].text[j] == '\e' && j < line[i].len - 1) { //-V781 ++j; if(line[i].text[j] == ELEMENTS_BOLD_MARKER) { if(bold) { diff --git a/applications/services/gui/modules/button_menu.c b/applications/services/gui/modules/button_menu.c index 5204c6f22..095c57b3c 100644 --- a/applications/services/gui/modules/button_menu.c +++ b/applications/services/gui/modules/button_menu.c @@ -25,7 +25,7 @@ struct ButtonMenuItem { void* callback_context; }; -ARRAY_DEF(ButtonMenuItemArray, ButtonMenuItem, M_POD_OPLIST); +ARRAY_DEF(ButtonMenuItemArray, ButtonMenuItem, M_POD_OPLIST); //-V658 struct ButtonMenu { View* view; diff --git a/applications/services/gui/modules/button_panel.c b/applications/services/gui/modules/button_panel.c index efc512bc5..c718649b1 100644 --- a/applications/services/gui/modules/button_panel.c +++ b/applications/services/gui/modules/button_panel.c @@ -98,7 +98,7 @@ void button_panel_reserve(ButtonPanel* button_panel, size_t reserve_x, size_t re furi_check(reserve_x > 0); furi_check(reserve_y > 0); - with_view_model( + with_view_model( //-V621 button_panel->view, ButtonPanelModel * model, { diff --git a/applications/services/gui/modules/file_browser_worker.c b/applications/services/gui/modules/file_browser_worker.c index 521666e6d..457345ff2 100644 --- a/applications/services/gui/modules/file_browser_worker.c +++ b/applications/services/gui/modules/file_browser_worker.c @@ -32,8 +32,8 @@ typedef enum { (WorkerEvtStop | WorkerEvtLoad | WorkerEvtFolderEnter | WorkerEvtFolderExit | \ WorkerEvtFolderRefresh | WorkerEvtConfigChange) -ARRAY_DEF(IdxLastArray, int32_t) -ARRAY_DEF(ExtFilterArray, FuriString*, FURI_STRING_OPLIST) +ARRAY_DEF(IdxLastArray, int32_t) //-V658 +ARRAY_DEF(ExtFilterArray, FuriString*, FURI_STRING_OPLIST) //-V658 struct BrowserWorker { FuriThread* thread; diff --git a/applications/services/gui/modules/menu.c b/applications/services/gui/modules/menu.c index 012fa17d6..5e9452b53 100644 --- a/applications/services/gui/modules/menu.c +++ b/applications/services/gui/modules/menu.c @@ -17,7 +17,7 @@ typedef struct { void* callback_context; } MenuItem; -ARRAY_DEF(MenuItemArray, MenuItem, M_POD_OPLIST); +ARRAY_DEF(MenuItemArray, MenuItem, M_POD_OPLIST); //-V658 #define M_OPL_MenuItemArray_t() ARRAY_OPLIST(MenuItemArray, M_POD_OPLIST) diff --git a/applications/services/gui/modules/variable_item_list.c b/applications/services/gui/modules/variable_item_list.c index f8110034e..aec0c7080 100644 --- a/applications/services/gui/modules/variable_item_list.c +++ b/applications/services/gui/modules/variable_item_list.c @@ -14,7 +14,7 @@ struct VariableItem { void* context; }; -ARRAY_DEF(VariableItemArray, VariableItem, M_POD_OPLIST); +ARRAY_DEF(VariableItemArray, VariableItem, M_POD_OPLIST); //-V658 struct VariableItemList { View* view; diff --git a/applications/services/gui/modules/widget_elements/widget_element_text_scroll.c b/applications/services/gui/modules/widget_elements/widget_element_text_scroll.c index 491ffc6bc..866aad815 100644 --- a/applications/services/gui/modules/widget_elements/widget_element_text_scroll.c +++ b/applications/services/gui/modules/widget_elements/widget_element_text_scroll.c @@ -10,7 +10,7 @@ typedef struct { FuriString* text; } TextScrollLineArray; -ARRAY_DEF(TextScrollLineArray, TextScrollLineArray, M_POD_OPLIST) +ARRAY_DEF(TextScrollLineArray, TextScrollLineArray, M_POD_OPLIST) //-V658 typedef struct { TextScrollLineArray_t line_array; diff --git a/applications/services/gui/scene_manager_i.h b/applications/services/gui/scene_manager_i.h index 85f7e7bee..8bfe46993 100644 --- a/applications/services/gui/scene_manager_i.h +++ b/applications/services/gui/scene_manager_i.h @@ -8,7 +8,7 @@ #include "scene_manager.h" #include -ARRAY_DEF(SceneManagerIdStack, uint32_t, M_DEFAULT_OPLIST); +ARRAY_DEF(SceneManagerIdStack, uint32_t, M_DEFAULT_OPLIST); //-V658 typedef struct { uint32_t state; diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 7b37f2510..341994a23 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -88,6 +88,7 @@ static void loader_show_gui_error( LoaderMessageLoaderStatusResult status, const char* name, FuriString* error_message) { + furi_check(name); DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); DialogMessage* message = dialog_message_alloc(); @@ -609,6 +610,8 @@ static LoaderMessageLoaderStatusResult loader_do_start_by_name( status.value = loader_make_success_status(error_message); status.error = LoaderStatusErrorUnknown; + if(name == NULL) return status; + LoaderEvent event; event.type = LoaderEventTypeApplicationBeforeLoad; furi_pubsub_publish(loader->pubsub, &event); @@ -835,7 +838,10 @@ int32_t loader_srv(void* p) { switch(message.type) { case LoaderMessageTypeStartByName: { LoaderMessageLoaderStatusResult status = loader_do_start_by_name( - loader, message.start.name, message.start.args, message.start.error_message); + loader, + message.start.name, + message.start.args, + message.start.error_message); //-V595 *(message.status_value) = status; if(status.value != LoaderStatusOk) loader_do_emit_queue_empty_event(loader); api_lock_unlock(message.api_lock); @@ -844,7 +850,7 @@ int32_t loader_srv(void* p) { case LoaderMessageTypeStartByNameDetachedWithGuiError: { FuriString* error_message = furi_string_alloc(); LoaderMessageLoaderStatusResult status = loader_do_start_by_name( - loader, message.start.name, message.start.args, error_message); + loader, message.start.name, message.start.args, error_message); //-V595 loader_show_gui_error(status, message.start.name, error_message); if(status.value != LoaderStatusOk) loader_do_emit_queue_empty_event(loader); if(message.start.name) free((void*)message.start.name); diff --git a/applications/services/rpc/rpc_storage.c b/applications/services/rpc/rpc_storage.c index 163535f9a..df65f84a1 100644 --- a/applications/services/rpc/rpc_storage.c +++ b/applications/services/rpc/rpc_storage.c @@ -293,7 +293,7 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex finish = true; } - while(!finish) { + while(!finish) { //-V1044 FileInfo fileinfo; char* name = malloc(MAX_NAME_LENGTH + 1); if(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) { diff --git a/applications/services/rpc/rpc_system.c b/applications/services/rpc/rpc_system.c index 1cc0f90eb..4bf923d7f 100644 --- a/applications/services/rpc/rpc_system.c +++ b/applications/services/rpc/rpc_system.c @@ -30,7 +30,8 @@ static void rpc_system_system_ping_process(const PB_Main* request, void* context } PB_Main response = PB_Main_init_default; - response.command_status = PB_CommandStatus_OK; + // PB_CommandStatus_OK is 0 in current case, and var by default is 0 too, commenting PVS warn + response.command_status = PB_CommandStatus_OK; //-V1048 response.command_id = request->command_id; response.which_content = PB_Main_system_ping_response_tag; diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c index 17ef88358..dee7ad7bd 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c @@ -76,7 +76,7 @@ void desktop_settings_scene_favorite_on_enter(void* context) { default_passport = true; } } else { - favorite_id &= ~(SCENE_STATE_SET_DUMMY_APP); + favorite_id &= ~(SCENE_STATE_SET_DUMMY_APP); //-V784 furi_assert(favorite_id < DummyAppNumber); curr_favorite_app = &app->settings.dummy_apps[favorite_id]; default_passport = true; @@ -182,7 +182,7 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e furi_assert(favorite_id < FavoriteAppNumber); curr_favorite_app = &app->settings.favorite_apps[favorite_id]; } else { - favorite_id &= ~(SCENE_STATE_SET_DUMMY_APP); + favorite_id &= ~(SCENE_STATE_SET_DUMMY_APP); //-V784 furi_assert(favorite_id < DummyAppNumber); curr_favorite_app = &app->settings.dummy_apps[favorite_id]; } diff --git a/applications/system/js_app/js_modules.c b/applications/system/js_app/js_modules.c index 095880f17..d7f38ba04 100644 --- a/applications/system/js_app/js_modules.c +++ b/applications/system/js_app/js_modules.c @@ -27,7 +27,7 @@ typedef struct { // - an rbtree because i deemed it more tedious to implement, and with the // amount of modules in use (under 10 in the overwhelming majority of cases) // i bet it's going to be slower than a plain array -ARRAY_DEF(JsModuleArray, JsModuleData, M_POD_OPLIST); +ARRAY_DEF(JsModuleArray, JsModuleData, M_POD_OPLIST); //-V658 #define M_OPL_JsModuleArray_t() ARRAY_OPLIST(JsModuleArray) static const JsModuleDescriptor modules_builtin[] = { diff --git a/applications/system/js_app/modules/js_serial.c b/applications/system/js_app/modules/js_serial.c index d903939ce..78f4db63a 100644 --- a/applications/system/js_app/modules/js_serial.c +++ b/applications/system/js_app/modules/js_serial.c @@ -20,7 +20,7 @@ typedef struct { char* data; } PatternArrayItem; -ARRAY_DEF(PatternArray, PatternArrayItem, M_POD_OPLIST); +ARRAY_DEF(PatternArray, PatternArrayItem, M_POD_OPLIST); //-V658 static void js_serial_on_async_rx(FuriHalSerialHandle* handle, FuriHalSerialRxEvent event, void* context) { diff --git a/lib/drivers/lp5562.c b/lib/drivers/lp5562.c index 7db9bbce4..18f998997 100644 --- a/lib/drivers/lp5562.c +++ b/lib/drivers/lp5562.c @@ -192,6 +192,9 @@ void lp5562_execute_ramp( // Prepare command sequence uint16_t program[16]; uint8_t diff = (val_end > val_start) ? (val_end - val_start) : (val_start - val_end); + if(diff == 0) { // Making division below safer + diff = 1; + } uint16_t time_step = time * 2 / diff; uint8_t prescaller = 0; if(time_step > 0x3F) { diff --git a/lib/music_worker/music_worker.c b/lib/music_worker/music_worker.c index ce91f6029..420f17f29 100644 --- a/lib/music_worker/music_worker.c +++ b/lib/music_worker/music_worker.c @@ -26,7 +26,7 @@ typedef struct { uint8_t dots; } NoteBlock; -ARRAY_DEF(NoteBlockArray, NoteBlock, M_POD_OPLIST); +ARRAY_DEF(NoteBlockArray, NoteBlock, M_POD_OPLIST); //-V658 struct MusicWorker { FuriThread* thread; diff --git a/lib/print/printf_tiny.c b/lib/print/printf_tiny.c index 387a6451e..96d979450 100644 --- a/lib/print/printf_tiny.c +++ b/lib/print/printf_tiny.c @@ -650,7 +650,7 @@ static int // evaluate flags flags = 0U; - do { + do { //-V1044 switch(*format) { case '0': flags |= FLAGS_ZEROPAD; diff --git a/lib/subghz/protocols/bin_raw.c b/lib/subghz/protocols/bin_raw.c index e90f1508e..a1e0e816b 100644 --- a/lib/subghz/protocols/bin_raw.c +++ b/lib/subghz/protocols/bin_raw.c @@ -455,7 +455,7 @@ static bool //sort by number of occurrences bool swap = true; - while(swap) { + while(swap) { //-V1044 swap = false; for(size_t i = 1; i < BIN_RAW_SEARCH_CLASSES; i++) { if(classes[i].count > classes[i - 1].count) { @@ -571,7 +571,7 @@ static bool bit_count = 0; if(data_markup_ind == BIN_RAW_MAX_MARKUP_COUNT) break; - ind &= 0xFFFFFFF8; //jump to the pre whole byte + ind &= 0xFFFFFFF8; //jump to the pre whole byte //-V784 } } while(gap_ind != 0); if((data_markup_ind != BIN_RAW_MAX_MARKUP_COUNT) && (ind != 0)) { diff --git a/lib/subghz/receiver.c b/lib/subghz/receiver.c index 88fac4e34..e7dff3b9a 100644 --- a/lib/subghz/receiver.c +++ b/lib/subghz/receiver.c @@ -8,7 +8,7 @@ typedef struct { SubGhzProtocolEncoderBase* base; } SubGhzReceiverSlot; -ARRAY_DEF(SubGhzReceiverSlotArray, SubGhzReceiverSlot, M_POD_OPLIST); +ARRAY_DEF(SubGhzReceiverSlotArray, SubGhzReceiverSlot, M_POD_OPLIST); //-V658 #define M_OPL_SubGhzReceiverSlotArray_t() ARRAY_OPLIST(SubGhzReceiverSlotArray, M_POD_OPLIST) struct SubGhzReceiver { diff --git a/lib/subghz/subghz_keystore.h b/lib/subghz/subghz_keystore.h index 6138651eb..b1bca9625 100644 --- a/lib/subghz/subghz_keystore.h +++ b/lib/subghz/subghz_keystore.h @@ -14,7 +14,7 @@ typedef struct { uint16_t type; } SubGhzKey; -ARRAY_DEF(SubGhzKeyArray, SubGhzKey, M_POD_OPLIST) +ARRAY_DEF(SubGhzKeyArray, SubGhzKey, M_POD_OPLIST) //-V658 #define M_OPL_SubGhzKeyArray_t() ARRAY_OPLIST(SubGhzKeyArray, M_POD_OPLIST) diff --git a/lib/subghz/subghz_setting.c b/lib/subghz/subghz_setting.c index 12b7bd16a..ff1753d25 100644 --- a/lib/subghz/subghz_setting.c +++ b/lib/subghz/subghz_setting.c @@ -163,7 +163,7 @@ typedef struct { size_t custom_preset_data_size; } SubGhzSettingCustomPresetItem; -ARRAY_DEF(SubGhzSettingCustomPresetItemArray, SubGhzSettingCustomPresetItem, M_POD_OPLIST) +ARRAY_DEF(SubGhzSettingCustomPresetItemArray, SubGhzSettingCustomPresetItem, M_POD_OPLIST) //-V658 #define M_OPL_SubGhzSettingCustomPresetItemArray_t() \ ARRAY_OPLIST(SubGhzSettingCustomPresetItemArray, M_POD_OPLIST) diff --git a/lib/toolbox/cli/shell/cli_shell_completions.c b/lib/toolbox/cli/shell/cli_shell_completions.c index 6b6634dbb..64133fe93 100644 --- a/lib/toolbox/cli/shell/cli_shell_completions.c +++ b/lib/toolbox/cli/shell/cli_shell_completions.c @@ -1,6 +1,6 @@ #include "cli_shell_completions.h" -ARRAY_DEF(CommandCompletions, FuriString*, FURI_STRING_OPLIST); // -V524 +ARRAY_DEF(CommandCompletions, FuriString*, FURI_STRING_OPLIST); // -V524 //-V658 #define M_OPL_CommandCompletions_t() ARRAY_OPLIST(CommandCompletions) struct CliShellCompletions { diff --git a/lib/toolbox/strint.c b/lib/toolbox/strint.c index 8c7f36976..113becd05 100644 --- a/lib/toolbox/strint.c +++ b/lib/toolbox/strint.c @@ -80,7 +80,7 @@ StrintParseError strint_to_uint64_internal( if(result > mul_limit) return StrintParseOverflowError; result *= base; - if(result > limit - digit_value) return StrintParseOverflowError; + if(result > limit - digit_value) return StrintParseOverflowError; //-V658 result += digit_value; read_total++; diff --git a/targets/f7/src/stm32wb55_startup.c b/targets/f7/src/stm32wb55_startup.c index 1c1cfdcc3..6aecab06b 100644 --- a/targets/f7/src/stm32wb55_startup.c +++ b/targets/f7/src/stm32wb55_startup.c @@ -68,7 +68,7 @@ void SystemInit(void) { RCC->PLLSAI1CFGR = 0x22041000U; #endif // Reset HSEBYP bit - RCC->CR &= 0xFFFBFFFFU; + RCC->CR &= 0xFFFBFFFFU; //-V784 // Disable all RCC related interrupts RCC->CIER = 0x00000000; } From 0d5beedb012f1a676b48106c7a21996d83906081 Mon Sep 17 00:00:00 2001 From: hedger Date: Wed, 24 Sep 2025 19:36:45 +0100 Subject: [PATCH 076/192] BLE: improved pairing security (#4240) * ble: use unique root security keys for new pairings after pairing reset; added migrations for existing pairing data; unit_tests: added migration tests * bt: lower logging level * hal: bt: updated doxygen strings * hal: ble: Added checks for root_keys ptr * service: ble: bt_keys_storage minor cleanup --- .../debug/unit_tests/tests/bt/bt_test.c | 159 +++++++++++++ applications/services/bt/bt_service/bt.c | 10 +- .../services/bt/bt_service/bt_keys_storage.c | 219 ++++++++++++++---- .../services/bt/bt_service/bt_keys_storage.h | 4 + applications/services/bt/bt_settings.c | 8 +- targets/f18/api_symbols.csv | 9 +- targets/f7/api_symbols.csv | 9 +- targets/f7/ble_glue/gap.c | 26 +-- targets/f7/ble_glue/gap.h | 14 +- targets/f7/furi_hal/furi_hal_bt.c | 15 +- targets/furi_hal_include/furi_hal_bt.h | 4 + 11 files changed, 391 insertions(+), 86 deletions(-) diff --git a/applications/debug/unit_tests/tests/bt/bt_test.c b/applications/debug/unit_tests/tests/bt/bt_test.c index a0f189fdb..be7e8f1ff 100644 --- a/applications/debug/unit_tests/tests/bt/bt_test.c +++ b/applications/debug/unit_tests/tests/bt/bt_test.c @@ -4,10 +4,23 @@ #include #include +#include #define BT_TEST_KEY_STORAGE_FILE_PATH EXT_PATH("unit_tests/bt_test.keys") +#define BT_TEST_MIGRATION_FILE_PATH EXT_PATH("unit_tests/bt_migration_test.keys") #define BT_TEST_NVM_RAM_BUFF_SIZE (507 * 4) // The same as in ble NVM storage +// Identity root key +static const uint8_t gap_legacy_irk[16] = + {0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}; +// Encryption root key +static const uint8_t gap_legacy_erk[16] = + {0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21}; + +// Test constants for migration (matching bt_keys_storage.c) +#define BT_KEYS_STORAGE_MAGIC_TEST (0x18) +#define BT_KEYS_STORAGE_VERSION_1_TEST (1) + typedef struct { Storage* storage; BtKeysStorage* bt_keys_storage; @@ -88,6 +101,134 @@ static void bt_test_keys_remove_test_file(void) { "Can't remove test file"); } +// Helper function to create a version 0 file manually +static bool + bt_test_create_v0_file(const char* file_path, const uint8_t* nvm_data, size_t nvm_size) { + // Version 0 files use saved_struct format with magic 0x18, version 0, containing only BLE pairing data + return saved_struct_save( + file_path, + nvm_data, + nvm_size, + BT_KEYS_STORAGE_MAGIC_TEST, + 0); // Version 0 +} + +// Helper function to verify file format version +static bool bt_test_verify_file_version(const char* file_path, uint32_t expected_version) { + uint8_t magic, version; + size_t size; + + if(!saved_struct_get_metadata(file_path, &magic, &version, &size)) { + return false; + } + + return (magic == BT_KEYS_STORAGE_MAGIC_TEST && version == expected_version); +} + +// Test migration from version 0 to version 1, including root key preservation +static void bt_test_migration_v0_to_v1(void) { + // Create test NVM data + const size_t test_nvm_size = 100; + uint8_t test_nvm_data[test_nvm_size]; + for(size_t i = 0; i < test_nvm_size; i++) { + test_nvm_data[i] = (uint8_t)(i & 0xFF); + } + + // Create a version 0 file + mu_assert( + bt_test_create_v0_file(BT_TEST_MIGRATION_FILE_PATH, test_nvm_data, test_nvm_size), + "Failed to create version 0 test file"); + + // Create BT keys storage and load the v0 file (should trigger migration) + BtKeysStorage* migration_storage = bt_keys_storage_alloc(BT_TEST_MIGRATION_FILE_PATH); + uint8_t loaded_buffer[BT_TEST_NVM_RAM_BUFF_SIZE]; + memset(loaded_buffer, 0, sizeof(loaded_buffer)); + bt_keys_storage_set_ram_params(migration_storage, loaded_buffer, sizeof(loaded_buffer)); + + // Load should succeed and migrate v0 to v1 + mu_assert(bt_keys_storage_load(migration_storage), "Failed to load and migrate v0 file"); + + // Verify the file is now version 1 + mu_assert( + bt_test_verify_file_version(BT_TEST_MIGRATION_FILE_PATH, BT_KEYS_STORAGE_VERSION_1_TEST), + "File was not migrated to version 1"); + + // Verify the NVM data was preserved during migration + mu_assert( + memcmp(test_nvm_data, loaded_buffer, test_nvm_size) == 0, + "NVM data was corrupted during migration"); + + // Verify that legacy root keys are used after migration + const GapRootSecurityKeys* migrated_keys = bt_keys_storage_get_root_keys(migration_storage); + mu_assert( + memcmp(migrated_keys->irk, gap_legacy_irk, sizeof(gap_legacy_irk)) == 0, + "IRK not set to legacy after migration"); + mu_assert( + memcmp(migrated_keys->erk, gap_legacy_erk, sizeof(gap_legacy_erk)) == 0, + "ERK not set to legacy after migration"); + + bt_keys_storage_free(migration_storage); + storage_simply_remove(bt_test->storage, BT_TEST_MIGRATION_FILE_PATH); +} + +// Test that migration preserves existing pairing data and root keys are not changed on reload +static void bt_test_migration_preserves_pairings_and_keys(void) { + const size_t pairing_data_size = 200; + uint8_t pairing_data[pairing_data_size]; + for(size_t i = 0; i < pairing_data_size; i++) { + pairing_data[i] = (uint8_t)((i * 7 + 42) & 0xFF); + } + mu_assert( + bt_test_create_v0_file(BT_TEST_MIGRATION_FILE_PATH, pairing_data, pairing_data_size), + "Failed to create v0 file with pairing data"); + + GapRootSecurityKeys keys_after_first_load; + for(int iteration = 0; iteration < 2; iteration++) { + BtKeysStorage* storage = bt_keys_storage_alloc(BT_TEST_MIGRATION_FILE_PATH); + uint8_t buffer[BT_TEST_NVM_RAM_BUFF_SIZE]; + memset(buffer, 0, sizeof(buffer)); + bt_keys_storage_set_ram_params(storage, buffer, sizeof(buffer)); + mu_assert(bt_keys_storage_load(storage), "Failed to load on iteration"); + mu_assert( + memcmp(pairing_data, buffer, pairing_data_size) == 0, + "Pairing data corrupted on iteration"); + const GapRootSecurityKeys* keys = bt_keys_storage_get_root_keys(storage); + if(iteration == 0) + memcpy(&keys_after_first_load, keys, sizeof(GapRootSecurityKeys)); + else + mu_assert( + memcmp(&keys_after_first_load, keys, sizeof(GapRootSecurityKeys)) == 0, + "Root keys changed after reload"); + bt_keys_storage_free(storage); + } + storage_simply_remove(bt_test->storage, BT_TEST_MIGRATION_FILE_PATH); +} + +// Test that delete operation generates new secure keys in v1 and does not match legacy +static void bt_test_delete_generates_new_keys_and_not_legacy(void) { + BtKeysStorage* storage = bt_keys_storage_alloc(BT_TEST_MIGRATION_FILE_PATH); + uint8_t buffer[BT_TEST_NVM_RAM_BUFF_SIZE]; + memset(buffer, 0x55, sizeof(buffer)); + bt_keys_storage_set_ram_params(storage, buffer, sizeof(buffer)); + mu_assert(bt_keys_storage_update(storage, buffer, 100), "Failed to create initial v1 file"); + const GapRootSecurityKeys* original_keys = bt_keys_storage_get_root_keys(storage); + uint8_t original_keys_copy[sizeof(GapRootSecurityKeys)]; + memcpy(original_keys_copy, original_keys, sizeof(original_keys_copy)); + bt_keys_storage_delete(storage); + const GapRootSecurityKeys* new_keys = bt_keys_storage_get_root_keys(storage); + mu_assert( + memcmp(original_keys_copy, new_keys, sizeof(original_keys_copy)) != 0, + "Root keys were not regenerated after delete"); + mu_assert( + memcmp(new_keys->irk, gap_legacy_irk, sizeof(gap_legacy_irk)) != 0, + "IRK after delete should not match legacy"); + mu_assert( + memcmp(new_keys->erk, gap_legacy_erk, sizeof(gap_legacy_erk)) != 0, + "ERK after delete should not match legacy"); + bt_keys_storage_free(storage); + storage_simply_remove(bt_test->storage, BT_TEST_MIGRATION_FILE_PATH); +} + MU_TEST(bt_test_keys_storage_serial_profile) { furi_check(bt_test); @@ -96,10 +237,28 @@ MU_TEST(bt_test_keys_storage_serial_profile) { bt_test_keys_remove_test_file(); } +MU_TEST(bt_test_migration_v0_to_v1_test) { + furi_check(bt_test); + bt_test_migration_v0_to_v1(); +} + +MU_TEST(bt_test_migration_preserves_pairings_and_keys_test) { + furi_check(bt_test); + bt_test_migration_preserves_pairings_and_keys(); +} + +MU_TEST(bt_test_delete_generates_new_keys_and_not_legacy_test) { + furi_check(bt_test); + bt_test_delete_generates_new_keys_and_not_legacy(); +} + MU_TEST_SUITE(test_bt) { bt_test_alloc(); MU_RUN_TEST(bt_test_keys_storage_serial_profile); + MU_RUN_TEST(bt_test_migration_v0_to_v1_test); + MU_RUN_TEST(bt_test_migration_preserves_pairings_and_keys_test); + MU_RUN_TEST(bt_test_delete_generates_new_keys_and_not_legacy_test); bt_test_free(); } diff --git a/applications/services/bt/bt_service/bt.c b/applications/services/bt/bt_service/bt.c index 2eeeab286..7554d87ef 100644 --- a/applications/services/bt/bt_service/bt.c +++ b/applications/services/bt/bt_service/bt.c @@ -403,6 +403,7 @@ static void bt_change_profile(Bt* bt, BtMessage* message) { bt->current_profile = furi_hal_bt_change_app( message->data.profile.template, message->data.profile.params, + bt_keys_storage_get_root_keys(bt->keys_storage), bt_on_gap_event_callback, bt); if(bt->current_profile) { @@ -458,7 +459,6 @@ static void bt_load_keys(Bt* bt) { bt_keys_storage_load(bt->keys_storage); bt->current_profile = NULL; - } else { FURI_LOG_I(TAG, "Keys unchanged"); } @@ -466,8 +466,12 @@ static void bt_load_keys(Bt* bt) { static void bt_start_application(Bt* bt) { if(!bt->current_profile) { - bt->current_profile = - furi_hal_bt_change_app(ble_profile_serial, NULL, bt_on_gap_event_callback, bt); + bt->current_profile = furi_hal_bt_change_app( + ble_profile_serial, + NULL, + bt_keys_storage_get_root_keys(bt->keys_storage), + bt_on_gap_event_callback, + bt); if(!bt->current_profile) { FURI_LOG_E(TAG, "BLE App start failed"); diff --git a/applications/services/bt/bt_service/bt_keys_storage.c b/applications/services/bt/bt_service/bt_keys_storage.c index 57742e8e2..0735a52b7 100644 --- a/applications/services/bt/bt_service/bt_keys_storage.c +++ b/applications/services/bt/bt_service/bt_keys_storage.c @@ -2,21 +2,92 @@ #include #include -#include -#include +#include -#define BT_KEYS_STORAGE_VERSION (0) -#define BT_KEYS_STORAGE_MAGIC (0x18) +#include + +#include +#include + +#define BT_KEYS_STORAGE_MAGIC (0x18) +#define BT_KEYS_STORAGE_VERSION (1) +#define BT_KEYS_STORAGE_LEGACY_VERSION (0) // Legacy version with no root keys #define TAG "BtKeyStorage" +// Identity root key +static const uint8_t gap_legacy_irk[16] = + {0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}; +// Encryption root key +static const uint8_t gap_legacy_erk[16] = + {0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21}; + struct BtKeysStorage { uint8_t* nvm_sram_buff; uint16_t nvm_sram_buff_size; uint16_t current_size; FuriString* file_path; + GapRootSecurityKeys root_keys; }; +typedef struct { + GapRootSecurityKeys root_keys; + uint8_t pairing_data[]; +} BtKeysStorageFile; + +static bool bt_keys_storage_save(BtKeysStorage* instance) { + furi_assert(instance); + + size_t total_size = sizeof(BtKeysStorageFile) + instance->current_size; + BtKeysStorageFile* save_data = malloc(total_size); + + memcpy(&save_data->root_keys, &instance->root_keys, sizeof(GapRootSecurityKeys)); + + furi_hal_bt_nvm_sram_sem_acquire(); + memcpy(save_data->pairing_data, instance->nvm_sram_buff, instance->current_size); + furi_hal_bt_nvm_sram_sem_release(); + + bool saved = saved_struct_save( + furi_string_get_cstr(instance->file_path), + save_data, + sizeof(GapRootSecurityKeys) + instance->current_size, + BT_KEYS_STORAGE_MAGIC, + BT_KEYS_STORAGE_VERSION); + + free(save_data); + return saved; +} + +static bool bt_keys_storage_load_keys_and_pairings( + BtKeysStorage* instance, + const uint8_t* file_data, + size_t data_size) { + if(data_size < sizeof(GapRootSecurityKeys)) { + return false; + } + + const BtKeysStorageFile* loaded = (const BtKeysStorageFile*)file_data; + memcpy(&instance->root_keys, &loaded->root_keys, sizeof(GapRootSecurityKeys)); + + size_t ble_data_size = data_size - sizeof(GapRootSecurityKeys); + if(ble_data_size > instance->nvm_sram_buff_size) { + FURI_LOG_E(TAG, "BLE data too large for SRAM buffer"); + return false; + } + + furi_hal_bt_nvm_sram_sem_acquire(); + memcpy(instance->nvm_sram_buff, loaded->pairing_data, ble_data_size); + instance->current_size = ble_data_size; + furi_hal_bt_nvm_sram_sem_release(); + + return true; +} + +static void bt_keys_storage_regenerate_root_keys(BtKeysStorage* instance) { + furi_hal_random_fill_buf(instance->root_keys.erk, sizeof(instance->root_keys.erk)); + furi_hal_random_fill_buf(instance->root_keys.irk, sizeof(instance->root_keys.irk)); +} + bool bt_keys_storage_delete(BtKeysStorage* instance) { furi_assert(instance); @@ -25,6 +96,15 @@ bool bt_keys_storage_delete(BtKeysStorage* instance) { furi_hal_bt_stop_advertising(); delete_succeed = furi_hal_bt_clear_white_list(); + + FURI_LOG_I(TAG, "Root keys regen"); + bt_keys_storage_regenerate_root_keys(instance); + + instance->current_size = 0; + if(!bt_keys_storage_save(instance)) { + FURI_LOG_E(TAG, "Save after delete failed"); + } + if(bt_is_active) { furi_hal_bt_start_advertising(); } @@ -42,6 +122,7 @@ BtKeysStorage* bt_keys_storage_alloc(const char* keys_storage_path) { instance->file_path = furi_string_alloc(); furi_string_set_str(instance->file_path, keys_storage_path); + bt_keys_storage_regenerate_root_keys(instance); return instance; } @@ -76,20 +157,31 @@ static bool bt_keys_storage_file_exists(const char* file_path) { return ret; } -static bool bt_keys_storage_validate_file(const char* file_path, size_t* payload_size) { +static bool bt_keys_storage_validate_file( + const char* file_path, + size_t* payload_size, + uint8_t* file_version) { uint8_t magic, version; size_t size; if(!saved_struct_get_metadata(file_path, &magic, &version, &size)) { - FURI_LOG_E(TAG, "Failed to get metadata"); + FURI_LOG_W(TAG, "Failed to get metadata"); return false; - } else if(magic != BT_KEYS_STORAGE_MAGIC || version != BT_KEYS_STORAGE_VERSION) { - FURI_LOG_E(TAG, "File version mismatch"); + } else if(magic != BT_KEYS_STORAGE_MAGIC) { + FURI_LOG_W(TAG, "File magic mismatch"); + return false; + } else if(version > BT_KEYS_STORAGE_VERSION) { + FURI_LOG_E( + TAG, + "File version %d is newer than supported version %d", + version, + BT_KEYS_STORAGE_VERSION); return false; } *payload_size = size; + *file_version = version; return true; } @@ -102,32 +194,45 @@ bool bt_keys_storage_is_changed(BtKeysStorage* instance) { do { const char* file_path = furi_string_get_cstr(instance->file_path); size_t payload_size; + uint8_t file_version; if(!bt_keys_storage_file_exists(file_path)) { FURI_LOG_W(TAG, "Missing or empty file"); + is_changed = true; break; + } - } else if(!bt_keys_storage_validate_file(file_path, &payload_size)) { - FURI_LOG_E(TAG, "Invalid or corrupted file"); + if(!bt_keys_storage_validate_file(file_path, &payload_size, &file_version)) { + FURI_LOG_W(TAG, "Invalid or corrupted file"); + is_changed = true; + break; + } + + // Early check for legacy version: always considered changed, no need to load + if(file_version == BT_KEYS_STORAGE_LEGACY_VERSION) { + is_changed = true; break; } data_buffer = malloc(payload_size); - const bool data_loaded = saved_struct_load( - file_path, data_buffer, payload_size, BT_KEYS_STORAGE_MAGIC, BT_KEYS_STORAGE_VERSION); + file_path, data_buffer, payload_size, BT_KEYS_STORAGE_MAGIC, file_version); if(!data_loaded) { FURI_LOG_E(TAG, "Failed to load file"); break; + } - } else if(payload_size == instance->current_size) { + // At this point, it's version 1 file we have + const BtKeysStorageFile* loaded = (const BtKeysStorageFile*)data_buffer; + size_t expected_file_size = sizeof(GapRootSecurityKeys) + instance->current_size; + if(payload_size == expected_file_size) { furi_hal_bt_nvm_sram_sem_acquire(); - is_changed = memcmp(data_buffer, instance->nvm_sram_buff, payload_size); + is_changed = + memcmp(loaded->pairing_data, instance->nvm_sram_buff, instance->current_size); furi_hal_bt_nvm_sram_sem_release(); - } else { - FURI_LOG_D(TAG, "Size mismatch"); + FURI_LOG_D(TAG, "NVRAM sz mismatch (v1)"); is_changed = true; } } while(false); @@ -139,45 +244,59 @@ bool bt_keys_storage_is_changed(BtKeysStorage* instance) { return is_changed; } +static bool bt_keys_storage_load_legacy_pairings( + BtKeysStorage* instance, + const uint8_t* file_data, + size_t payload_size) { + FURI_LOG_I(TAG, "Loaded v0, upgrading to v1"); + memcpy(instance->root_keys.irk, gap_legacy_irk, sizeof(instance->root_keys.irk)); + memcpy(instance->root_keys.erk, gap_legacy_erk, sizeof(instance->root_keys.erk)); + if(payload_size > instance->nvm_sram_buff_size) { + FURI_LOG_E(TAG, "Pairing too large"); + return false; + } + furi_hal_bt_nvm_sram_sem_acquire(); + memcpy(instance->nvm_sram_buff, file_data, payload_size); + instance->current_size = payload_size; + furi_hal_bt_nvm_sram_sem_release(); + if(!bt_keys_storage_save(instance)) { + FURI_LOG_W(TAG, "Upgrade to v1 failed"); + } else { + FURI_LOG_I(TAG, "Upgraded to v1"); + } + return true; +} + bool bt_keys_storage_load(BtKeysStorage* instance) { furi_assert(instance); + const char* file_path = furi_string_get_cstr(instance->file_path); + size_t payload_size; + uint8_t file_version; + if(!bt_keys_storage_validate_file(file_path, &payload_size, &file_version)) { + FURI_LOG_E(TAG, "Invalid or corrupted file"); + return false; + } + bool loaded = false; + uint8_t* file_data = malloc(payload_size); do { - const char* file_path = furi_string_get_cstr(instance->file_path); - - // Get payload size - size_t payload_size; - if(!bt_keys_storage_validate_file(file_path, &payload_size)) { - FURI_LOG_E(TAG, "Invalid or corrupted file"); - break; - - } else if(payload_size > instance->nvm_sram_buff_size) { - FURI_LOG_E(TAG, "NVM RAM buffer overflow"); + if(!saved_struct_load( + file_path, file_data, payload_size, BT_KEYS_STORAGE_MAGIC, file_version)) { + FURI_LOG_E(TAG, "Failed to load"); break; } - // Load saved data to ram - furi_hal_bt_nvm_sram_sem_acquire(); - const bool data_loaded = saved_struct_load( - file_path, - instance->nvm_sram_buff, - payload_size, - BT_KEYS_STORAGE_MAGIC, - BT_KEYS_STORAGE_VERSION); - furi_hal_bt_nvm_sram_sem_release(); - - if(!data_loaded) { - FURI_LOG_E(TAG, "Failed to load file"); + if(file_version == BT_KEYS_STORAGE_LEGACY_VERSION) { + loaded = bt_keys_storage_load_legacy_pairings(instance, file_data, payload_size); break; } - - instance->current_size = payload_size; - - loaded = true; + // Only v1 left + loaded = bt_keys_storage_load_keys_and_pairings(instance, file_data, payload_size); } while(false); + free(file_data); return loaded; } @@ -203,14 +322,8 @@ bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32 instance->current_size = new_size; - furi_hal_bt_nvm_sram_sem_acquire(); - bool data_updated = saved_struct_save( - furi_string_get_cstr(instance->file_path), - instance->nvm_sram_buff, - new_size, - BT_KEYS_STORAGE_MAGIC, - BT_KEYS_STORAGE_VERSION); - furi_hal_bt_nvm_sram_sem_release(); + // Save using version 1 format with embedded root keys + bool data_updated = bt_keys_storage_save(instance); if(!data_updated) { FURI_LOG_E(TAG, "Failed to update key storage"); @@ -222,3 +335,9 @@ bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32 return updated; } + +const GapRootSecurityKeys* bt_keys_storage_get_root_keys(BtKeysStorage* instance) { + furi_assert(instance); + + return &instance->root_keys; +} diff --git a/applications/services/bt/bt_service/bt_keys_storage.h b/applications/services/bt/bt_service/bt_keys_storage.h index b7a127035..2033b055b 100644 --- a/applications/services/bt/bt_service/bt_keys_storage.h +++ b/applications/services/bt/bt_service/bt_keys_storage.h @@ -3,6 +3,8 @@ #include #include +#include + #ifdef __cplusplus extern "C" { #endif @@ -19,6 +21,8 @@ void bt_keys_storage_set_ram_params(BtKeysStorage* instance, uint8_t* buff, uint bool bt_keys_storage_is_changed(BtKeysStorage* instance); +const GapRootSecurityKeys* bt_keys_storage_get_root_keys(BtKeysStorage* instance); + bool bt_keys_storage_load(BtKeysStorage* instance); bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32_t size); diff --git a/applications/services/bt/bt_settings.c b/applications/services/bt/bt_settings.c index abdc97f7e..da607e540 100644 --- a/applications/services/bt/bt_settings.c +++ b/applications/services/bt/bt_settings.c @@ -2,6 +2,7 @@ #include "bt_settings_filename.h" #include + #include #include @@ -14,12 +15,13 @@ void bt_settings_load(BtSettings* bt_settings) { furi_assert(bt_settings); - const bool success = saved_struct_load( + const bool load_success = saved_struct_load( BT_SETTINGS_PATH, bt_settings, sizeof(BtSettings), BT_SETTINGS_MAGIC, BT_SETTINGS_VERSION); - if(!success) { + if(!load_success) { FURI_LOG_W(TAG, "Failed to load settings, using defaults"); - memset(bt_settings, 0, sizeof(BtSettings)); + + bt_settings->enabled = false; bt_settings_save(bt_settings); } } diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index fbbb5d13f..2bbf81e7a 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,86.1,, +Version,+,87.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -703,6 +703,7 @@ Function,+,bt_forget_bonded_devices,void,Bt* Function,+,bt_keys_storage_alloc,BtKeysStorage*,const char* Function,+,bt_keys_storage_delete,_Bool,BtKeysStorage* Function,+,bt_keys_storage_free,void,BtKeysStorage* +Function,+,bt_keys_storage_get_root_keys,const GapRootSecurityKeys*,BtKeysStorage* Function,+,bt_keys_storage_is_changed,_Bool,BtKeysStorage* Function,+,bt_keys_storage_load,_Bool,BtKeysStorage* Function,+,bt_keys_storage_set_default_path,void,Bt* @@ -1173,7 +1174,7 @@ Function,+,furi_hal_adc_convert_vref,float,"FuriHalAdcHandle*, uint16_t" Function,+,furi_hal_adc_init,void, Function,+,furi_hal_adc_read,uint16_t,"FuriHalAdcHandle*, FuriHalAdcChannel" Function,+,furi_hal_adc_release,void,FuriHalAdcHandle* -Function,+,furi_hal_bt_change_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*" +Function,+,furi_hal_bt_change_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, const GapRootSecurityKeys*, GapEventCallback, void*" Function,+,furi_hal_bt_check_profile_type,_Bool,"FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*" Function,+,furi_hal_bt_clear_white_list,_Bool, Function,+,furi_hal_bt_dump_state,void,FuriString* @@ -1200,7 +1201,7 @@ Function,+,furi_hal_bt_nvm_sram_sem_release,void, Function,+,furi_hal_bt_reinit,void, Function,+,furi_hal_bt_set_key_storage_change_callback,void,"BleGlueKeyStorageChangedCallback, void*" Function,+,furi_hal_bt_start_advertising,void, -Function,+,furi_hal_bt_start_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*" +Function,+,furi_hal_bt_start_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, const GapRootSecurityKeys*, GapEventCallback, void*" Function,+,furi_hal_bt_start_packet_rx,void,"uint8_t, uint8_t" Function,+,furi_hal_bt_start_packet_tx,void,"uint8_t, uint8_t, uint8_t" Function,+,furi_hal_bt_start_radio_stack,_Bool, @@ -1740,7 +1741,7 @@ Function,-,gap_extra_beacon_set_data,_Bool,"const uint8_t*, uint8_t" Function,-,gap_extra_beacon_start,_Bool, Function,-,gap_extra_beacon_stop,_Bool, Function,-,gap_get_state,GapState, -Function,-,gap_init,_Bool,"GapConfig*, GapEventCallback, void*" +Function,-,gap_init,_Bool,"GapConfig*, const GapRootSecurityKeys*, GapEventCallback, void*" Function,-,gap_start_advertising,void, Function,-,gap_stop_advertising,void, Function,-,gap_thread_stop,void, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index f2b13e4a9..2370133d6 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,86.1,, +Version,+,87.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -780,6 +780,7 @@ Function,+,bt_forget_bonded_devices,void,Bt* Function,+,bt_keys_storage_alloc,BtKeysStorage*,const char* Function,+,bt_keys_storage_delete,_Bool,BtKeysStorage* Function,+,bt_keys_storage_free,void,BtKeysStorage* +Function,+,bt_keys_storage_get_root_keys,const GapRootSecurityKeys*,BtKeysStorage* Function,+,bt_keys_storage_is_changed,_Bool,BtKeysStorage* Function,+,bt_keys_storage_load,_Bool,BtKeysStorage* Function,+,bt_keys_storage_set_default_path,void,Bt* @@ -1285,7 +1286,7 @@ Function,+,furi_hal_adc_convert_vref,float,"FuriHalAdcHandle*, uint16_t" Function,+,furi_hal_adc_init,void, Function,+,furi_hal_adc_read,uint16_t,"FuriHalAdcHandle*, FuriHalAdcChannel" Function,+,furi_hal_adc_release,void,FuriHalAdcHandle* -Function,+,furi_hal_bt_change_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*" +Function,+,furi_hal_bt_change_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, const GapRootSecurityKeys*, GapEventCallback, void*" Function,+,furi_hal_bt_check_profile_type,_Bool,"FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*" Function,+,furi_hal_bt_clear_white_list,_Bool, Function,+,furi_hal_bt_dump_state,void,FuriString* @@ -1312,7 +1313,7 @@ Function,+,furi_hal_bt_nvm_sram_sem_release,void, Function,+,furi_hal_bt_reinit,void, Function,+,furi_hal_bt_set_key_storage_change_callback,void,"BleGlueKeyStorageChangedCallback, void*" Function,+,furi_hal_bt_start_advertising,void, -Function,+,furi_hal_bt_start_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*" +Function,+,furi_hal_bt_start_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, const GapRootSecurityKeys*, GapEventCallback, void*" Function,+,furi_hal_bt_start_packet_rx,void,"uint8_t, uint8_t" Function,+,furi_hal_bt_start_packet_tx,void,"uint8_t, uint8_t, uint8_t" Function,+,furi_hal_bt_start_radio_stack,_Bool, @@ -1961,7 +1962,7 @@ Function,-,gap_extra_beacon_set_data,_Bool,"const uint8_t*, uint8_t" Function,-,gap_extra_beacon_start,_Bool, Function,-,gap_extra_beacon_stop,_Bool, Function,-,gap_get_state,GapState, -Function,-,gap_init,_Bool,"GapConfig*, GapEventCallback, void*" +Function,-,gap_init,_Bool,"GapConfig*, const GapRootSecurityKeys*, GapEventCallback, void*" Function,-,gap_start_advertising,void, Function,-,gap_stop_advertising,void, Function,-,gap_thread_stop,void, diff --git a/targets/f7/ble_glue/gap.c b/targets/f7/ble_glue/gap.c index 70c9d1c6f..38fc5e420 100644 --- a/targets/f7/ble_glue/gap.c +++ b/targets/f7/ble_glue/gap.c @@ -51,13 +51,6 @@ typedef enum { GapCommandKillThread, } GapCommand; -// Identity root key -static const uint8_t gap_irk[16] = - {0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}; -// Encryption root key -static const uint8_t gap_erk[16] = - {0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21}; - static Gap* gap = NULL; static void gap_advertise_start(GapState new_state); @@ -333,7 +326,9 @@ static void set_manufacturer_data(uint8_t* mfg_data, uint8_t mfg_data_len) { gap->service.mfg_data_len += mfg_data_len; } -static void gap_init_svc(Gap* gap) { +static void gap_init_svc(Gap* gap, const GapRootSecurityKeys* root_keys) { + furi_check(root_keys); + tBleStatus status; uint32_t srd_bd_addr[2]; @@ -351,9 +346,9 @@ static void gap_init_svc(Gap* gap) { aci_hal_write_config_data( CONFIG_DATA_RANDOM_ADDRESS_OFFSET, CONFIG_DATA_RANDOM_ADDRESS_LEN, (uint8_t*)srd_bd_addr); // Set Identity root key used to derive LTK and CSRK - aci_hal_write_config_data(CONFIG_DATA_IR_OFFSET, CONFIG_DATA_IR_LEN, (uint8_t*)gap_irk); + aci_hal_write_config_data(CONFIG_DATA_IR_OFFSET, CONFIG_DATA_IR_LEN, root_keys->irk); // Set Encryption root key used to derive LTK and CSRK - aci_hal_write_config_data(CONFIG_DATA_ER_OFFSET, CONFIG_DATA_ER_LEN, (uint8_t*)gap_erk); + aci_hal_write_config_data(CONFIG_DATA_ER_OFFSET, CONFIG_DATA_ER_LEN, root_keys->erk); // Set TX Power to 0 dBm aci_hal_set_tx_power_level(1, 0x19); // Initialize GATT interface @@ -535,7 +530,11 @@ static void gap_advetise_timer_callback(void* context) { furi_check(furi_message_queue_put(gap->command_queue, &command, 0) == FuriStatusOk); } -bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { +bool gap_init( + GapConfig* config, + const GapRootSecurityKeys* root_keys, + GapEventCallback on_event_cb, + void* context) { if(!ble_glue_is_radio_stack_ready()) { return false; } @@ -548,7 +547,7 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { gap->advertise_timer = furi_timer_alloc(gap_advetise_timer_callback, FuriTimerTypeOnce, NULL); // Initialization of GATT & GAP layer gap->service.adv_name = config->adv_name; - gap_init_svc(gap); + gap_init_svc(gap, root_keys); ble_event_dispatcher_init(); // Initialization of the GAP state gap->state_mutex = furi_mutex_alloc(FuriMutexTypeNormal); @@ -573,14 +572,13 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { set_manufacturer_data(gap->config->mfg_data, gap->config->mfg_data_len); } + gap->service.adv_svc_uuid_len = 1; if(gap->config->adv_service.UUID_Type == UUID_TYPE_16) { uint8_t adv_service_uid[2]; - gap->service.adv_svc_uuid_len = 1; adv_service_uid[0] = gap->config->adv_service.Service_UUID_16 & 0xff; adv_service_uid[1] = gap->config->adv_service.Service_UUID_16 >> 8; set_advertisment_service_uid(adv_service_uid, sizeof(adv_service_uid)); } else if(gap->config->adv_service.UUID_Type == UUID_TYPE_128) { - gap->service.adv_svc_uuid_len = 1; set_advertisment_service_uid( gap->config->adv_service.Service_UUID_128, sizeof(gap->config->adv_service.Service_UUID_128)); diff --git a/targets/f7/ble_glue/gap.h b/targets/f7/ble_glue/gap.h index b2bf0ffc1..57a0bb79d 100644 --- a/targets/f7/ble_glue/gap.h +++ b/targets/f7/ble_glue/gap.h @@ -6,6 +6,7 @@ #include #define GAP_MAC_ADDR_SIZE (6) +#define GAP_KEY_SIZE (0x10) /* * GAP helpers - background thread that handles BLE GAP events and advertising. @@ -83,7 +84,18 @@ typedef struct { GapConnectionParamsRequest conn_param; } GapConfig; -bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context); +typedef struct { + // Encryption Root key. Must be unique per-device (or app) + uint8_t erk[GAP_KEY_SIZE]; + // Identity Root key. Used for resolving RPAs, if configured + uint8_t irk[GAP_KEY_SIZE]; +} GapRootSecurityKeys; + +bool gap_init( + GapConfig* config, + const GapRootSecurityKeys* root_keys, + GapEventCallback on_event_cb, + void* context); void gap_start_advertising(void); diff --git a/targets/f7/furi_hal/furi_hal_bt.c b/targets/f7/furi_hal/furi_hal_bt.c index 2c1a9367b..f1881f94f 100644 --- a/targets/f7/furi_hal/furi_hal_bt.c +++ b/targets/f7/furi_hal/furi_hal_bt.c @@ -36,6 +36,9 @@ static FuriHalBt furi_hal_bt = { .stack = FuriHalBtStackUnknown, }; +static FuriHalBleProfileBase* current_profile = NULL; +static GapConfig current_config = {0}; + void furi_hal_bt_init(void) { FURI_LOG_I(TAG, "Start BT initialization"); furi_hal_bus_enable(FuriHalBusHSEM); @@ -149,9 +152,6 @@ bool furi_hal_bt_is_testing_supported(void) { } } -static FuriHalBleProfileBase* current_profile = NULL; -static GapConfig current_config = {0}; - bool furi_hal_bt_check_profile_type( FuriHalBleProfileBase* profile, const FuriHalBleProfileTemplate* profile_template) { @@ -165,10 +165,12 @@ bool furi_hal_bt_check_profile_type( FuriHalBleProfileBase* furi_hal_bt_start_app( const FuriHalBleProfileTemplate* profile_template, FuriHalBleProfileParams params, + const GapRootSecurityKeys* root_keys, GapEventCallback event_cb, void* context) { furi_check(event_cb); furi_check(profile_template); + furi_check(root_keys); furi_check(current_profile == NULL); do { @@ -183,7 +185,7 @@ FuriHalBleProfileBase* furi_hal_bt_start_app( profile_template->get_gap_config(¤t_config, params); - if(!gap_init(¤t_config, event_cb, context)) { + if(!gap_init(¤t_config, root_keys, event_cb, context)) { gap_thread_stop(); FURI_LOG_E(TAG, "Failed to init GAP"); break; @@ -239,12 +241,11 @@ void furi_hal_bt_reinit(void) { FuriHalBleProfileBase* furi_hal_bt_change_app( const FuriHalBleProfileTemplate* profile_template, FuriHalBleProfileParams profile_params, + const GapRootSecurityKeys* root_keys, GapEventCallback event_cb, void* context) { - furi_check(event_cb); - furi_hal_bt_reinit(); - return furi_hal_bt_start_app(profile_template, profile_params, event_cb, context); + return furi_hal_bt_start_app(profile_template, profile_params, root_keys, event_cb, context); } bool furi_hal_bt_is_active(void) { diff --git a/targets/furi_hal_include/furi_hal_bt.h b/targets/furi_hal_include/furi_hal_bt.h index 6da723311..1711f703d 100644 --- a/targets/furi_hal_include/furi_hal_bt.h +++ b/targets/furi_hal_include/furi_hal_bt.h @@ -77,6 +77,7 @@ bool furi_hal_bt_check_profile_type( * * @param profile_template FuriHalBleProfileTemplate instance * @param params Parameters to pass to the profile. Can be NULL + * @param root_keys pointer to root keys * @param event_cb GapEventCallback instance * @param context pointer to context * @@ -85,6 +86,7 @@ bool furi_hal_bt_check_profile_type( FURI_WARN_UNUSED FuriHalBleProfileBase* furi_hal_bt_start_app( const FuriHalBleProfileTemplate* profile_template, FuriHalBleProfileParams params, + const GapRootSecurityKeys* root_keys, GapEventCallback event_cb, void* context); @@ -100,6 +102,7 @@ void furi_hal_bt_reinit(void); * @param profile_template FuriHalBleProfileTemplate instance * @param profile_params Parameters to pass to the profile. Can be NULL * @param event_cb GapEventCallback instance + * @param root_keys pointer to root keys * @param context pointer to context * * @return instance of profile, NULL on failure @@ -107,6 +110,7 @@ void furi_hal_bt_reinit(void); FURI_WARN_UNUSED FuriHalBleProfileBase* furi_hal_bt_change_app( const FuriHalBleProfileTemplate* profile_template, FuriHalBleProfileParams profile_params, + const GapRootSecurityKeys* root_keys, GapEventCallback event_cb, void* context); From d0360625d6e1344eb87c80cd47f699717721f9ec Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Wed, 24 Sep 2025 23:24:28 +0400 Subject: [PATCH 077/192] [FL-3925] JS views finished (#4155) * js: value destructuring and tests * js: temporary fix to see size impact * js_val: reduce code size 1 * i may be stupid. * test: js_value args * Revert "js: temporary fix to see size impact" This reverts commit f51d726dbafc4300d3552020de1c3b8f9ecd3ac1. * pvs: silence warnings * style: formatting * pvs: silence warnings? * pvs: silence warnings?? * js_value: redesign declaration types for less code * js: temporary fix to see size impact * style: formatting * pvs: fix helpful warnings * js_value: reduce .rodata size * pvs: fix helpful warning * js_value: reduce code size 1 * fix build error * style: format * Revert "js: temporary fix to see size impact" This reverts commit d6a46f01794132e882e03fd273dec24386a4f8ba. * style: format * js: move to new arg parser * style: format * feat: all js views done * js, toolbox: generalize string owning * toolbox: silence pvs warning --------- Co-authored-by: hedger Co-authored-by: hedger --- applications/services/gui/modules/popup.c | 5 +- applications/services/gui/modules/popup.h | 2 +- applications/system/js_app/application.fam | 48 +++ .../js_app/examples/apps/Scripts/gui.js | 120 +++++++- .../js_app/modules/js_gui/button_menu.c | 169 +++++++++++ .../js_app/modules/js_gui/button_panel.c | 274 ++++++++++++++++++ .../system/js_app/modules/js_gui/icon.c | 10 + .../system/js_app/modules/js_gui/js_gui.c | 21 +- .../system/js_app/modules/js_gui/js_gui.h | 5 + .../modules/js_gui/js_gui_api_table_i.h | 3 +- .../system/js_app/modules/js_gui/menu.c | 105 +++++++ .../js_app/modules/js_gui/number_input.c | 130 +++++++++ .../system/js_app/modules/js_gui/popup.c | 102 +++++++ .../system/js_app/modules/js_gui/submenu.c | 33 ++- .../system/js_app/modules/js_gui/vi_list.c | 163 +++++++++++ .../packages/fz-sdk/gui/button_menu.d.ts | 40 +++ .../packages/fz-sdk/gui/button_panel.d.ts | 49 ++++ .../js_app/packages/fz-sdk/gui/icon.d.ts | 5 +- .../js_app/packages/fz-sdk/gui/index.d.ts | 18 +- .../js_app/packages/fz-sdk/gui/menu.d.ts | 38 +++ .../packages/fz-sdk/gui/number_input.d.ts | 44 +++ .../js_app/packages/fz-sdk/gui/popup.d.ts | 43 +++ .../js_app/packages/fz-sdk/gui/submenu.d.ts | 5 +- .../js_app/packages/fz-sdk/gui/vi_list.d.ts | 38 +++ .../js_app/packages/fz-sdk/package.json | 2 +- lib/toolbox/SConscript | 1 + lib/toolbox/str_buffer.c | 18 ++ lib/toolbox/str_buffer.h | 47 +++ targets/f18/api_symbols.csv | 3 + targets/f7/api_symbols.csv | 3 + 30 files changed, 1496 insertions(+), 48 deletions(-) create mode 100644 applications/system/js_app/modules/js_gui/button_menu.c create mode 100644 applications/system/js_app/modules/js_gui/button_panel.c create mode 100644 applications/system/js_app/modules/js_gui/menu.c create mode 100644 applications/system/js_app/modules/js_gui/number_input.c create mode 100644 applications/system/js_app/modules/js_gui/popup.c create mode 100644 applications/system/js_app/modules/js_gui/vi_list.c create mode 100644 applications/system/js_app/packages/fz-sdk/gui/button_menu.d.ts create mode 100644 applications/system/js_app/packages/fz-sdk/gui/button_panel.d.ts create mode 100644 applications/system/js_app/packages/fz-sdk/gui/menu.d.ts create mode 100644 applications/system/js_app/packages/fz-sdk/gui/number_input.d.ts create mode 100644 applications/system/js_app/packages/fz-sdk/gui/popup.d.ts create mode 100644 applications/system/js_app/packages/fz-sdk/gui/vi_list.d.ts create mode 100644 lib/toolbox/str_buffer.c create mode 100644 lib/toolbox/str_buffer.h diff --git a/applications/services/gui/modules/popup.c b/applications/services/gui/modules/popup.c index 5c9c75e4a..6b5cbfdd0 100644 --- a/applications/services/gui/modules/popup.c +++ b/applications/services/gui/modules/popup.c @@ -93,13 +93,12 @@ static bool popup_view_input_callback(InputEvent* event, void* context) { void popup_start_timer(void* context) { Popup* popup = context; if(popup->timer_enabled) { - uint32_t timer_period = - popup->timer_period_in_ms / (1000.0f / furi_kernel_get_tick_frequency()); + uint32_t timer_period = furi_ms_to_ticks(popup->timer_period_in_ms); if(timer_period == 0) timer_period = 1; if(furi_timer_start(popup->timer, timer_period) != FuriStatusOk) { furi_crash(); - }; + } } } diff --git a/applications/services/gui/modules/popup.h b/applications/services/gui/modules/popup.h index a3e8dc773..d694dc45b 100644 --- a/applications/services/gui/modules/popup.h +++ b/applications/services/gui/modules/popup.h @@ -41,7 +41,7 @@ void popup_free(Popup* popup); */ View* popup_get_view(Popup* popup); -/** Set popup header text +/** Set popup timeout callback * * @param popup Popup instance * @param callback PopupCallback diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index 843ab5543..43e2cbeff 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -78,6 +78,54 @@ App( sources=["modules/js_gui/text_input.c"], ) +App( + appid="js_gui__number_input", + apptype=FlipperAppType.PLUGIN, + entry_point="js_view_number_input_ep", + requires=["js_app"], + sources=["modules/js_gui/number_input.c"], +) + +App( + appid="js_gui__button_panel", + apptype=FlipperAppType.PLUGIN, + entry_point="js_view_button_panel_ep", + requires=["js_app"], + sources=["modules/js_gui/button_panel.c"], +) + +App( + appid="js_gui__popup", + apptype=FlipperAppType.PLUGIN, + entry_point="js_view_popup_ep", + requires=["js_app"], + sources=["modules/js_gui/popup.c"], +) + +App( + appid="js_gui__button_menu", + apptype=FlipperAppType.PLUGIN, + entry_point="js_view_button_menu_ep", + requires=["js_app"], + sources=["modules/js_gui/button_menu.c"], +) + +App( + appid="js_gui__menu", + apptype=FlipperAppType.PLUGIN, + entry_point="js_view_menu_ep", + requires=["js_app"], + sources=["modules/js_gui/menu.c"], +) + +App( + appid="js_gui__vi_list", + apptype=FlipperAppType.PLUGIN, + entry_point="js_view_vi_list_ep", + requires=["js_app"], + sources=["modules/js_gui/vi_list.c"], +) + App( appid="js_gui__byte_input", apptype=FlipperAppType.PLUGIN, diff --git a/applications/system/js_app/examples/apps/Scripts/gui.js b/applications/system/js_app/examples/apps/Scripts/gui.js index bc569cee0..3a179fc9f 100644 --- a/applications/system/js_app/examples/apps/Scripts/gui.js +++ b/applications/system/js_app/examples/apps/Scripts/gui.js @@ -9,6 +9,12 @@ let byteInputView = require("gui/byte_input"); let textBoxView = require("gui/text_box"); let dialogView = require("gui/dialog"); let filePicker = require("gui/file_picker"); +let buttonMenuView = require("gui/button_menu"); +let buttonPanelView = require("gui/button_panel"); +let menuView = require("gui/menu"); +let numberInputView = require("gui/number_input"); +let popupView = require("gui/popup"); +let viListView = require("gui/vi_list"); let widget = require("gui/widget"); let icon = require("gui/icon"); let flipper = require("flipper"); @@ -27,6 +33,11 @@ let stopwatchWidgetElements = [ { element: "button", button: "right", text: "Back" }, ]; +// icons for the button panel +let offIcons = [icon.getBuiltin("off_19x20"), icon.getBuiltin("off_hover_19x20")]; +let powerIcons = [icon.getBuiltin("power_19x20"), icon.getBuiltin("power_hover_19x20")]; +let settingsIcon = icon.getBuiltin("Settings_14"); + // declare view instances let views = { loading: loadingView.make(), @@ -48,19 +59,57 @@ let views = { text: "This is a very long string that demonstrates the TextBox view. Use the D-Pad to scroll backwards and forwards.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse rhoncus est malesuada quam egestas ultrices. Maecenas non eros a nulla eleifend vulputate et ut risus. Quisque in mauris mattis, venenatis risus eget, aliquam diam. Fusce pretium feugiat mauris, ut faucibus ex volutpat in. Phasellus volutpat ex sed gravida consectetur. Aliquam sed lectus feugiat, tristique lectus et, bibendum lacus. Ut sit amet augue eu sapien elementum aliquam quis vitae tortor. Vestibulum quis commodo odio. In elementum fermentum massa, eu pellentesque nibh cursus at. Integer eleifend lacus nec purus elementum sodales. Nulla elementum neque urna, non vulputate massa semper sed. Fusce ut nisi vitae dui blandit congue pretium vitae turpis.", }), stopwatchWidget: widget.makeWith({}, stopwatchWidgetElements), + buttonMenu: buttonMenuView.makeWith({ + header: "Header" + }, [ + { type: "common", label: "Test" }, + { type: "control", label: "Test2" }, + ]), + buttonPanel: buttonPanelView.makeWith({ + matrixSizeX: 2, + matrixSizeY: 2, + }, [ + { type: "button", x: 0, y: 0, matrixX: 0, matrixY: 0, icon: offIcons[0], iconSelected: offIcons[1] }, + { type: "button", x: 30, y: 30, matrixX: 1, matrixY: 1, icon: powerIcons[0], iconSelected: powerIcons[1] }, + { type: "label", x: 0, y: 50, text: "Label", font: "primary" }, + ]), + menu: menuView.makeWith({}, [ + { label: "One", icon: settingsIcon }, + { label: "Two", icon: settingsIcon }, + { label: "three", icon: settingsIcon }, + ]), + numberKbd: numberInputView.makeWith({ + header: "Number input", + defaultValue: 100, + minValue: 0, + maxValue: 200, + }), + popup: popupView.makeWith({ + header: "Hello", + text: "I'm going to be gone\nin 2 seconds", + }), + viList: viListView.makeWith({}, [ + { label: "One", variants: ["1", "1.0"] }, + { label: "Two", variants: ["2", "2.0"] }, + ]), demos: submenuView.makeWith({ header: "Choose a demo", - items: [ - "Hourglass screen", - "Empty screen", - "Text input & Dialog", - "Byte input", - "Text box", - "File picker", - "Widget", - "Exit app", - ], - }), + }, [ + "Hourglass screen", + "Empty screen", + "Text input & Dialog", + "Byte input", + "Text box", + "File picker", + "Widget", + "Button menu", + "Button panel", + "Menu", + "Number input", + "Popup", + "Var. item list", + "Exit app", + ]), }; // demo selector @@ -92,6 +141,19 @@ eventLoop.subscribe(views.demos.chosen, function (_sub, index, gui, eventLoop, v } else if (index === 6) { gui.viewDispatcher.switchTo(views.stopwatchWidget); } else if (index === 7) { + gui.viewDispatcher.switchTo(views.buttonMenu); + } else if (index === 8) { + gui.viewDispatcher.switchTo(views.buttonPanel); + } else if (index === 9) { + gui.viewDispatcher.switchTo(views.menu); + } else if (index === 10) { + gui.viewDispatcher.switchTo(views.numberKbd); + } else if (index === 11) { + views.popup.set("timeout", 2000); + gui.viewDispatcher.switchTo(views.popup); + } else if (index === 12) { + gui.viewDispatcher.switchTo(views.viList); + } else if (index === 13) { eventLoop.stop(); } }, gui, eventLoop, views); @@ -156,6 +218,42 @@ eventLoop.subscribe(eventLoop.timer("periodic", 500), function (_sub, _item, vie return [views, stopwatchWidgetElements, halfSeconds]; }, views, stopwatchWidgetElements, 0); +// go back after popup times out +eventLoop.subscribe(views.popup.timeout, function (_sub, _item, gui, views) { + gui.viewDispatcher.switchTo(views.demos); +}, gui, views); + +// button menu callback +eventLoop.subscribe(views.buttonMenu.input, function (_sub, input, gui, views) { + views.helloDialog.set("text", "You selected #" + input.index.toString()); + views.helloDialog.set("center", "Cool!"); + gui.viewDispatcher.switchTo(views.helloDialog); +}, gui, views); + +// button panel callback +eventLoop.subscribe(views.buttonPanel.input, function (_sub, input, gui, views) { + views.helloDialog.set("text", "You selected #" + input.index.toString()); + views.helloDialog.set("center", "Cool!"); + gui.viewDispatcher.switchTo(views.helloDialog); +}, gui, views); + +// menu callback +eventLoop.subscribe(views.menu.chosen, function (_sub, index, gui, views) { + views.helloDialog.set("text", "You selected #" + index.toString()); + views.helloDialog.set("center", "Cool!"); + gui.viewDispatcher.switchTo(views.helloDialog); +}, gui, views); + +// menu callback +eventLoop.subscribe(views.numberKbd.input, function (_sub, number, gui, views) { + views.helloDialog.set("text", "You typed " + number.toString()); + views.helloDialog.set("center", "Cool!"); + gui.viewDispatcher.switchTo(views.helloDialog); +}, gui, views); + +// ignore VI list +eventLoop.subscribe(views.viList.valueUpdate, function (_sub, _item) {}); + // run UI gui.viewDispatcher.switchTo(views.demos); eventLoop.run(); diff --git a/applications/system/js_app/modules/js_gui/button_menu.c b/applications/system/js_app/modules/js_gui/button_menu.c new file mode 100644 index 000000000..e247e1635 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/button_menu.c @@ -0,0 +1,169 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include "../js_event_loop/js_event_loop.h" +#include +#include + +typedef struct { + int32_t next_index; + StrBuffer str_buffer; + + FuriMessageQueue* input_queue; + JsEventLoopContract contract; +} JsBtnMenuContext; + +typedef struct { + int32_t index; + InputType input_type; +} JsBtnMenuEvent; + +static const char* js_input_type_to_str(InputType type) { + switch(type) { + case InputTypePress: + return "press"; + case InputTypeRelease: + return "release"; + case InputTypeShort: + return "short"; + case InputTypeLong: + return "long"; + case InputTypeRepeat: + return "repeat"; + default: + furi_crash(); + } +} + +static mjs_val_t + input_transformer(struct mjs* mjs, FuriMessageQueue* queue, JsBtnMenuContext* context) { + UNUSED(context); + JsBtnMenuEvent event; + furi_check(furi_message_queue_get(queue, &event, 0) == FuriStatusOk); + + mjs_val_t event_obj = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, event_obj) { + JS_FIELD("index", mjs_mk_number(mjs, event.index)); + JS_FIELD("type", mjs_mk_string(mjs, js_input_type_to_str(event.input_type), ~0, false)); + } + + return event_obj; +} + +static void input_callback(void* ctx, int32_t index, InputType type) { + JsBtnMenuContext* context = ctx; + JsBtnMenuEvent event = { + .index = index, + .input_type = type, + }; + furi_check(furi_message_queue_put(context->input_queue, &event, 0) == FuriStatusOk); +} + +static bool matrix_header_assign( + struct mjs* mjs, + ButtonMenu* menu, + JsViewPropValue value, + JsBtnMenuContext* context) { + UNUSED(mjs); + button_menu_set_header(menu, str_buffer_make_owned_clone(&context->str_buffer, value.string)); + return true; +} + +static bool js_button_menu_add_child( + struct mjs* mjs, + ButtonMenu* menu, + JsBtnMenuContext* context, + mjs_val_t child_obj) { + static const JsValueEnumVariant js_button_menu_item_type_variants[] = { + {"common", ButtonMenuItemTypeCommon}, + {"control", ButtonMenuItemTypeControl}, + }; + static const JsValueDeclaration js_button_menu_item_type = + JS_VALUE_ENUM(ButtonMenuItemType, js_button_menu_item_type_variants); + + static const JsValueDeclaration js_button_menu_string = JS_VALUE_SIMPLE(JsValueTypeString); + + static const JsValueObjectField js_button_menu_child_fields[] = { + {"type", &js_button_menu_item_type}, + {"label", &js_button_menu_string}, + }; + static const JsValueDeclaration js_button_menu_child = + JS_VALUE_OBJECT(js_button_menu_child_fields); + + ButtonMenuItemType item_type; + const char* label; + JsValueParseStatus status; + JS_VALUE_PARSE( + mjs, + JS_VALUE_PARSE_SOURCE_VALUE(&js_button_menu_child), + JsValueParseFlagReturnOnError, + &status, + &child_obj, + &item_type, + &label); + if(status != JsValueParseStatusOk) return false; + + button_menu_add_item( + menu, + str_buffer_make_owned_clone(&context->str_buffer, label), + context->next_index++, + input_callback, + item_type, + context); + + return true; +} + +static void js_button_menu_reset_children(ButtonMenu* menu, JsBtnMenuContext* context) { + context->next_index = 0; + button_menu_reset(menu); + str_buffer_clear_all_clones(&context->str_buffer); +} + +static JsBtnMenuContext* ctx_make(struct mjs* mjs, ButtonMenu* menu, mjs_val_t view_obj) { + UNUSED(menu); + JsBtnMenuContext* context = malloc(sizeof(JsBtnMenuContext)); + *context = (JsBtnMenuContext){ + .next_index = 0, + .str_buffer = {0}, + .input_queue = furi_message_queue_alloc(1, sizeof(JsBtnMenuEvent)), + }; + context->contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeQueue, + .object = context->input_queue, + .non_timer = + { + .event = FuriEventLoopEventIn, + .transformer = (JsEventLoopTransformer)input_transformer, + .transformer_context = context, + }, + }; + mjs_set(mjs, view_obj, "input", ~0, mjs_mk_foreign(mjs, &context->contract)); + return context; +} + +static void ctx_destroy(ButtonMenu* input, JsBtnMenuContext* context, FuriEventLoop* loop) { + UNUSED(input); + furi_event_loop_maybe_unsubscribe(loop, context->input_queue); + furi_message_queue_free(context->input_queue); + str_buffer_clear_all_clones(&context->str_buffer); + free(context); +} + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)button_menu_alloc, + .free = (JsViewFree)button_menu_free, + .get_view = (JsViewGetView)button_menu_get_view, + .custom_make = (JsViewCustomMake)ctx_make, + .custom_destroy = (JsViewCustomDestroy)ctx_destroy, + .add_child = (JsViewAddChild)js_button_menu_add_child, + .reset_children = (JsViewResetChildren)js_button_menu_reset_children, + .prop_cnt = 1, + .props = { + (JsViewPropDescriptor){ + .name = "header", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)matrix_header_assign}, + }}; + +JS_GUI_VIEW_DEF(button_menu, &view_descriptor); diff --git a/applications/system/js_app/modules/js_gui/button_panel.c b/applications/system/js_app/modules/js_gui/button_panel.c new file mode 100644 index 000000000..ebd4edb57 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/button_panel.c @@ -0,0 +1,274 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include "../js_event_loop/js_event_loop.h" +#include +#include + +typedef struct { + size_t matrix_x, matrix_y; + int32_t next_index; + StrBuffer str_buffer; + + FuriMessageQueue* input_queue; + JsEventLoopContract contract; +} JsBtnPanelContext; + +typedef struct { + int32_t index; + InputType input_type; +} JsBtnPanelEvent; + +static const char* js_input_type_to_str(InputType type) { + switch(type) { + case InputTypePress: + return "press"; + case InputTypeRelease: + return "release"; + case InputTypeShort: + return "short"; + case InputTypeLong: + return "long"; + case InputTypeRepeat: + return "repeat"; + default: + furi_crash(); + } +} + +static mjs_val_t + input_transformer(struct mjs* mjs, FuriMessageQueue* queue, JsBtnPanelContext* context) { + UNUSED(context); + JsBtnPanelEvent event; + furi_check(furi_message_queue_get(queue, &event, 0) == FuriStatusOk); + + mjs_val_t event_obj = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, event_obj) { + JS_FIELD("index", mjs_mk_number(mjs, event.index)); + JS_FIELD("type", mjs_mk_string(mjs, js_input_type_to_str(event.input_type), ~0, false)); + } + + return event_obj; +} + +static void input_callback(void* ctx, int32_t index, InputType type) { + JsBtnPanelContext* context = ctx; + JsBtnPanelEvent event = { + .index = index, + .input_type = type, + }; + furi_check(furi_message_queue_put(context->input_queue, &event, 0) == FuriStatusOk); +} + +static bool matrix_size_x_assign( + struct mjs* mjs, + ButtonPanel* panel, + JsViewPropValue value, + JsBtnPanelContext* context) { + UNUSED(mjs); + context->matrix_x = value.number; + button_panel_reserve(panel, context->matrix_x, context->matrix_y); + return true; +} + +static bool matrix_size_y_assign( + struct mjs* mjs, + ButtonPanel* panel, + JsViewPropValue value, + JsBtnPanelContext* context) { + UNUSED(mjs); + context->matrix_y = value.number; + button_panel_reserve(panel, context->matrix_x, context->matrix_y); + return true; +} + +static bool js_button_panel_add_child( + struct mjs* mjs, + ButtonPanel* panel, + JsBtnPanelContext* context, + mjs_val_t child_obj) { + typedef enum { + JsButtonPanelChildTypeButton, + JsButtonPanelChildTypeLabel, + JsButtonPanelChildTypeIcon, + } JsButtonPanelChildType; + static const JsValueEnumVariant js_button_panel_child_type_variants[] = { + {"button", JsButtonPanelChildTypeButton}, + {"label", JsButtonPanelChildTypeLabel}, + {"icon", JsButtonPanelChildTypeIcon}, + }; + static const JsValueDeclaration js_button_panel_child_type = + JS_VALUE_ENUM(JsButtonPanelChildType, js_button_panel_child_type_variants); + + static const JsValueDeclaration js_button_panel_number = JS_VALUE_SIMPLE(JsValueTypeInt32); + static const JsValueObjectField js_button_panel_common_fields[] = { + {"type", &js_button_panel_child_type}, + {"x", &js_button_panel_number}, + {"y", &js_button_panel_number}, + }; + static const JsValueDeclaration js_button_panel_common = + JS_VALUE_OBJECT(js_button_panel_common_fields); + + static const JsValueDeclaration js_button_panel_pointer = + JS_VALUE_SIMPLE(JsValueTypeRawPointer); + static const JsValueObjectField js_button_panel_button_fields[] = { + {"matrixX", &js_button_panel_number}, + {"matrixY", &js_button_panel_number}, + {"icon", &js_button_panel_pointer}, + {"iconSelected", &js_button_panel_pointer}, + }; + static const JsValueDeclaration js_button_panel_button = + JS_VALUE_OBJECT(js_button_panel_button_fields); + + static const JsValueDeclaration js_button_panel_string = JS_VALUE_SIMPLE(JsValueTypeString); + static const JsValueObjectField js_button_panel_label_fields[] = { + {"text", &js_button_panel_string}, + {"font", &js_gui_font_declaration}, + }; + static const JsValueDeclaration js_button_panel_label = + JS_VALUE_OBJECT(js_button_panel_label_fields); + + static const JsValueObjectField js_button_panel_icon_fields[] = { + {"icon", &js_button_panel_pointer}, + }; + static const JsValueDeclaration js_button_panel_icon = + JS_VALUE_OBJECT(js_button_panel_icon_fields); + + JsButtonPanelChildType child_type; + int32_t x, y; + JsValueParseStatus status; + JS_VALUE_PARSE( + mjs, + JS_VALUE_PARSE_SOURCE_VALUE(&js_button_panel_common), + JsValueParseFlagReturnOnError, + &status, + &child_obj, + &child_type, + &x, + &y); + if(status != JsValueParseStatusOk) return false; + + switch(child_type) { + case JsButtonPanelChildTypeButton: { + int32_t matrix_x, matrix_y; + const Icon *icon, *icon_selected; + JS_VALUE_PARSE( + mjs, + JS_VALUE_PARSE_SOURCE_VALUE(&js_button_panel_button), + JsValueParseFlagReturnOnError, + &status, + &child_obj, + &matrix_x, + &matrix_y, + &icon, + &icon_selected); + if(status != JsValueParseStatusOk) return false; + button_panel_add_item( + panel, + context->next_index++, + matrix_x, + matrix_y, + x, + y, + icon, + icon_selected, + (ButtonItemCallback)input_callback, + context); + break; + } + + case JsButtonPanelChildTypeLabel: { + const char* text; + Font font; + JS_VALUE_PARSE( + mjs, + JS_VALUE_PARSE_SOURCE_VALUE(&js_button_panel_label), + JsValueParseFlagReturnOnError, + &status, + &child_obj, + &text, + &font); + if(status != JsValueParseStatusOk) return false; + button_panel_add_label( + panel, x, y, font, str_buffer_make_owned_clone(&context->str_buffer, text)); + break; + } + + case JsButtonPanelChildTypeIcon: { + const Icon* icon; + JS_VALUE_PARSE( + mjs, + JS_VALUE_PARSE_SOURCE_VALUE(&js_button_panel_icon), + JsValueParseFlagReturnOnError, + &status, + &child_obj, + &icon); + if(status != JsValueParseStatusOk) return false; + button_panel_add_icon(panel, x, y, icon); + break; + } + } + + return true; +} + +static void js_button_panel_reset_children(ButtonPanel* panel, JsBtnPanelContext* context) { + context->next_index = 0; + button_panel_reset(panel); + button_panel_reserve(panel, context->matrix_x, context->matrix_y); + str_buffer_clear_all_clones(&context->str_buffer); +} + +static JsBtnPanelContext* ctx_make(struct mjs* mjs, ButtonPanel* panel, mjs_val_t view_obj) { + UNUSED(panel); + JsBtnPanelContext* context = malloc(sizeof(JsBtnPanelContext)); + *context = (JsBtnPanelContext){ + .matrix_x = 1, + .matrix_y = 1, + .next_index = 0, + .str_buffer = {0}, + .input_queue = furi_message_queue_alloc(1, sizeof(JsBtnPanelEvent)), + }; + context->contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeQueue, + .object = context->input_queue, + .non_timer = + { + .event = FuriEventLoopEventIn, + .transformer = (JsEventLoopTransformer)input_transformer, + .transformer_context = context, + }, + }; + mjs_set(mjs, view_obj, "input", ~0, mjs_mk_foreign(mjs, &context->contract)); + return context; +} + +static void ctx_destroy(ButtonPanel* input, JsBtnPanelContext* context, FuriEventLoop* loop) { + UNUSED(input); + furi_event_loop_maybe_unsubscribe(loop, context->input_queue); + furi_message_queue_free(context->input_queue); + str_buffer_clear_all_clones(&context->str_buffer); + free(context); +} + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)button_panel_alloc, + .free = (JsViewFree)button_panel_free, + .get_view = (JsViewGetView)button_panel_get_view, + .custom_make = (JsViewCustomMake)ctx_make, + .custom_destroy = (JsViewCustomDestroy)ctx_destroy, + .add_child = (JsViewAddChild)js_button_panel_add_child, + .reset_children = (JsViewResetChildren)js_button_panel_reset_children, + .prop_cnt = 2, + .props = { + (JsViewPropDescriptor){ + .name = "matrixSizeX", + .type = JsViewPropTypeNumber, + .assign = (JsViewPropAssign)matrix_size_x_assign}, + (JsViewPropDescriptor){ + .name = "matrixSizeY", + .type = JsViewPropTypeNumber, + .assign = (JsViewPropAssign)matrix_size_y_assign}, + }}; + +JS_GUI_VIEW_DEF(button_panel, &view_descriptor); diff --git a/applications/system/js_app/modules/js_gui/icon.c b/applications/system/js_app/modules/js_gui/icon.c index 4fc6da2e0..11e26d02f 100644 --- a/applications/system/js_app/modules/js_gui/icon.c +++ b/applications/system/js_app/modules/js_gui/icon.c @@ -14,9 +14,19 @@ typedef struct { .name = #icon, .data = &I_##icon \ } +#define ANIM_ICON_DEF(icon) \ + (IconDefinition) { \ + .name = #icon, .data = &A_##icon \ + } + static const IconDefinition builtin_icons[] = { ICON_DEF(DolphinWait_59x54), ICON_DEF(js_script_10px), + ICON_DEF(off_19x20), + ICON_DEF(off_hover_19x20), + ICON_DEF(power_19x20), + ICON_DEF(power_hover_19x20), + ANIM_ICON_DEF(Settings_14), }; // Firmware's Icon struct needs a frames array, and uses a small CompressHeader diff --git a/applications/system/js_app/modules/js_gui/js_gui.c b/applications/system/js_app/modules/js_gui/js_gui.c index c20d980aa..2f7825717 100644 --- a/applications/system/js_app/modules/js_gui/js_gui.c +++ b/applications/system/js_app/modules/js_gui/js_gui.c @@ -31,6 +31,14 @@ typedef struct { void* custom_data; } JsGuiViewData; +static const JsValueEnumVariant js_gui_font_variants[] = { + {"primary", FontPrimary}, + {"secondary", FontSecondary}, + {"keyboard", FontKeyboard}, + {"bit_numbers", FontBigNumbers}, +}; +const JsValueDeclaration js_gui_font_declaration = JS_VALUE_ENUM(Font, js_gui_font_variants); + /** * @brief Transformer for custom events */ @@ -273,9 +281,12 @@ static bool /** * @brief Sets the list of children. Not available from JS. */ -static bool - js_gui_view_internal_set_children(struct mjs* mjs, mjs_val_t children, JsGuiViewData* data) { - data->descriptor->reset_children(data->specific_view, data->custom_data); +static bool js_gui_view_internal_set_children( + struct mjs* mjs, + mjs_val_t children, + JsGuiViewData* data, + bool do_reset) { + if(do_reset) data->descriptor->reset_children(data->specific_view, data->custom_data); for(size_t i = 0; i < mjs_array_length(mjs, children); i++) { mjs_val_t child = mjs_array_get(mjs, children, i); @@ -357,7 +368,7 @@ static void js_gui_view_set_children(struct mjs* mjs) { if(!data->descriptor->add_child || !data->descriptor->reset_children) JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children"); - js_gui_view_internal_set_children(mjs, children, data); + js_gui_view_internal_set_children(mjs, children, data, true); } /** @@ -450,7 +461,7 @@ static void js_gui_vf_make_with(struct mjs* mjs) { if(!data->descriptor->add_child || !data->descriptor->reset_children) JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children"); - if(!js_gui_view_internal_set_children(mjs, children, data)) return; + if(!js_gui_view_internal_set_children(mjs, children, data, false)) return; } mjs_return(mjs, view_obj); diff --git a/applications/system/js_app/modules/js_gui/js_gui.h b/applications/system/js_app/modules/js_gui/js_gui.h index d9d98df39..552eaee7c 100644 --- a/applications/system/js_app/modules/js_gui/js_gui.h +++ b/applications/system/js_app/modules/js_gui/js_gui.h @@ -20,6 +20,11 @@ typedef union { mjs_val_t term; } JsViewPropValue; +/** + * JS-to-C font enum mapping + */ +extern const JsValueDeclaration js_gui_font_declaration; + /** * @brief Assigns a value to a view property * diff --git a/applications/system/js_app/modules/js_gui/js_gui_api_table_i.h b/applications/system/js_app/modules/js_gui/js_gui_api_table_i.h index 852b3d107..c83a38e30 100644 --- a/applications/system/js_app/modules/js_gui/js_gui_api_table_i.h +++ b/applications/system/js_app/modules/js_gui/js_gui_api_table_i.h @@ -1,4 +1,5 @@ #include "js_gui.h" static constexpr auto js_gui_api_table = sort(create_array_t( - API_METHOD(js_gui_make_view_factory, mjs_val_t, (struct mjs*, const JsViewDescriptor*)))); + API_METHOD(js_gui_make_view_factory, mjs_val_t, (struct mjs*, const JsViewDescriptor*)), + API_VARIABLE(js_gui_font_declaration, const JsValueDeclaration))); diff --git a/applications/system/js_app/modules/js_gui/menu.c b/applications/system/js_app/modules/js_gui/menu.c new file mode 100644 index 000000000..c8e23470f --- /dev/null +++ b/applications/system/js_app/modules/js_gui/menu.c @@ -0,0 +1,105 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include "../js_event_loop/js_event_loop.h" +#include +#include + +typedef struct { + int32_t next_index; + StrBuffer str_buffer; + + FuriMessageQueue* queue; + JsEventLoopContract contract; +} JsMenuCtx; + +static mjs_val_t choose_transformer(struct mjs* mjs, FuriMessageQueue* queue, void* context) { + UNUSED(context); + uint32_t index; + furi_check(furi_message_queue_get(queue, &index, 0) == FuriStatusOk); + return mjs_mk_number(mjs, (double)index); +} + +static void choose_callback(void* context, uint32_t index) { + JsMenuCtx* ctx = context; + furi_check(furi_message_queue_put(ctx->queue, &index, 0) == FuriStatusOk); +} + +static bool + js_menu_add_child(struct mjs* mjs, Menu* menu, JsMenuCtx* context, mjs_val_t child_obj) { + static const JsValueDeclaration js_menu_string = JS_VALUE_SIMPLE(JsValueTypeString); + static const JsValueDeclaration js_menu_pointer = JS_VALUE_SIMPLE(JsValueTypeRawPointer); + + static const JsValueObjectField js_menu_child_fields[] = { + {"icon", &js_menu_pointer}, + {"label", &js_menu_string}, + }; + static const JsValueDeclaration js_menu_child = JS_VALUE_OBJECT(js_menu_child_fields); + + const Icon* icon; + const char* label; + JsValueParseStatus status; + JS_VALUE_PARSE( + mjs, + JS_VALUE_PARSE_SOURCE_VALUE(&js_menu_child), + JsValueParseFlagReturnOnError, + &status, + &child_obj, + &icon, + &label); + if(status != JsValueParseStatusOk) return false; + + menu_add_item( + menu, + str_buffer_make_owned_clone(&context->str_buffer, label), + icon, + context->next_index++, + choose_callback, + context); + + return true; +} + +static void js_menu_reset_children(Menu* menu, JsMenuCtx* context) { + context->next_index = 0; + menu_reset(menu); + str_buffer_clear_all_clones(&context->str_buffer); +} + +static JsMenuCtx* ctx_make(struct mjs* mjs, Menu* input, mjs_val_t view_obj) { + UNUSED(input); + JsMenuCtx* context = malloc(sizeof(JsMenuCtx)); + context->queue = furi_message_queue_alloc(1, sizeof(uint32_t)); + context->contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeQueue, + .object = context->queue, + .non_timer = + { + .event = FuriEventLoopEventIn, + .transformer = (JsEventLoopTransformer)choose_transformer, + }, + }; + mjs_set(mjs, view_obj, "chosen", ~0, mjs_mk_foreign(mjs, &context->contract)); + return context; +} + +static void ctx_destroy(Menu* input, JsMenuCtx* context, FuriEventLoop* loop) { + UNUSED(input); + furi_event_loop_maybe_unsubscribe(loop, context->queue); + furi_message_queue_free(context->queue); + str_buffer_clear_all_clones(&context->str_buffer); + free(context); +} + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)menu_alloc, + .free = (JsViewFree)menu_free, + .get_view = (JsViewGetView)menu_get_view, + .custom_make = (JsViewCustomMake)ctx_make, + .custom_destroy = (JsViewCustomDestroy)ctx_destroy, + .add_child = (JsViewAddChild)js_menu_add_child, + .reset_children = (JsViewResetChildren)js_menu_reset_children, + .prop_cnt = 0, + .props = {}, +}; +JS_GUI_VIEW_DEF(menu, &view_descriptor); diff --git a/applications/system/js_app/modules/js_gui/number_input.c b/applications/system/js_app/modules/js_gui/number_input.c new file mode 100644 index 000000000..2a1aa6ee6 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/number_input.c @@ -0,0 +1,130 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include "../js_event_loop/js_event_loop.h" +#include + +typedef struct { + int32_t default_val, min_val, max_val; + FuriMessageQueue* input_queue; + JsEventLoopContract contract; +} JsNumKbdContext; + +static mjs_val_t + input_transformer(struct mjs* mjs, FuriMessageQueue* queue, JsNumKbdContext* context) { + UNUSED(context); + int32_t number; + furi_check(furi_message_queue_get(queue, &number, 0) == FuriStatusOk); + return mjs_mk_number(mjs, number); +} + +static void input_callback(void* ctx, int32_t value) { + JsNumKbdContext* context = ctx; + furi_check(furi_message_queue_put(context->input_queue, &value, 0) == FuriStatusOk); +} + +static bool header_assign( + struct mjs* mjs, + NumberInput* input, + JsViewPropValue value, + JsNumKbdContext* context) { + UNUSED(mjs); + UNUSED(context); + number_input_set_header_text(input, value.string); + return true; +} + +static bool min_val_assign( + struct mjs* mjs, + NumberInput* input, + JsViewPropValue value, + JsNumKbdContext* context) { + UNUSED(mjs); + context->min_val = value.number; + number_input_set_result_callback( + input, input_callback, context, context->default_val, context->min_val, context->max_val); + return true; +} + +static bool max_val_assign( + struct mjs* mjs, + NumberInput* input, + JsViewPropValue value, + JsNumKbdContext* context) { + UNUSED(mjs); + context->max_val = value.number; + number_input_set_result_callback( + input, input_callback, context, context->default_val, context->min_val, context->max_val); + return true; +} + +static bool default_val_assign( + struct mjs* mjs, + NumberInput* input, + JsViewPropValue value, + JsNumKbdContext* context) { + UNUSED(mjs); + context->default_val = value.number; + number_input_set_result_callback( + input, input_callback, context, context->default_val, context->min_val, context->max_val); + return true; +} + +static JsNumKbdContext* ctx_make(struct mjs* mjs, NumberInput* input, mjs_val_t view_obj) { + JsNumKbdContext* context = malloc(sizeof(JsNumKbdContext)); + *context = (JsNumKbdContext){ + .default_val = 0, + .max_val = 100, + .min_val = 0, + .input_queue = furi_message_queue_alloc(1, sizeof(int32_t)), + }; + context->contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeQueue, + .object = context->input_queue, + .non_timer = + { + .event = FuriEventLoopEventIn, + .transformer = (JsEventLoopTransformer)input_transformer, + .transformer_context = context, + }, + }; + number_input_set_result_callback( + input, input_callback, context, context->default_val, context->min_val, context->max_val); + mjs_set(mjs, view_obj, "input", ~0, mjs_mk_foreign(mjs, &context->contract)); + return context; +} + +static void ctx_destroy(NumberInput* input, JsNumKbdContext* context, FuriEventLoop* loop) { + UNUSED(input); + furi_event_loop_maybe_unsubscribe(loop, context->input_queue); + furi_message_queue_free(context->input_queue); + free(context); +} + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)number_input_alloc, + .free = (JsViewFree)number_input_free, + .get_view = (JsViewGetView)number_input_get_view, + .custom_make = (JsViewCustomMake)ctx_make, + .custom_destroy = (JsViewCustomDestroy)ctx_destroy, + .prop_cnt = 4, + .props = { + (JsViewPropDescriptor){ + .name = "header", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)header_assign}, + (JsViewPropDescriptor){ + .name = "minValue", + .type = JsViewPropTypeNumber, + .assign = (JsViewPropAssign)min_val_assign}, + (JsViewPropDescriptor){ + .name = "maxValue", + .type = JsViewPropTypeNumber, + .assign = (JsViewPropAssign)max_val_assign}, + (JsViewPropDescriptor){ + .name = "defaultValue", + .type = JsViewPropTypeNumber, + .assign = (JsViewPropAssign)default_val_assign}, + }}; + +JS_GUI_VIEW_DEF(number_input, &view_descriptor); diff --git a/applications/system/js_app/modules/js_gui/popup.c b/applications/system/js_app/modules/js_gui/popup.c new file mode 100644 index 000000000..65f827ab1 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/popup.c @@ -0,0 +1,102 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include "../js_event_loop/js_event_loop.h" +#include +#include + +typedef struct { + StrBuffer str_buffer; + FuriSemaphore* semaphore; + JsEventLoopContract contract; +} JsPopupCtx; + +static void timeout_callback(JsPopupCtx* context) { + furi_check(furi_semaphore_release(context->semaphore) == FuriStatusOk); +} + +static bool + header_assign(struct mjs* mjs, Popup* popup, JsViewPropValue value, JsPopupCtx* context) { + UNUSED(mjs); + UNUSED(context); + popup_set_header( + popup, + str_buffer_make_owned_clone(&context->str_buffer, value.string), + 64, + 0, + AlignCenter, + AlignTop); + return true; +} + +static bool + text_assign(struct mjs* mjs, Popup* popup, JsViewPropValue value, JsPopupCtx* context) { + UNUSED(mjs); + UNUSED(context); + popup_set_text( + popup, + str_buffer_make_owned_clone(&context->str_buffer, value.string), + 64, + 32, + AlignCenter, + AlignCenter); + return true; +} + +static bool + timeout_assign(struct mjs* mjs, Popup* popup, JsViewPropValue value, JsPopupCtx* context) { + UNUSED(mjs); + UNUSED(context); + popup_set_timeout(popup, value.number); + popup_enable_timeout(popup); + return true; +} + +static JsPopupCtx* ctx_make(struct mjs* mjs, Popup* popup, mjs_val_t view_obj) { + JsPopupCtx* context = malloc(sizeof(JsPopupCtx)); + context->semaphore = furi_semaphore_alloc(1, 0); + context->contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeSemaphore, + .object = context->semaphore, + .non_timer = + { + .event = FuriEventLoopEventIn, + }, + }; + mjs_set(mjs, view_obj, "timeout", ~0, mjs_mk_foreign(mjs, &context->contract)); + popup_set_callback(popup, (PopupCallback)timeout_callback); + popup_set_context(popup, context); + return context; +} + +static void ctx_destroy(Popup* popup, JsPopupCtx* context, FuriEventLoop* loop) { + UNUSED(popup); + furi_event_loop_maybe_unsubscribe(loop, context->semaphore); + furi_semaphore_free(context->semaphore); + str_buffer_clear_all_clones(&context->str_buffer); + free(context); +} + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)popup_alloc, + .free = (JsViewFree)popup_free, + .get_view = (JsViewGetView)popup_get_view, + .custom_make = (JsViewCustomMake)ctx_make, + .custom_destroy = (JsViewCustomDestroy)ctx_destroy, + .prop_cnt = 3, + .props = { + (JsViewPropDescriptor){ + .name = "header", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)header_assign}, + (JsViewPropDescriptor){ + .name = "text", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)text_assign}, + (JsViewPropDescriptor){ + .name = "timeout", + .type = JsViewPropTypeNumber, + .assign = (JsViewPropAssign)timeout_assign}, + }}; + +JS_GUI_VIEW_DEF(popup, &view_descriptor); diff --git a/applications/system/js_app/modules/js_gui/submenu.c b/applications/system/js_app/modules/js_gui/submenu.c index c142bcddb..64ab1b906 100644 --- a/applications/system/js_app/modules/js_gui/submenu.c +++ b/applications/system/js_app/modules/js_gui/submenu.c @@ -6,6 +6,7 @@ #define QUEUE_LEN 2 typedef struct { + int32_t next_index; FuriMessageQueue* queue; JsEventLoopContract contract; } JsSubmenuCtx; @@ -30,18 +31,24 @@ static bool return true; } -static bool items_assign(struct mjs* mjs, Submenu* submenu, JsViewPropValue value, void* context) { - UNUSED(mjs); - submenu_reset(submenu); - size_t len = mjs_array_length(mjs, value.term); - for(size_t i = 0; i < len; i++) { - mjs_val_t item = mjs_array_get(mjs, value.term, i); - if(!mjs_is_string(item)) return false; - submenu_add_item(submenu, mjs_get_string(mjs, &item, NULL), i, choose_callback, context); - } +static bool js_submenu_add_child( + struct mjs* mjs, + Submenu* submenu, + JsSubmenuCtx* context, + mjs_val_t child_obj) { + const char* str = mjs_get_string(mjs, &child_obj, NULL); + if(!str) return false; + + submenu_add_item(submenu, str, context->next_index++, choose_callback, context); + return true; } +static void js_submenu_reset_children(Submenu* submenu, JsSubmenuCtx* context) { + context->next_index = 0; + submenu_reset(submenu); +} + static JsSubmenuCtx* ctx_make(struct mjs* mjs, Submenu* input, mjs_val_t view_obj) { UNUSED(input); JsSubmenuCtx* context = malloc(sizeof(JsSubmenuCtx)); @@ -73,15 +80,13 @@ static const JsViewDescriptor view_descriptor = { .get_view = (JsViewGetView)submenu_get_view, .custom_make = (JsViewCustomMake)ctx_make, .custom_destroy = (JsViewCustomDestroy)ctx_destroy, - .prop_cnt = 2, + .add_child = (JsViewAddChild)js_submenu_add_child, + .reset_children = (JsViewResetChildren)js_submenu_reset_children, + .prop_cnt = 1, .props = { (JsViewPropDescriptor){ .name = "header", .type = JsViewPropTypeString, .assign = (JsViewPropAssign)header_assign}, - (JsViewPropDescriptor){ - .name = "items", - .type = JsViewPropTypeArr, - .assign = (JsViewPropAssign)items_assign}, }}; JS_GUI_VIEW_DEF(submenu, &view_descriptor); diff --git a/applications/system/js_app/modules/js_gui/vi_list.c b/applications/system/js_app/modules/js_gui/vi_list.c new file mode 100644 index 000000000..bdea2fba1 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/vi_list.c @@ -0,0 +1,163 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include "../js_event_loop/js_event_loop.h" +#include +#include + +typedef struct { + StrBuffer str_buffer; + + // let mjs do the memory management heavy lifting, store children in a js array + struct mjs* mjs; + mjs_val_t children; + VariableItemList* list; + + FuriMessageQueue* input_queue; + JsEventLoopContract contract; +} JsViListContext; + +typedef struct { + int32_t item_index; + int32_t value_index; +} JsViListEvent; + +static mjs_val_t + input_transformer(struct mjs* mjs, FuriMessageQueue* queue, JsViListContext* context) { + UNUSED(context); + JsViListEvent event; + furi_check(furi_message_queue_get(queue, &event, 0) == FuriStatusOk); + + mjs_val_t event_obj = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, event_obj) { + JS_FIELD("itemIndex", mjs_mk_number(mjs, event.item_index)); + JS_FIELD("valueIndex", mjs_mk_number(mjs, event.value_index)); + } + + return event_obj; +} + +static void js_vi_list_change_callback(VariableItem* item) { + JsViListContext* context = variable_item_get_context(item); + struct mjs* mjs = context->mjs; + uint8_t item_index = variable_item_list_get_selected_item_index(context->list); + uint8_t value_index = variable_item_get_current_value_index(item); + + // type safety ensured in add_child + mjs_val_t variants = mjs_array_get(mjs, context->children, item_index); + mjs_val_t variant = mjs_array_get(mjs, variants, value_index); + variable_item_set_current_value_text(item, mjs_get_string(mjs, &variant, NULL)); + + JsViListEvent event = { + .item_index = item_index, + .value_index = value_index, + }; + furi_check(furi_message_queue_put(context->input_queue, &event, 0) == FuriStatusOk); +} + +static bool js_vi_list_add_child( + struct mjs* mjs, + VariableItemList* list, + JsViListContext* context, + mjs_val_t child_obj) { + static const JsValueDeclaration js_vi_list_string = JS_VALUE_SIMPLE(JsValueTypeString); + static const JsValueDeclaration js_vi_list_arr = JS_VALUE_SIMPLE(JsValueTypeAnyArray); + static const JsValueDeclaration js_vi_list_int_default_0 = + JS_VALUE_SIMPLE_W_DEFAULT(JsValueTypeInt32, int32_val, 0); + + static const JsValueObjectField js_vi_list_child_fields[] = { + {"label", &js_vi_list_string}, + {"variants", &js_vi_list_arr}, + {"defaultSelected", &js_vi_list_int_default_0}, + }; + static const JsValueDeclaration js_vi_list_child = + JS_VALUE_OBJECT_W_DEFAULTS(js_vi_list_child_fields); + + JsValueParseStatus status; + const char* label; + mjs_val_t variants; + int32_t default_selected; + JS_VALUE_PARSE( + mjs, + JS_VALUE_PARSE_SOURCE_VALUE(&js_vi_list_child), + JsValueParseFlagReturnOnError, + &status, + &child_obj, + &label, + &variants, + &default_selected); + if(status != JsValueParseStatusOk) return false; + + size_t variants_cnt = mjs_array_length(mjs, variants); + for(size_t i = 0; i < variants_cnt; i++) + if(!mjs_is_string(mjs_array_get(mjs, variants, i))) return false; + + VariableItem* item = variable_item_list_add( + list, + str_buffer_make_owned_clone(&context->str_buffer, label), + variants_cnt, + js_vi_list_change_callback, + context); + variable_item_set_current_value_index(item, default_selected); + mjs_val_t default_variant = mjs_array_get(mjs, variants, default_selected); + variable_item_set_current_value_text(item, mjs_get_string(mjs, &default_variant, NULL)); + + mjs_array_push(context->mjs, context->children, variants); + + return true; +} + +static void js_vi_list_reset_children(VariableItemList* list, JsViListContext* context) { + mjs_disown(context->mjs, &context->children); + context->children = mjs_mk_array(context->mjs); + mjs_own(context->mjs, &context->children); + + variable_item_list_reset(list); + str_buffer_clear_all_clones(&context->str_buffer); +} + +static JsViListContext* ctx_make(struct mjs* mjs, VariableItemList* list, mjs_val_t view_obj) { + JsViListContext* context = malloc(sizeof(JsViListContext)); + *context = (JsViListContext){ + .str_buffer = {0}, + .mjs = mjs, + .children = mjs_mk_array(mjs), + .list = list, + .input_queue = furi_message_queue_alloc(1, sizeof(JsViListEvent)), + }; + mjs_own(context->mjs, &context->children); + context->contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeQueue, + .object = context->input_queue, + .non_timer = + { + .event = FuriEventLoopEventIn, + .transformer = (JsEventLoopTransformer)input_transformer, + .transformer_context = context, + }, + }; + mjs_set(mjs, view_obj, "valueUpdate", ~0, mjs_mk_foreign(mjs, &context->contract)); + return context; +} + +static void ctx_destroy(VariableItemList* input, JsViListContext* context, FuriEventLoop* loop) { + UNUSED(input); + furi_event_loop_maybe_unsubscribe(loop, context->input_queue); + furi_message_queue_free(context->input_queue); + str_buffer_clear_all_clones(&context->str_buffer); + free(context); +} + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)variable_item_list_alloc, + .free = (JsViewFree)variable_item_list_free, + .get_view = (JsViewGetView)variable_item_list_get_view, + .custom_make = (JsViewCustomMake)ctx_make, + .custom_destroy = (JsViewCustomDestroy)ctx_destroy, + .add_child = (JsViewAddChild)js_vi_list_add_child, + .reset_children = (JsViewResetChildren)js_vi_list_reset_children, + .prop_cnt = 0, + .props = {}, +}; + +JS_GUI_VIEW_DEF(vi_list, &view_descriptor); diff --git a/applications/system/js_app/packages/fz-sdk/gui/button_menu.d.ts b/applications/system/js_app/packages/fz-sdk/gui/button_menu.d.ts new file mode 100644 index 000000000..3b5dd2359 --- /dev/null +++ b/applications/system/js_app/packages/fz-sdk/gui/button_menu.d.ts @@ -0,0 +1,40 @@ +/** + * Displays a list of buttons. + * + * Sample screenshot of the view + * + * ```js + * let eventLoop = require("event_loop"); + * let gui = require("gui"); + * let buttonMenuView = require("gui/button_menu"); + * ``` + * + * This module depends on the `gui` module, which in turn depends on the + * `event_loop` module, so they _must_ be imported in this order. It is also + * recommended to conceptualize these modules first before using this one. + * + * # Example + * For an example refer to the `gui.js` example script. + * + * # View props + * - `header`: Textual header above the buttons + * + * @version Added in JS SDK 0.4 + * @module + */ + +import type { View, ViewFactory, InputType } from "."; +import type { Contract } from "../event_loop"; + +type Props = { + header: string; +}; + +type Child = { type: "common" | "control", label: string }; + +declare class ButtonMenu extends View { + input: Contract<{ index: number, type: InputType }>; +} +declare class ButtonMenuFactory extends ViewFactory { } +declare const factory: ButtonMenuFactory; +export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/button_panel.d.ts b/applications/system/js_app/packages/fz-sdk/gui/button_panel.d.ts new file mode 100644 index 000000000..dcc956fed --- /dev/null +++ b/applications/system/js_app/packages/fz-sdk/gui/button_panel.d.ts @@ -0,0 +1,49 @@ +/** + * Displays a button matrix. + * + * Sample screenshot of the view + * + * ```js + * let eventLoop = require("event_loop"); + * let gui = require("gui"); + * let buttonPanelView = require("gui/button_panel"); + * ``` + * + * This module depends on the `gui` module, which in turn depends on the + * `event_loop` module, so they _must_ be imported in this order. It is also + * recommended to conceptualize these modules first before using this one. + * + * # Example + * For an example refer to the `gui.js` example script. + * + * # View props + * - `matrixSizeX`: Width of imaginary grid used for navigation + * - `matrixSizeY`: Height of imaginary grid used for navigation + * + * @version Added in JS SDK 0.4 + * @module + */ + +import type { View, ViewFactory, Font, InputType } from "."; +import type { Contract } from "../event_loop"; +import { IconData } from "./icon"; + +type Props = { + matrixSizeX: number, + matrixSizeY: number, +}; + +type Position = { x: number, y: number }; + +type ButtonChild = { type: "button", matrixX: number, matrixY: number, icon: IconData, iconSelected: IconData } & Position; +type LabelChild = { type: "label", font: Font, text: string } & Position; +type IconChild = { type: "icon", icon: IconData }; + +type Child = ButtonChild | LabelChild | IconChild; + +declare class ButtonPanel extends View { + input: Contract<{ index: number, type: InputType }>; +} +declare class ButtonPanelFactory extends ViewFactory { } +declare const factory: ButtonPanelFactory; +export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/icon.d.ts b/applications/system/js_app/packages/fz-sdk/gui/icon.d.ts index 03fc8c53b..8f6e69311 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/icon.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/icon.d.ts @@ -1,4 +1,7 @@ -export type BuiltinIcon = "DolphinWait_59x54" | "js_script_10px"; +export type BuiltinIcon = "DolphinWait_59x54" | "js_script_10px" + | "off_19x20" | "off_hover_19x20" + | "power_19x20" | "power_hover_19x20" + | "Settings_14"; export type IconData = symbol & { "__tag__": "icon" }; // introducing a nominal type in a hacky way; the `__tag__` property doesn't really exist. diff --git a/applications/system/js_app/packages/fz-sdk/gui/index.d.ts b/applications/system/js_app/packages/fz-sdk/gui/index.d.ts index 77e5891cd..1c1d14a79 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/index.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/index.d.ts @@ -24,24 +24,23 @@ * ### View * In Flipper's terminology, a "View" is a fullscreen design element that * assumes control over the entire viewport and all input events. Different - * types of views are available (not all of which are unfortunately currently - * implemented in JS): + * types of views are available: * | View | Has JS adapter? | * |----------------------|-----------------------| - * | `button_menu` | ❌ | - * | `button_panel` | ❌ | + * | `button_menu` | ✅ | + * | `button_panel` | ✅ | * | `byte_input` | ✅ | * | `dialog_ex` | ✅ (as `dialog`) | * | `empty_screen` | ✅ | * | `file_browser` | ✅ (as `file_picker`) | * | `loading` | ✅ | - * | `menu` | ❌ | - * | `number_input` | ❌ | - * | `popup` | ❌ | + * | `menu` | ✅ | + * | `number_input` | ✅ | + * | `popup` | ✅ | * | `submenu` | ✅ | * | `text_box` | ✅ | * | `text_input` | ✅ | - * | `variable_item_list` | ❌ | + * | `variable_item_list` | ✅ (as `vi_list`) | * | `widget` | ✅ | * * In JS, each view has its own set of properties (or just "props"). The @@ -119,6 +118,9 @@ import type { Contract } from "../event_loop"; +export type Font = "primary" | "secondary" | "keyboard" | "big_numbers"; +export type InputType = "press" | "release" | "short" | "long" | "repeat"; + type Properties = { [K: string]: any }; export declare class View { diff --git a/applications/system/js_app/packages/fz-sdk/gui/menu.d.ts b/applications/system/js_app/packages/fz-sdk/gui/menu.d.ts new file mode 100644 index 000000000..a72fe4b77 --- /dev/null +++ b/applications/system/js_app/packages/fz-sdk/gui/menu.d.ts @@ -0,0 +1,38 @@ +/** + * A list of selectable entries consisting of an icon and a label. + * + * Sample screenshot of the view + * + * ```js + * let eventLoop = require("event_loop"); + * let gui = require("gui"); + * let submenuView = require("gui/menu"); + * ``` + * + * This module depends on the `gui` module, which in turn depends on the + * `event_loop` module, so they _must_ be imported in this order. It is also + * recommended to conceptualize these modules first before using this one. + * + * # Example + * For an example refer to the GUI example. + * + * # View props + * This view doesn't have any props. + * + * @version Added in JS SDK 0.1 + * @version API changed in JS SDK 0.4 + * @module + */ + +import type { View, ViewFactory } from "."; +import type { Contract } from "../event_loop"; +import type { IconData } from "./icon"; + +type Props = {}; +type Child = { icon: IconData, label: string }; +declare class Submenu extends View { + chosen: Contract; +} +declare class SubmenuFactory extends ViewFactory { } +declare const factory: SubmenuFactory; +export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/number_input.d.ts b/applications/system/js_app/packages/fz-sdk/gui/number_input.d.ts new file mode 100644 index 000000000..4882c85fc --- /dev/null +++ b/applications/system/js_app/packages/fz-sdk/gui/number_input.d.ts @@ -0,0 +1,44 @@ +/** + * Displays a number input keyboard. + * + * Sample screenshot of the view + * + * ```js + * let eventLoop = require("event_loop"); + * let gui = require("gui"); + * let numberInputView = require("gui/number_input"); + * ``` + * + * This module depends on the `gui` module, which in turn depends on the + * `event_loop` module, so they _must_ be imported in this order. It is also + * recommended to conceptualize these modules first before using this one. + * + * # Example + * For an example refer to the `gui.js` example script. + * + * # View props + * - `header`: Text displayed at the top of the screen + * - `minValue`: Minimum allowed numeric value + * - `maxValue`: Maximum allowed numeric value + * - `defaultValue`: Default numeric value + * + * @version Added in JS SDK 0.4 + * @module + */ + +import type { View, ViewFactory } from "."; +import type { Contract } from "../event_loop"; + +type Props = { + header: string, + minValue: number, + maxValue: number, + defaultValue: number, +} +type Child = never; +declare class NumberInput extends View { + input: Contract; +} +declare class NumberInputFactory extends ViewFactory { } +declare const factory: NumberInputFactory; +export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/popup.d.ts b/applications/system/js_app/packages/fz-sdk/gui/popup.d.ts new file mode 100644 index 000000000..7f77b79f8 --- /dev/null +++ b/applications/system/js_app/packages/fz-sdk/gui/popup.d.ts @@ -0,0 +1,43 @@ +/** + * Like a Dialog, but with a built-in timer. + * + * Sample screenshot of the view + * + * ```js + * let eventLoop = require("event_loop"); + * let gui = require("gui"); + * let popupView = require("gui/popup"); + * ``` + * + * This module depends on the `gui` module, which in turn depends on the + * `event_loop` module, so they _must_ be imported in this order. It is also + * recommended to conceptualize these modules first before using this one. + * + * # Example + * For an example refer to the `gui.js` example script. + * + * # View props + * - `header`: Text displayed in bold at the top of the screen + * - `text`: Text displayed in the middle of the string + * - `timeout`: Timeout, in milliseconds, after which the event will fire. The + * timer starts counting down when this property is assigned. + * + * @version Added in JS SDK 0.1 + * @module + */ + +import type { View, ViewFactory } from "."; +import type { Contract } from "../event_loop"; + +type Props = { + header: string, + text: string, + timeout: number, +} +type Child = never; +declare class Popup extends View { + timeout: Contract; +} +declare class PopupFactory extends ViewFactory { } +declare const factory: PopupFactory; +export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/submenu.d.ts b/applications/system/js_app/packages/fz-sdk/gui/submenu.d.ts index e73856bee..9fb8d51cf 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/submenu.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/submenu.d.ts @@ -18,9 +18,9 @@ * * # View props * - `header`: Text displayed at the top of the screen in bold - * - `items`: Array of selectable textual items * * @version Added in JS SDK 0.1 + * @version API changed in JS SDK 0.4 * @module */ @@ -29,9 +29,8 @@ import type { Contract } from "../event_loop"; type Props = { header: string, - items: string[], }; -type Child = never; +type Child = string; declare class Submenu extends View { chosen: Contract; } diff --git a/applications/system/js_app/packages/fz-sdk/gui/vi_list.d.ts b/applications/system/js_app/packages/fz-sdk/gui/vi_list.d.ts new file mode 100644 index 000000000..73826338c --- /dev/null +++ b/applications/system/js_app/packages/fz-sdk/gui/vi_list.d.ts @@ -0,0 +1,38 @@ +/** + * Displays a list of settings-like variable items. + * + * Sample screenshot of the view + * + * ```js + * let eventLoop = require("event_loop"); + * let gui = require("gui"); + * let viListView = require("gui/vi_list"); + * ``` + * + * This module depends on the `gui` module, which in turn depends on the + * `event_loop` module, so they _must_ be imported in this order. It is also + * recommended to conceptualize these modules first before using this one. + * + * # Example + * For an example refer to the `gui.js` example script. + * + * # View props + * This view doesn't have any props + * + * @version Added in JS SDK 0.4 + * @module + */ + +import type { View, ViewFactory } from "."; +import type { Contract } from "../event_loop"; + +type Props = {}; + +type Child = { label: string, variants: string[] }; + +declare class ViList extends View { + valueUpdate: Contract<{ itemIndex: number, valueIndex: number }>; +} +declare class ViListFactory extends ViewFactory { } +declare const factory: ViListFactory; +export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/package.json b/applications/system/js_app/packages/fz-sdk/package.json index 020f3d45c..aa504008b 100644 --- a/applications/system/js_app/packages/fz-sdk/package.json +++ b/applications/system/js_app/packages/fz-sdk/package.json @@ -24,4 +24,4 @@ "prompts": "^2.4.2", "serialport": "^12.0.0" } -} \ No newline at end of file +} diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index ad368e2a1..eb92c9c00 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -29,6 +29,7 @@ env.Append( File("float_tools.h"), File("value_index.h"), File("tar/tar_archive.h"), + File("str_buffer.h"), File("stream/stream.h"), File("stream/file_stream.h"), File("stream/string_stream.h"), diff --git a/lib/toolbox/str_buffer.c b/lib/toolbox/str_buffer.c new file mode 100644 index 000000000..a46a3f27b --- /dev/null +++ b/lib/toolbox/str_buffer.c @@ -0,0 +1,18 @@ +#include "str_buffer.h" + +const char* str_buffer_make_owned_clone(StrBuffer* buffer, const char* str) { + char* owned = strdup(str); + buffer->n_owned_strings++; + buffer->owned_strings = + realloc(buffer->owned_strings, buffer->n_owned_strings * sizeof(const char*)); // -V701 + buffer->owned_strings[buffer->n_owned_strings - 1] = owned; + return owned; +} + +void str_buffer_clear_all_clones(StrBuffer* buffer) { + for(size_t i = 0; i < buffer->n_owned_strings; i++) { + free(buffer->owned_strings[i]); + } + free(buffer->owned_strings); + buffer->owned_strings = NULL; +} diff --git a/lib/toolbox/str_buffer.h b/lib/toolbox/str_buffer.h new file mode 100644 index 000000000..9623065c8 --- /dev/null +++ b/lib/toolbox/str_buffer.h @@ -0,0 +1,47 @@ +/** + * @file str_buffer.h + * + * Allows you to create an owned clone of however many strings that you need, + * then free all of them at once. Essentially the simplest possible append-only + * unindexable array of owned C-style strings. + */ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief StrBuffer instance + * + * Place this struct directly wherever you want, it doesn't have to be `alloc`ed + * and `free`d. + */ +typedef struct { + char** owned_strings; + size_t n_owned_strings; +} StrBuffer; + +/** + * @brief Makes a owned duplicate of the provided string + * + * @param[in] buffer StrBuffer instance + * @param[in] str Input C-style string + * + * @returns C-style string that contains to be valid event after `str` becomes + * invalid + */ +const char* str_buffer_make_owned_clone(StrBuffer* buffer, const char* str); + +/** + * @brief Clears all owned duplicates + * + * @param[in] buffer StrBuffer instance + */ +void str_buffer_clear_all_clones(StrBuffer* buffer); + +#ifdef __cplusplus +} +#endif diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 2bbf81e7a..0590a16b8 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -173,6 +173,7 @@ Header,+,lib/toolbox/protocols/protocol_dict.h,, Header,+,lib/toolbox/pulse_protocols/pulse_glue.h,, Header,+,lib/toolbox/saved_struct.h,, Header,+,lib/toolbox/simple_array.h,, +Header,+,lib/toolbox/str_buffer.h,, Header,+,lib/toolbox/stream/buffered_file_stream.h,, Header,+,lib/toolbox/stream/file_stream.h,, Header,+,lib/toolbox/stream/stream.h,, @@ -2632,6 +2633,8 @@ Function,+,storage_simply_remove,_Bool,"Storage*, const char*" Function,+,storage_simply_remove_recursive,_Bool,"Storage*, const char*" Function,-,stpcpy,char*,"char*, const char*" Function,-,stpncpy,char*,"char*, const char*, size_t" +Function,+,str_buffer_clear_all_clones,void,StrBuffer* +Function,+,str_buffer_make_owned_clone,const char*,"StrBuffer*, const char*" Function,+,strcasecmp,int,"const char*, const char*" Function,-,strcasecmp_l,int,"const char*, const char*, locale_t" Function,+,strcasestr,char*,"const char*, const char*" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 2370133d6..1169749cd 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -245,6 +245,7 @@ Header,+,lib/toolbox/protocols/protocol_dict.h,, Header,+,lib/toolbox/pulse_protocols/pulse_glue.h,, Header,+,lib/toolbox/saved_struct.h,, Header,+,lib/toolbox/simple_array.h,, +Header,+,lib/toolbox/str_buffer.h,, Header,+,lib/toolbox/stream/buffered_file_stream.h,, Header,+,lib/toolbox/stream/file_stream.h,, Header,+,lib/toolbox/stream/stream.h,, @@ -3322,6 +3323,8 @@ Function,+,storage_simply_remove,_Bool,"Storage*, const char*" Function,+,storage_simply_remove_recursive,_Bool,"Storage*, const char*" Function,-,stpcpy,char*,"char*, const char*" Function,-,stpncpy,char*,"char*, const char*, size_t" +Function,+,str_buffer_clear_all_clones,void,StrBuffer* +Function,+,str_buffer_make_owned_clone,const char*,"StrBuffer*, const char*" Function,+,strcasecmp,int,"const char*, const char*" Function,-,strcasecmp_l,int,"const char*, const char*, locale_t" Function,+,strcasestr,char*,"const char*, const char*" From d17fd8ed7f63fdb424c4afd0c0a2ed07875e8154 Mon Sep 17 00:00:00 2001 From: Aerosnail <63903087+Aerosnail@users.noreply.github.com> Date: Thu, 25 Sep 2025 14:15:43 +0200 Subject: [PATCH 078/192] SubGhz: fix TIM17 config not applied immediately (#4205) Co-authored-by: hedger --- applications/drivers/subghz/cc1101_ext/cc1101_ext.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index 357214505..3807dffa6 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -553,11 +553,13 @@ void subghz_device_cc1101_ext_start_async_rx( furi_hal_bus_enable(FuriHalBusTIM17); // Configure TIM + LL_TIM_InitTypeDef TIM_InitStruct = {0}; //Set the timer resolution to 2 us - LL_TIM_SetPrescaler(TIM17, (64 << 1) - 1); - LL_TIM_SetCounterMode(TIM17, LL_TIM_COUNTERMODE_UP); - LL_TIM_SetAutoReload(TIM17, 0xFFFF); - LL_TIM_SetClockDivision(TIM17, LL_TIM_CLOCKDIVISION_DIV1); + TIM_InitStruct.Prescaler = (64 << 1) - 1; + TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP; + TIM_InitStruct.Autoreload = 0xFFFF; + TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1; + LL_TIM_Init(TIM17, &TIM_InitStruct); // Timer: advanced LL_TIM_SetClockSource(TIM17, LL_TIM_CLOCKSOURCE_INTERNAL); From 013c5ffa52e5c78f018c820f2e77a984cc1d26a9 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Thu, 25 Sep 2025 16:34:06 +0200 Subject: [PATCH 079/192] BadUSB: Fix case sensitive key combos, fix modifiers in HOLD/RELEASE (#4216) * BadUSB: Fix key combos main keys being case sensitive * BadUSB: Fix modifier keys with HOLD/RELEASE commands --------- Co-authored-by: hedger --- applications/main/bad_usb/helpers/ducky_script.c | 15 ++++++++++----- .../main/bad_usb/helpers/ducky_script_commands.c | 4 ++-- .../main/bad_usb/helpers/ducky_script_i.h | 4 +++- .../main/bad_usb/helpers/ducky_script_keycodes.c | 12 ++++++++++++ 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/applications/main/bad_usb/helpers/ducky_script.c b/applications/main/bad_usb/helpers/ducky_script.c index 99034028d..3ed38e805 100644 --- a/applications/main/bad_usb/helpers/ducky_script.c +++ b/applications/main/bad_usb/helpers/ducky_script.c @@ -48,13 +48,20 @@ bool ducky_is_line_end(const char chr) { return (chr == ' ') || (chr == '\0') || (chr == '\r') || (chr == '\n'); } -uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_chars) { +uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_modifiers) { uint16_t keycode = ducky_get_keycode_by_name(param); if(keycode != HID_KEYBOARD_NONE) { return keycode; } - if((accept_chars) && (strlen(param) > 0)) { + if(accept_modifiers) { + uint16_t keycode = ducky_get_modifier_keycode_by_name(param); + if(keycode != HID_KEYBOARD_NONE) { + return keycode; + } + } + + if(strlen(param) > 0) { return BADUSB_ASCII_TO_KEY(bad_usb, param[0]) & 0xFF; } return 0; @@ -211,9 +218,7 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) { // Main key char next_char = *line_cstr; - uint16_t main_key = ducky_get_keycode_by_name(line_cstr); - if(!main_key && next_char) main_key = BADUSB_ASCII_TO_KEY(bad_usb, next_char); - key = modifiers | main_key; + key = modifiers | ducky_get_keycode(bad_usb, line_cstr, false); if(key == 0 && next_char) ducky_error(bad_usb, "No keycode defined for %s", line_cstr); diff --git a/applications/main/bad_usb/helpers/ducky_script_commands.c b/applications/main/bad_usb/helpers/ducky_script_commands.c index 1b4ff55cb..95b9be75f 100644 --- a/applications/main/bad_usb/helpers/ducky_script_commands.c +++ b/applications/main/bad_usb/helpers/ducky_script_commands.c @@ -92,7 +92,7 @@ static int32_t ducky_fnc_sysrq(BadUsbScript* bad_usb, const char* line, int32_t UNUSED(param); line = &line[ducky_get_command_len(line) + 1]; - uint16_t key = ducky_get_keycode(bad_usb, line, true); + uint16_t key = ducky_get_keycode(bad_usb, line, false); bad_usb->hid->kb_press(bad_usb->hid_inst, KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN); bad_usb->hid->kb_press(bad_usb->hid_inst, key); bad_usb->hid->release_all(bad_usb->hid_inst); @@ -196,7 +196,7 @@ static int32_t ducky_fnc_globe(BadUsbScript* bad_usb, const char* line, int32_t UNUSED(param); line = &line[ducky_get_command_len(line) + 1]; - uint16_t key = ducky_get_keycode(bad_usb, line, true); + uint16_t key = ducky_get_keycode(bad_usb, line, false); if(key == HID_KEYBOARD_NONE) { return ducky_error(bad_usb, "No keycode defined for %s", line); } diff --git a/applications/main/bad_usb/helpers/ducky_script_i.h b/applications/main/bad_usb/helpers/ducky_script_i.h index 2b15c2586..19c2f545a 100644 --- a/applications/main/bad_usb/helpers/ducky_script_i.h +++ b/applications/main/bad_usb/helpers/ducky_script_i.h @@ -48,7 +48,7 @@ struct BadUsbScript { size_t string_print_pos; }; -uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_chars); +uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_modifiers); uint32_t ducky_get_command_len(const char* line); @@ -56,6 +56,8 @@ bool ducky_is_line_end(const char chr); uint16_t ducky_get_next_modifier_keycode_by_name(const char** param); +uint16_t ducky_get_modifier_keycode_by_name(const char* param); + uint16_t ducky_get_keycode_by_name(const char* param); uint16_t ducky_get_media_keycode_by_name(const char* param); diff --git a/applications/main/bad_usb/helpers/ducky_script_keycodes.c b/applications/main/bad_usb/helpers/ducky_script_keycodes.c index ce957bb4e..77cc324d3 100644 --- a/applications/main/bad_usb/helpers/ducky_script_keycodes.c +++ b/applications/main/bad_usb/helpers/ducky_script_keycodes.c @@ -131,6 +131,18 @@ uint16_t ducky_get_next_modifier_keycode_by_name(const char** param) { return HID_KEYBOARD_NONE; } +uint16_t ducky_get_modifier_keycode_by_name(const char* param) { + for(size_t i = 0; i < COUNT_OF(ducky_modifier_keys); i++) { + size_t key_cmd_len = strlen(ducky_modifier_keys[i].name); + if((strncmp(param, ducky_modifier_keys[i].name, key_cmd_len) == 0) && + (ducky_is_line_end(param[key_cmd_len]))) { + return ducky_modifier_keys[i].keycode; + } + } + + return HID_KEYBOARD_NONE; +} + uint16_t ducky_get_keycode_by_name(const char* param) { for(size_t i = 0; i < COUNT_OF(ducky_keys); i++) { size_t key_cmd_len = strlen(ducky_keys[i].name); From 8c0c7f1bb9c4f7f5ed75989bfd244cab52b44b06 Mon Sep 17 00:00:00 2001 From: Zinong Li <131403964+zinongli@users.noreply.github.com> Date: Thu, 25 Sep 2025 21:22:56 +0400 Subject: [PATCH 080/192] LFRFID: Show ISO-3166 Country Names For Pet Chips (#4091) * Country list header and printf * off load country list to asset * lint * Field specific getters * check string length before cutting * lfrfid: minor iso3166 cleanup --------- Co-authored-by: hedger Co-authored-by: hedger --- applications/main/lfrfid/application.fam | 1 + .../resources/lfrfid/assets/iso3166.lfrfid | 252 ++++++++++++++++++ lib/lfrfid/protocols/protocol_fdx_b.c | 23 +- lib/lfrfid/tools/iso_3166.c | 98 +++++++ lib/lfrfid/tools/iso_3166.h | 43 +++ 5 files changed, 413 insertions(+), 4 deletions(-) create mode 100644 applications/main/lfrfid/resources/lfrfid/assets/iso3166.lfrfid create mode 100644 lib/lfrfid/tools/iso_3166.c create mode 100644 lib/lfrfid/tools/iso_3166.h diff --git a/applications/main/lfrfid/application.fam b/applications/main/lfrfid/application.fam index d6fca74f4..00352098e 100644 --- a/applications/main/lfrfid/application.fam +++ b/applications/main/lfrfid/application.fam @@ -7,6 +7,7 @@ App( icon="A_125khz_14", stack_size=2 * 1024, order=20, + resources="resources", fap_libs=["assets"], fap_icon="icon.png", fap_category="RFID", diff --git a/applications/main/lfrfid/resources/lfrfid/assets/iso3166.lfrfid b/applications/main/lfrfid/resources/lfrfid/assets/iso3166.lfrfid new file mode 100644 index 000000000..f7d9d396c --- /dev/null +++ b/applications/main/lfrfid/resources/lfrfid/assets/iso3166.lfrfid @@ -0,0 +1,252 @@ +Filetype: Flipper LFRFID resources +Version: 1 +# country code: 2-letter name 3-letter name full name +0004: AF AFG Afghanistan +0248: AX ALA Åland Islands +0008: AL ALB Albania +0012: DZ DZA Algeria +0016: AS ASM American Samoa +0020: AD AND Andorra +0024: AO AGO Angola +0660: AI AIA Anguilla +0010: AQ ATA Antarctica +0028: AG ATG Antigua and Barbuda +0032: AR ARG Argentina +0051: AM ARM Armenia +0533: AW ABW Aruba +0036: AU AUS Australia +0040: AT AUT Austria +0031: AZ AZE Azerbaijan +0044: BS BHS Bahamas +0048: BH BHR Bahrain +0050: BD BGD Bangladesh +0052: BB BRB Barbados +0112: BY BLR Belarus +0056: BE BEL Belgium +0084: BZ BLZ Belize +0204: BJ BEN Benin +0060: BM BMU Bermuda +0064: BT BTN Bhutan +0068: BO BOL Bolivia, Plurinational State of +0535: BQ BES Bonaire, Sint Eustatius and Saba +0070: BA BIH Bosnia and Herzegovina +0072: BW BWA Botswana +0074: BV BVT Bouvet Island +0076: BR BRA Brazil +0086: IO IOT British Indian Ocean Territory +0096: BN BRN Brunei Darussalam +0100: BG BGR Bulgaria +0854: BF BFA Burkina Faso +0108: BI BDI Burundi +0132: CV CPV Cabo Verde +0116: KH KHM Cambodia +0120: CM CMR Cameroon +0124: CA CAN Canada +0136: KY CYM Cayman Islands +0140: CF CAF Central African Republic +0148: TD TCD Chad +0152: CL CHL Chile +0156: CN CHN China +0162: CX CXR Christmas Island +0166: CC CCK Cocos (Keeling) Islands +0170: CO COL Colombia +0174: KM COM Comoros +0178: CG COG Congo +0180: CD COD Congo, Democratic Republic of the +0184: CK COK Cook Islands +0188: CR CRI Costa Rica +0384: CI CIV Côte d'Ivoire +0191: HR HRV Croatia +0192: CU CUB Cuba +0531: CW CUW Curaçao +0196: CY CYP Cyprus +0203: CZ CZE Czechia +0208: DK DNK Denmark +0262: DJ DJI Djibouti +0212: DM DMA Dominica +0214: DO DOM Dominican Republic +0218: EC ECU Ecuador +0818: EG EGY Egypt +0222: SV SLV El Salvador +0226: GQ GNQ Equatorial Guinea +0232: ER ERI Eritrea +0233: EE EST Estonia +0748: SZ SWZ Eswatini +0231: ET ETH Ethiopia +0238: FK FLK Falkland Islands (Malvinas) +0234: FO FRO Faroe Islands +0242: FJ FJI Fiji +0246: FI FIN Finland +0250: FR FRA France +0254: GF GUF French Guiana +0258: PF PYF French Polynesia +0260: TF ATF French Southern Territories +0266: GA GAB Gabon +0270: GM GMB Gambia +0268: GE GEO Georgia +0276: DE DEU Germany +0288: GH GHA Ghana +0292: GI GIB Gibraltar +0300: GR GRC Greece +0304: GL GRL Greenland +0308: GD GRD Grenada +0312: GP GLP Guadeloupe +0316: GU GUM Guam +0320: GT GTM Guatemala +0831: GG GGY Guernsey +0324: GN GIN Guinea +0624: GW GNB Guinea-Bissau +0328: GY GUY Guyana +0332: HT HTI Haiti +0334: HM HMD Heard Island and McDonald Islands +0336: VA VAT Holy See +0340: HN HND Honduras +0344: HK HKG Hong Kong +0348: HU HUN Hungary +0352: IS ISL Iceland +0356: IN IND India +0360: ID IDN Indonesia +0364: IR IRN Iran, Islamic Republic of +0368: IQ IRQ Iraq +0372: IE IRL Ireland +0833: IM IMN Isle of Man +0376: IL ISR Israel +0380: IT ITA Italy +0388: JM JAM Jamaica +0392: JP JPN Japan +0832: JE JEY Jersey +0400: JO JOR Jordan +0398: KZ KAZ Kazakhstan +0404: KE KEN Kenya +0296: KI KIR Kiribati +0408: KP PRK Korea, Democratic People's Republic of +0410: KR KOR Korea, Republic of +0414: KW KWT Kuwait +0417: KG KGZ Kyrgyzstan +0418: LA LAO Lao People's Democratic Republic +0428: LV LVA Latvia +0422: LB LBN Lebanon +0426: LS LSO Lesotho +0430: LR LBR Liberia +0434: LY LBY Libya +0438: LI LIE Liechtenstein +0440: LT LTU Lithuania +0442: LU LUX Luxembourg +0446: MO MAC Macao +0450: MG MDG Madagascar +0454: MW MWI Malawi +0458: MY MYS Malaysia +0462: MV MDV Maldives +0466: ML MLI Mali +0470: MT MLT Malta +0584: MH MHL Marshall Islands +0474: MQ MTQ Martinique +0478: MR MRT Mauritania +0480: MU MUS Mauritius +0175: YT MYT Mayotte +0484: MX MEX Mexico +0583: FM FSM Micronesia, Federated States of +0498: MD MDA Moldova, Republic of +0492: MC MCO Monaco +0496: MN MNG Mongolia +0499: ME MNE Montenegro +0500: MS MSR Montserrat +0504: MA MAR Morocco +0508: MZ MOZ Mozambique +0104: MM MMR Myanmar +0516: NA NAM Namibia +0520: NR NRU Nauru +0524: NP NPL Nepal +0528: NL NLD Netherlands, Kingdom of the +0540: NC NCL New Caledonia +0554: NZ NZL New Zealand +0558: NI NIC Nicaragua +0562: NE NER Niger +0566: NG NGA Nigeria +0570: NU NIU Niue +0574: NF NFK Norfolk Island +0807: MK MKD North Macedonia +0580: MP MNP Northern Mariana Islands +0578: NO NOR Norway +0512: OM OMN Oman +0586: PK PAK Pakistan +0585: PW PLW Palau +0275: PS PSE Palestine, State of +0591: PA PAN Panama +0598: PG PNG Papua New Guinea +0600: PY PRY Paraguay +0604: PE PER Peru +0608: PH PHL Philippines +0612: PN PCN Pitcairn +0616: PL POL Poland +0620: PT PRT Portugal +0630: PR PRI Puerto Rico +0634: QA QAT Qatar +0638: RE REU Réunion +0642: RO ROU Romania +0643: RU RUS Russian Federation +0646: RW RWA Rwanda +0652: BL BLM Saint Barthélemy +0654: SH SHN Saint Helena, Ascension and Tristan da Cunha +0659: KN KNA Saint Kitts and Nevis +0662: LC LCA Saint Lucia +0663: MF MAF Saint Martin (French part) +0666: PM SPM Saint Pierre and Miquelon +0670: VC VCT Saint Vincent and the Grenadines +0882: WS WSM Samoa +0674: SM SMR San Marino +0678: ST STP Sao Tome and Principe +0682: SA SAU Saudi Arabia +0686: SN SEN Senegal +0688: RS SRB Serbia +0690: SC SYC Seychelles +0694: SL SLE Sierra Leone +0702: SG SGP Singapore +0534: SX SXM Sint Maarten (Dutch part) +0703: SK SVK Slovakia +0705: SI SVN Slovenia +0090: SB SLB Solomon Islands +0706: SO SOM Somalia +0710: ZA ZAF South Africa +0239: GS SGS South Georgia and the South Sandwich Islands +0728: SS SSD South Sudan +0724: ES ESP Spain +0144: LK LKA Sri Lanka +0729: SD SDN Sudan +0740: SR SUR Suriname +0744: SJ SJM Svalbard and Jan Mayen +0752: SE SWE Sweden +0756: CH CHE Switzerland +0760: SY SYR Syrian Arab Republic +0158: TW TWN Taiwan, Province of China +0762: TJ TJK Tajikistan +0834: TZ TZA Tanzania, United Republic of +0764: TH THA Thailand +0626: TL TLS Timor-Leste +0768: TG TGO Togo +0772: TK TKL Tokelau +0776: TO TON Tonga +0780: TT TTO Trinidad and Tobago +0788: TN TUN Tunisia +0792: TR TUR Türkiye +0795: TM TKM Turkmenistan +0796: TC TCA Turks and Caicos Islands +0798: TV TUV Tuvalu +0800: UG UGA Uganda +0804: UA UKR Ukraine +0784: AE ARE United Arab Emirates +0826: GB GBR United Kingdom of Great Britain and Northern Ireland +0840: US USA United States of America +0581: UM UMI United States Minor Outlying Islands +0858: UY URY Uruguay +0860: UZ UZB Uzbekistan +0548: VU VUT Vanuatu +0862: VE VEN Venezuela, Bolivarian Republic of +0704: VN VNM Viet Nam +0092: VG VGB Virgin Islands (British) +0850: VI VIR Virgin Islands (U.S.) +0876: WF WLF Wallis and Futuna +0732: EH ESH Western Sahara +0887: YE YEM Yemen +0894: ZM ZMB Zambia +0716: ZW ZWE Zimbabwe \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_fdx_b.c b/lib/lfrfid/protocols/protocol_fdx_b.c index 3de2b661b..83febb45f 100644 --- a/lib/lfrfid/protocols/protocol_fdx_b.c +++ b/lib/lfrfid/protocols/protocol_fdx_b.c @@ -5,6 +5,7 @@ #include #include "lfrfid_protocols.h" #include +#include #define FDX_B_ENCODED_BIT_SIZE (128) #define FDX_B_ENCODED_BYTE_SIZE (((FDX_B_ENCODED_BIT_SIZE) / 8)) @@ -287,15 +288,21 @@ void protocol_fdx_b_render_data(ProtocolFDXB* protocol, FuriString* result) { uint8_t user_info = bit_lib_get_bits(protocol->data, 55, 5); uint8_t replacement_number = bit_lib_get_bits(protocol->data, 60, 3); bool animal_flag = bit_lib_get_bit(protocol->data, 63); + Storage* storage = furi_record_open(RECORD_STORAGE); + FuriString* country_full_name = furi_string_alloc(); + bool country_found = iso_3166_get_full_name(storage, country_code, country_full_name); + furi_record_close(RECORD_STORAGE); furi_string_printf( result, "ID: %03hu-%012llu\n" "Country Code: %hu\n" + "Country: %s\n" "Temperature: ", country_code, national_code, - country_code); + country_code, + (country_found) ? furi_string_get_cstr(country_full_name) : "Unknown"); float temperature; if(protocol_fdx_b_get_temp(protocol->data, &temperature)) { @@ -320,6 +327,8 @@ void protocol_fdx_b_render_data(ProtocolFDXB* protocol, FuriString* result) { reserved, user_info, replacement_number); + + furi_string_free(country_full_name); } void protocol_fdx_b_render_brief_data(ProtocolFDXB* protocol, FuriString* result) { @@ -328,14 +337,18 @@ void protocol_fdx_b_render_brief_data(ProtocolFDXB* protocol, FuriString* result // 10 bit of country code uint16_t country_code = protocol_fdx_b_get_country_code(protocol->data); - + Storage* storage = furi_record_open(RECORD_STORAGE); + FuriString* country_two_letter = furi_string_alloc(); + bool country_found = iso_3166_get_two_letter(storage, country_code, country_two_letter); + furi_record_close(RECORD_STORAGE); furi_string_printf( result, "ID: %03hu-%012llu\n" - "Country: %hu; Temp.: ", + "Country: %hu %s; Temp.: ", country_code, national_code, - country_code); + country_code, + (country_found) ? furi_string_get_cstr(country_two_letter) : "Unknown"); float temperature; if(protocol_fdx_b_get_temp(protocol->data, &temperature)) { @@ -348,6 +361,8 @@ void protocol_fdx_b_render_brief_data(ProtocolFDXB* protocol, FuriString* result } else { furi_string_cat(result, "---"); } + + furi_string_free(country_two_letter); } bool protocol_fdx_b_write_data(ProtocolFDXB* protocol, void* data) { diff --git a/lib/lfrfid/tools/iso_3166.c b/lib/lfrfid/tools/iso_3166.c new file mode 100644 index 000000000..3986f8567 --- /dev/null +++ b/lib/lfrfid/tools/iso_3166.c @@ -0,0 +1,98 @@ +#include "iso_3166.h" + +#include + +#define RESOURCE_FILE_PATH (EXT_PATH("lfrfid/assets/iso3166.lfrfid")) + +static bool lfrfid_search_data(Storage* storage, uint16_t country_code, FuriString* out_line) { + static const char* lfrfid_resources_header = "Flipper LFRFID resources"; + static const uint32_t lfrfid_resources_file_version = 1; + + FuriString* key = furi_string_alloc_printf("%04d", country_code); + bool found = false; + furi_string_reset(out_line); + FlipperFormat* file = flipper_format_file_alloc(storage); + FuriString* temp_str = furi_string_alloc(); + + do { + // Open file + if(!flipper_format_file_open_existing(file, RESOURCE_FILE_PATH)) break; + // Read file header and version + uint32_t version = 0; + if(!flipper_format_read_header(file, temp_str, &version)) break; + if(furi_string_cmp_str(temp_str, lfrfid_resources_header) || + (version != lfrfid_resources_file_version)) + break; + if(!flipper_format_read_string(file, furi_string_get_cstr(key), out_line)) break; + found = true; + } while(false); + + furi_string_free(key); + furi_string_free(temp_str); + flipper_format_free(file); + + if(found) { + furi_string_replace_all(out_line, " ", ""); + } + + return found; +} + +bool iso_3166_get_two_letter(Storage* storage, uint16_t country_code, FuriString* out_two_letter) { + // We'll fetch the entire line from iso3166.lfrfid + FuriString* line = furi_string_alloc(); + bool found = lfrfid_search_data(storage, country_code, line); + + if(found) { + if(furi_string_size(line) < 2) { + furi_string_free(line); + FURI_LOG_E("Lfrifd:Iso_3166", "Not enough data for two-letter code"); + return false; // Not enough data for a two-letter code + } + // AFAFGAfghanistan + furi_string_left(line, 2); // AF + furi_string_set(out_two_letter, line); + } + furi_string_free(line); + return found; +} + +bool iso_3166_get_three_letter( + Storage* storage, + uint16_t country_code, + FuriString* out_three_letter) { + FuriString* line = furi_string_alloc(); + bool found = lfrfid_search_data(storage, country_code, line); + + if(found) { + if(furi_string_size(line) < 5) { + furi_string_free(line); + FURI_LOG_E("Lfrifd:Iso_3166", "Not enough data for three-letter code"); + return false; // Not enough data for a three-letter code + } + // AFAFGAfghanistan + furi_string_left(line, 5); // AFAFG + furi_string_right(line, 2); // AFG + furi_string_set(out_three_letter, line); + } + furi_string_free(line); + return found; +} + +bool iso_3166_get_full_name(Storage* storage, uint16_t country_code, FuriString* out_full_name) { + FuriString* line = furi_string_alloc(); + bool found = lfrfid_search_data(storage, country_code, line); + + if(found) { + if(furi_string_size(line) < 6) { + furi_string_free(line); + FURI_LOG_E("Lfrifd:Iso_3166", "Not enough data for full name"); + return false; // Not enough data for a full name + } + // AFAFGAfghanistan + furi_string_right(line, 5); // Afghanistan + furi_string_set(out_full_name, line); + } + furi_string_free(line); + return found; +} diff --git a/lib/lfrfid/tools/iso_3166.h b/lib/lfrfid/tools/iso_3166.h new file mode 100644 index 000000000..9f6fd0887 --- /dev/null +++ b/lib/lfrfid/tools/iso_3166.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Look up a country's 2-letter code (ISO 3166-1 Alpha-2). + * + * @param storage Pointer to a valid Storage. + * @param country_code Numeric ISO3166 code. e.g., 4 for Afghanistan. + * @param out_two_letter A caller-allocated FuriString to fill with the 2-letter code, if found. + */ +bool iso_3166_get_two_letter(Storage* storage, uint16_t country_code, FuriString* out_two_letter); + +/** + * @brief Look up a country's 3-letter code (ISO 3166-1 Alpha-3). + * + * @param storage Pointer to a valid Storage. + * @param country_code Numeric ISO3166 code. + * @param out_three_letter A caller-allocated FuriString to fill with the 3-letter code, if found. + */ +bool iso_3166_get_three_letter( + Storage* storage, + uint16_t country_code, + FuriString* out_three_letter); + +/** + * @brief Look up a country's full name. + * + * @param storage Pointer to a valid Storage. + * @param country_code Numeric ISO3166 code. + * @param out_full_name A caller-allocated FuriString to fill with the country's full name. + */ +bool iso_3166_get_full_name(Storage* storage, uint16_t country_code, FuriString* out_full_name); + +#ifdef __cplusplus +} +#endif From 2ef556d45646a406066fdbefe7f1163d03cb1d5b Mon Sep 17 00:00:00 2001 From: Axum Date: Thu, 25 Sep 2025 13:05:29 -0500 Subject: [PATCH 081/192] Update demo_windows.txt (#4211) Fix for newer version of ai enabled Windows Notepad not able to keep up with default fast input text. Co-authored-by: hedger --- applications/main/bad_usb/resources/badusb/demo_windows.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/applications/main/bad_usb/resources/badusb/demo_windows.txt b/applications/main/bad_usb/resources/badusb/demo_windows.txt index 2ed33b3c0..8e68f46c2 100644 --- a/applications/main/bad_usb/resources/badusb/demo_windows.txt +++ b/applications/main/bad_usb/resources/badusb/demo_windows.txt @@ -1,5 +1,7 @@ REM This is BadUSB demo script for windows +REM set slightly slower delay to ensure notepad picks up input without skipping characters +DEFAULT_STRING_DELAY 10 REM Open windows notepad DELAY 1000 GUI r From 46c335ad255354e0f22a0f63a440988843d24879 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 28 Sep 2025 01:53:21 +0300 Subject: [PATCH 082/192] upd nfc OFW PR 4271 [ci skip] --- lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index cf942172b..c3dbe88d6 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -469,9 +469,8 @@ static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPol instance->mfu_event.data->key_request_data.key; } } else { - FURI_LOG_D(TAG, "No key provided, skipping auth"); - instance->state = MfUltralightPollerStateReadPages; - return command; + // Fallback: use key from auth context (for sync poller compatibility) + instance->auth_context.tdes_key = instance->mfu_event.data->auth_context.tdes_key; } instance->auth_context.auth_success = false; // For debugging From 372681553a05f734d9f505365c8f3ec3728a513e Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 28 Sep 2025 01:55:35 +0300 Subject: [PATCH 083/192] remove honeywellsec for now [ci skip] TODO: make proper decoder, current works very unstable, same goes for encoder, current doesnt act as original signal, having no "gap" between packets --- lib/subghz/protocols/honeywell.c | 371 -------------------------- lib/subghz/protocols/honeywell.h | 61 ----- lib/subghz/protocols/protocol_items.c | 1 - lib/subghz/protocols/protocol_items.h | 1 - 4 files changed, 434 deletions(-) delete mode 100644 lib/subghz/protocols/honeywell.c delete mode 100644 lib/subghz/protocols/honeywell.h diff --git a/lib/subghz/protocols/honeywell.c b/lib/subghz/protocols/honeywell.c deleted file mode 100644 index 8d8dc22d4..000000000 --- a/lib/subghz/protocols/honeywell.c +++ /dev/null @@ -1,371 +0,0 @@ -#include "honeywell.h" -#include - -//Created by HTotoo 2023-10-30 -//Got a lot of help from LiQuiDz. -//Protocol decoding help from: https://github.com/merbanan/rtl_433/blob/master/src/devices/honeywell.c - -/* -64 bit packets, repeated multiple times per open/close event. - -Protocol whitepaper: "DEFCON 22: Home Insecurity" by Logan Lamb. - -Data layout: - - PP PP C IIIII EE SS SS - -- P: 16bit Preamble and sync bit (always ff fe) -- C: 4bit Channel -- I: 20bit Device serial number / or counter value -- E: 8bit Event, where 0x80 = Open/Close, 0x04 = Heartbeat / or id -- S: 16bit CRC -*/ - -#define TAG "SubGhzProtocolHoneywell" - -uint16_t subghz_protocol_honeywell_crc16( - uint8_t const message[], - unsigned nBytes, - uint16_t polynomial, - uint16_t init) { - uint16_t remainder = init; - unsigned byte, bit; - - for(byte = 0; byte < nBytes; ++byte) { - remainder ^= message[byte] << 8; - for(bit = 0; bit < 8; ++bit) { - if(remainder & 0x8000) { - remainder = (remainder << 1) ^ polynomial; - } else { - remainder = (remainder << 1); - } - } - } - return remainder; -} - -void subghz_protocol_decoder_honeywell_free(void* context) { - furi_assert(context); - SubGhzProtocolDecoderHoneywell* instance = context; - free(instance); -} - -void subghz_protocol_decoder_honeywell_reset(void* context) { - furi_assert(context); - SubGhzProtocolDecoderHoneywell* instance = context; - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; -} - -void subghz_protocol_decoder_honeywell_addbit(void* context, bool data) { - SubGhzProtocolDecoderHoneywell* instance = context; - instance->decoder.decode_data = (instance->decoder.decode_data << 1) | data; - instance->decoder.decode_count_bit++; - - if(instance->decoder.decode_count_bit < 62) return; - - uint16_t preamble = (instance->decoder.decode_data >> 48) & 0xFFFF; - //can be multiple, since flipper can't read it well.. - if(preamble == 0b0011111111111110 || preamble == 0b0111111111111110 || - preamble == 0b1111111111111110) { - uint8_t datatocrc[4]; - datatocrc[0] = (instance->decoder.decode_data >> 40) & 0xFFFF; - datatocrc[1] = (instance->decoder.decode_data >> 32) & 0xFFFF; - datatocrc[2] = (instance->decoder.decode_data >> 24) & 0xFFFF; - datatocrc[3] = (instance->decoder.decode_data >> 16) & 0xFFFF; - uint8_t channel = (instance->decoder.decode_data >> 44) & 0xF; - uint16_t crc_calc = 0; - if(channel == 0x2 || channel == 0x4 || channel == 0xA) { - // 2GIG brand - crc_calc = subghz_protocol_honeywell_crc16(datatocrc, 4, 0x8050, 0); - } else if(channel == 0x8) { - crc_calc = subghz_protocol_honeywell_crc16(datatocrc, 4, 0x8005, 0); - } else { - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - return; - } - uint16_t crc = instance->decoder.decode_data & 0xFFFF; - if(crc == crc_calc) { - //the data is good. process it. - instance->generic.data = instance->decoder.decode_data; - instance->generic.data_count_bit = - instance->decoder - .decode_count_bit; //maybe set it to 64, and hack the first 2 bits to 1! will see if replay needs it - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - } else { - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - return; - } - } else if(instance->decoder.decode_count_bit >= 64) { - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - return; - } -} - -void subghz_protocol_decoder_honeywell_feed(void* context, bool level, uint32_t duration) { - furi_assert(context); - SubGhzProtocolDecoderHoneywell* instance = context; - - ManchesterEvent event = ManchesterEventReset; - if(!level) { - if(DURATION_DIFF(duration, subghz_protocol_honeywell_const.te_short) < - subghz_protocol_honeywell_const.te_delta) { - event = ManchesterEventShortLow; - } else if( - DURATION_DIFF(duration, subghz_protocol_honeywell_const.te_long) < - subghz_protocol_honeywell_const.te_delta * 2) { - event = ManchesterEventLongLow; - } - } else { - if(DURATION_DIFF(duration, subghz_protocol_honeywell_const.te_short) < - subghz_protocol_honeywell_const.te_delta) { - event = ManchesterEventShortHigh; - } else if( - DURATION_DIFF(duration, subghz_protocol_honeywell_const.te_long) < - subghz_protocol_honeywell_const.te_delta * 2) { - event = ManchesterEventLongHigh; - } - } - if(event != ManchesterEventReset) { - bool data; - bool data_ok = manchester_advance( - instance->manchester_saved_state, event, &instance->manchester_saved_state, &data); - if(data_ok) { - subghz_protocol_decoder_honeywell_addbit(instance, data); - } - } else { - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - } -} - -uint8_t subghz_protocol_decoder_honeywell_get_hash_data(void* context) { - furi_assert(context); - SubGhzProtocolDecoderHoneywell* instance = context; - return subghz_protocol_blocks_get_hash_data( - &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); -} - -SubGhzProtocolStatus subghz_protocol_decoder_honeywell_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset) { - furi_assert(context); - SubGhzProtocolDecoderHoneywell* instance = context; - return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); -} - -SubGhzProtocolStatus - subghz_protocol_decoder_honeywell_deserialize(void* context, FlipperFormat* flipper_format) { - furi_assert(context); - SubGhzProtocolDecoderHoneywell* instance = context; - return subghz_block_generic_deserialize_check_count_bit( - &instance->generic, - flipper_format, - subghz_protocol_honeywell_const.min_count_bit_for_found); -} - -void subghz_protocol_decoder_honeywell_get_string(void* context, FuriString* output) { - furi_assert(context); - SubGhzProtocolDecoderHoneywell* instance = context; - - // Parse here and not in decode to avoid visual glitches when loading from file - instance->generic.serial = (instance->generic.data >> 24) & 0xFFFFF; - instance->generic.btn = (instance->generic.data >> 16) & - 0xFF; //not exactly button, but can contain btn data too. - - uint8_t channel = (instance->generic.data >> 44) & 0xF; - uint8_t contact = (instance->generic.btn & 0x80) >> 7; - uint8_t tamper = (instance->generic.btn & 0x40) >> 6; - uint8_t reed = (instance->generic.btn & 0x20) >> 5; - uint8_t alarm = (instance->generic.btn & 0x10) >> 4; - uint8_t battery_low = (instance->generic.btn & 0x08) >> 3; - uint8_t heartbeat = (instance->generic.btn & 0x04) >> 2; - - furi_string_cat_printf( - output, - "%s\r\n%dbit " - "Sn:%07lu\r\nCh:%u Bat:%d Hb: %d\r\n" - "L1: %u, L2: %u, L3: %u, L4: %u\r\n", - instance->generic.protocol_name, - instance->generic.data_count_bit, - instance->generic.serial, - channel, - battery_low, - heartbeat, - contact, - reed, - alarm, - tamper); -} - -void* subghz_protocol_decoder_honeywell_alloc(SubGhzEnvironment* environment) { - UNUSED(environment); - SubGhzProtocolDecoderHoneywell* instance = malloc(sizeof(SubGhzProtocolDecoderHoneywell)); - instance->base.protocol = &subghz_protocol_honeywell; - instance->generic.protocol_name = instance->base.protocol->name; - return instance; -} - -void* subghz_protocol_encoder_honeywell_alloc(SubGhzEnvironment* environment) { - UNUSED(environment); - SubGhzProtocolEncoderHoneywell* instance = malloc(sizeof(SubGhzProtocolEncoderHoneywell)); - - instance->base.protocol = &subghz_protocol_honeywell; - instance->generic.protocol_name = instance->base.protocol->name; - - instance->encoder.repeat = 3; - instance->encoder.size_upload = 64 * 2 + 10; - instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); - instance->encoder.is_running = false; - return instance; -} - -void subghz_protocol_encoder_honeywell_free(void* context) { - furi_assert(context); - SubGhzProtocolEncoderHoneywell* instance = context; - free(instance->encoder.upload); - free(instance); -} -static LevelDuration - subghz_protocol_encoder_honeywell_add_duration_to_upload(ManchesterEncoderResult result) { - LevelDuration data = {.duration = 0, .level = 0}; - switch(result) { - case ManchesterEncoderResultShortLow: - data.duration = subghz_protocol_honeywell_const.te_short; - data.level = false; - break; - case ManchesterEncoderResultLongLow: - data.duration = subghz_protocol_honeywell_const.te_long; - data.level = false; - break; - case ManchesterEncoderResultLongHigh: - data.duration = subghz_protocol_honeywell_const.te_long; - data.level = true; - break; - case ManchesterEncoderResultShortHigh: - data.duration = subghz_protocol_honeywell_const.te_short; - data.level = true; - break; - - default: - furi_crash("SubGhz: ManchesterEncoderResult is incorrect."); - break; - } - return level_duration_make(data.level, data.duration); -} - -static void - subghz_protocol_encoder_honeywell_get_upload(SubGhzProtocolEncoderHoneywell* instance) { - furi_assert(instance); - size_t index = 0; - - ManchesterEncoderState enc_state; - manchester_encoder_reset(&enc_state); - ManchesterEncoderResult result; - - for(uint8_t i = 63; i > 0; i--) { - if(!manchester_encoder_advance( - &enc_state, bit_read(instance->generic.data, i - 1), &result)) { - instance->encoder.upload[index++] = - subghz_protocol_encoder_honeywell_add_duration_to_upload(result); - manchester_encoder_advance( - &enc_state, bit_read(instance->generic.data, i - 1), &result); - } - instance->encoder.upload[index++] = - subghz_protocol_encoder_honeywell_add_duration_to_upload(result); - } - instance->encoder.upload[index] = subghz_protocol_encoder_honeywell_add_duration_to_upload( - manchester_encoder_finish(&enc_state)); - if(level_duration_get_level(instance->encoder.upload[index])) { - index++; - } - instance->encoder.size_upload = index; -} - -SubGhzProtocolStatus - subghz_protocol_encoder_honeywell_deserialize(void* context, FlipperFormat* flipper_format) { - furi_assert(context); - SubGhzProtocolEncoderHoneywell* instance = context; - SubGhzProtocolStatus res = SubGhzProtocolStatusError; - do { - if(SubGhzProtocolStatusOk != - subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); - break; - } - - //optional parameter parameter - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - - subghz_protocol_encoder_honeywell_get_upload(instance); - - if(!flipper_format_rewind(flipper_format)) { - FURI_LOG_E(TAG, "Rewind error"); - break; - } - - instance->encoder.is_running = true; - - res = SubGhzProtocolStatusOk; - } while(false); - - return res; -} - -void subghz_protocol_encoder_honeywell_stop(void* context) { - SubGhzProtocolEncoderHoneywell* instance = context; - instance->encoder.is_running = false; -} - -LevelDuration subghz_protocol_encoder_honeywell_yield(void* context) { - SubGhzProtocolEncoderHoneywell* instance = context; - - if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { - instance->encoder.is_running = false; - return level_duration_reset(); - } - LevelDuration ret = instance->encoder.upload[instance->encoder.front]; - if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; - instance->encoder.front = 0; - } - return ret; -} - -const SubGhzProtocolDecoder subghz_protocol_honeywell_decoder = { - .alloc = subghz_protocol_decoder_honeywell_alloc, - .free = subghz_protocol_decoder_honeywell_free, - .feed = subghz_protocol_decoder_honeywell_feed, - .reset = subghz_protocol_decoder_honeywell_reset, - .get_hash_data = subghz_protocol_decoder_honeywell_get_hash_data, - .serialize = subghz_protocol_decoder_honeywell_serialize, - .deserialize = subghz_protocol_decoder_honeywell_deserialize, - .get_string = subghz_protocol_decoder_honeywell_get_string, -}; - -const SubGhzProtocolEncoder subghz_protocol_honeywell_encoder = { - .alloc = subghz_protocol_encoder_honeywell_alloc, - .free = subghz_protocol_encoder_honeywell_free, - .deserialize = subghz_protocol_encoder_honeywell_deserialize, - .stop = subghz_protocol_encoder_honeywell_stop, - .yield = subghz_protocol_encoder_honeywell_yield, -}; - -const SubGhzProtocol subghz_protocol_honeywell = { - .name = SUBGHZ_PROTOCOL_HONEYWELL_NAME, - .type = SubGhzProtocolTypeStatic, - .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | - SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | - SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send | SubGhzProtocolFlag_Sensors, - .encoder = &subghz_protocol_honeywell_encoder, - .decoder = &subghz_protocol_honeywell_decoder, - -}; diff --git a/lib/subghz/protocols/honeywell.h b/lib/subghz/protocols/honeywell.h deleted file mode 100644 index 8aa35ce6c..000000000 --- a/lib/subghz/protocols/honeywell.h +++ /dev/null @@ -1,61 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include "base.h" -#include "../blocks/generic.h" -#include -#include -#include - -#define SUBGHZ_PROTOCOL_HONEYWELL_NAME "Honeywell Sec" - -typedef struct SubGhzProtocolDecoderHoneywell SubGhzProtocolDecoderHoneywell; -typedef struct SubGhzProtocolEncoderHoneywell SubGhzProtocolEncoderHoneywell; - -extern const SubGhzProtocolDecoder subghz_protocol_honeywell_decoder; -extern const SubGhzProtocolEncoder subghz_protocol_honeywell_encoder; -extern const SubGhzProtocol subghz_protocol_honeywell; - -void* subghz_protocol_decoder_honeywell_alloc(SubGhzEnvironment* environment); - -void subghz_protocol_decoder_honeywell_free(void* context); - -void subghz_protocol_decoder_honeywell_reset(void* context); - -void subghz_protocol_decoder_honeywell_feed(void* context, bool level, uint32_t duration); - -uint8_t subghz_protocol_decoder_honeywell_get_hash_data(void* context); - -SubGhzProtocolStatus subghz_protocol_decoder_honeywell_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset); - -SubGhzProtocolStatus - subghz_protocol_decoder_honeywell_deserialize(void* context, FlipperFormat* flipper_format); - -void subghz_protocol_decoder_honeywell_get_string(void* context, FuriString* output); - -static const SubGhzBlockConst subghz_protocol_honeywell_const = { - .te_long = 280, - .te_short = 143, - .te_delta = 51, - .min_count_bit_for_found = 62, -}; - -struct SubGhzProtocolDecoderHoneywell { - SubGhzProtocolDecoderBase base; - SubGhzBlockGeneric generic; - SubGhzBlockDecoder decoder; - ManchesterState manchester_saved_state; -}; - -struct SubGhzProtocolEncoderHoneywell { - SubGhzProtocolEncoderBase base; - SubGhzBlockGeneric generic; - SubGhzProtocolBlockEncoder encoder; -}; diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index 465585d77..91ceaec32 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -44,7 +44,6 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = { &subghz_protocol_kinggates_stylo_4k, &subghz_protocol_bin_raw, &subghz_protocol_mastercode, - &subghz_protocol_honeywell, &subghz_protocol_legrand, &subghz_protocol_dickert_mahs, &subghz_protocol_gangqi, diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index 4f63b030e..1cde46ef5 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -45,7 +45,6 @@ #include "kinggates_stylo_4k.h" #include "bin_raw.h" #include "mastercode.h" -#include "honeywell.h" #include "legrand.h" #include "dickert_mahs.h" #include "gangqi.h" From 2dd6b5cdce81d5cc37c7457f7acfb96a6904489e Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 28 Sep 2025 01:57:04 +0300 Subject: [PATCH 084/192] format [ci skip] --- applications/system/js_app/js_modules.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/system/js_app/js_modules.h b/applications/system/js_app/js_modules.h index c19d4805c..1a4cd8f20 100644 --- a/applications/system/js_app/js_modules.h +++ b/applications/system/js_app/js_modules.h @@ -16,8 +16,8 @@ extern "C" { #define JS_SDK_VENDOR_FIRMWARE "unleashed" #define JS_SDK_VENDOR "flipperdevices" -#define JS_SDK_MAJOR 1 -#define JS_SDK_MINOR 0 +#define JS_SDK_MAJOR 1 +#define JS_SDK_MINOR 0 /** * @brief Returns the foreign pointer in `obj["_"]` From 1e431422e7d3baed4a2dd0dbd8733e6978024d23 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 28 Sep 2025 02:11:06 +0300 Subject: [PATCH 085/192] smol fixes [ci skip] --- applications/main/subghz/helpers/subghz_gen_info.c | 1 + lib/subghz/protocols/gangqi.c | 8 ++------ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_gen_info.c b/applications/main/subghz/helpers/subghz_gen_info.c index 69fa37187..459f600ca 100644 --- a/applications/main/subghz/helpers/subghz_gen_info.c +++ b/applications/main/subghz/helpers/subghz_gen_info.c @@ -1,6 +1,7 @@ #include "subghz_gen_info.h" #include "../helpers/subghz_txrx_create_protocol_key.h" #include +#include void subghz_gen_info_reset(GenInfo* gen_info) { furi_assert(gen_info); diff --git a/lib/subghz/protocols/gangqi.c b/lib/subghz/protocols/gangqi.c index 905c22982..a079a41bd 100644 --- a/lib/subghz/protocols/gangqi.c +++ b/lib/subghz/protocols/gangqi.c @@ -363,19 +363,15 @@ void subghz_protocol_decoder_gangqi_feed(void* context, bool level, volatile uin instance->decoder.parser_step = GangQiDecoderStepSaveDuration; } else if( // End of the key - DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_short * 4) < - subghz_protocol_gangqi_const.te_delta) { + (DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_long * 2) < + subghz_protocol_gangqi_const.te_delta * 3)) { //Found next GAP and add bit 0 or 1 (only bit 0 was found on the remotes) if((DURATION_DIFF( instance->decoder.te_last, subghz_protocol_gangqi_const.te_short) < - subghz_protocol_gangqi_const.te_delta) && - (DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_short * 4) < subghz_protocol_gangqi_const.te_delta)) { subghz_protocol_blocks_add_bit(&instance->decoder, 0); } if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_gangqi_const.te_long) < - subghz_protocol_gangqi_const.te_delta) && - (DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_short * 4) < subghz_protocol_gangqi_const.te_delta)) { subghz_protocol_blocks_add_bit(&instance->decoder, 1); } From a357dfd1adf9e4a5f49ce39a55d10d169f472c85 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 28 Sep 2025 04:50:50 +0300 Subject: [PATCH 086/192] upd changelog --- CHANGELOG.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ac4e782c..7f3e3e1ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,14 @@ ## Main changes -- Current API: 86.0 -* SubGHz: Tune Linear (edited by @WillyJL in PR #919 #920) (add better EZCode support), Dickert MAHS decoders +- Current API: 87.0 +* SubGHz: Tune Linear (edited by @WillyJL in PR #919 #920) (add better EZCode support) and Dickert MAHS protocol decoders * SubGHz: RAW protocol fixes (by @WillyJL) * SubGHz: Add ZKTeco 430.5 MHz add manually support * SubGHz: Add variant of 'Add Manually' menu with manual editing for each value (PR #909 #911 #914 | by @MrLego8-9) +* SubGHz: Temporarily remove HoneywellSec protocol due to unstable decoding and incorrect encoding +* OFW: LFRFID: Show ISO-3166 Country Names For Pet Chips +* OFW: JS views finished +* OFW: BLE: improved pairing security +* OFW: FeliCa Emulation: Handle certain Polling commands in firmware * OFW PR 4271: NFC: Ultralight C NFC App Key Management, Dictionary Attack (by @noproto) * OFW PR 4265: NFC: Fix read crash with unexpectedly large MFC AUTH(0) response (by @WillyJL) * OFW PR 4251: CLI: Fix long delay with quick connect/disconnect (by @WillyJL) @@ -11,6 +16,11 @@ * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * SubGHz: Fix crash in add manually menu +* OFW: Update demo_windows.txt +* OFW: Fix PVS warnings +* OFW: NFC: Amusement IC Card Parser (FeliCa Lite & Lite-S) +* OFW: hid_app mouse clicker: make mouse button selectable +* OFW: JS: Expose button event type in gui/widget button callback * OFW: NFC: MFC 1k Banapass Parser * OFW: GUI Bug Fix: Number Input Save Icon * Add possibility to use custom buttons when using the SubGHz remote app (by @MrLego8-9) From eea53491de6180710438ab7c19e84c4f98564329 Mon Sep 17 00:00:00 2001 From: RebornedBrain Date: Mon, 29 Sep 2025 13:34:49 +0300 Subject: [PATCH 087/192] [FL-3569] NFC CLI commands (#4158) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: FuriThread stdin * ci: fix f18 * feat: stdio callback context * feat: FuriPipe * POTENTIALLY EXPLOSIVE pipe welding * fix: non-explosive welding * Revert welding * docs: furi_pipe * feat: pipe event loop integration * update f18 sdk * f18 * docs: make doxygen happy * fix: event loop not triggering when pipe attached to stdio * fix: partial stdout in pipe * allow simultaneous in and out subscription in event loop * feat: vcp i/o * feat: cli ansi stuffs and history * feat: more line editing * working but slow cli rewrite * restore previous speed after 4 days of debugging 🥲 * fix: cli_app_should_stop * fix: cli and event_loop memory leaks * style: remove commented out code * ci: fix pvs warnings * fix: unit tests, event_loop crash * ci: fix build * ci: silence pvs warning * feat: cli gpio * ci: fix formatting * Fix memory leak during event loop unsubscription * Event better memory leak fix * feat: cli completions * Merge remote-tracking branch 'origin/dev' into portasynthinca3/3928-cli-threads * merge fixups * temporarily exclude speaker_debug app * pvs and unit tests fixups * feat: commands in fals * move commands out of flash, code cleanup * ci: fix errors * fix: run commands in buffer when stopping session * speedup cli file transfer * fix f18 * separate cli_shell into modules * fix pvs warning * fix qflipper refusing to connect * remove temp debug logs * remove erroneous conclusion * Fix memory leak during event loop unsubscription * Event better memory leak fix * unit test for the fix * improve thread stdio callback signatures * pipe stdout timeout * update api symbols * fix f18, formatting * fix pvs warnings * increase stack size, hope to fix unit tests * cli completions * more key combos * commands in fals * move commands out of flash * ci: fix errors * speedup cli file transfer * merge fixups * fix f18 * cli: revert flag changes * cli: fix formatting * cli, fbt: loopback perf benchmark * thread, event_loop: subscribing to thread flags * cli: signal internal events using thread flags, improve performance * fix f18, formatting * event_loop: fix crash * storage_cli: increase write_chunk buffer size again * cli: explanation for order=0 * thread, event_loop: thread flags callback refactor * cli: increase stack size * cli: rename cli_app_should_stop -> cli_is_pipe_broken_or_is_etx_next_char * cli: use plain array instead of mlib for history * cli: prepend file name to static fns * cli: fix formatting * cli_shell: increase stack size * Now cli_shell can be customized with another motd and another command set * Added custom motd callback definition * Now user can alloc and free his own cli command set * cli_vcp can now restart shell with another command set * Help command modified to show available commands from different command sets * Api adjustement * Reworked nfc_cli to start new shell with another command set * Revert custom shell changes from vcp * Custom motd callback moved to cli_shell * Cli Shell now can be started from ongoing cli command * Help command moved to a separate function so it can be used for custom shell * Now nfc command spawns separate shell for further nfc commands * cli_shell: give up pipe to command thread * fix formatting * cli_shell: separate into toolbox * speaker_debug: fix * fix: format * Merge branch 'portasynthinca3/3928-3929-cli-fals-threads' into portasynthinca3/3965-cli_shell-toolbox * fix merge * fix. merge. * fix formatting * fix: cmd flags * fix: formatting * Added basic command descriptor structs and macros * Basic nfc commands definitions added * Nfc cli commands collection and functions added * Raw skeleton of nfc cli processor added * cli: increase default stack depth * New callbacks for ctx alloc / free added * nfc_cli moved to cli folder * Some more logic for command processor * Scanner command no works via command_processor * plugin manifest adj * Argument descriptors were removed, now only keys left * Some helper command function implemented * Command processor logic now mostly works * Added all parsers and dummy implementation of raw cmd * Now processor checks duplicated keys and treat them as errors * Some renamings * Arguments processing moved to separate function * Now command processor can reuse context of previuos command for the next one if it's allowed * can_reuse callback added for checking if context can be reused * command processor is now freed on nfc cli exit * Some cleanups * First working version of raw command * Now input data are placed directly to bit buffer * Added tag * Introduced request/response structs * Moved raw command to a separate folder * Moved some common types to header * Added protocol specific handlers for iso14a and felica * Opened felica crc header for referencing * Added handler for iso14443_3b * Opened iso15693_3_poller for referencing * Added iso15693_3 handler for raw command * NfcCliRawError enum introduced for response result * Refactored handlers implementation * Formatting functions now added as helpers * New printing result logic * Not present error value added to enum * Timeout added to raw command * Command processor now supports multivalue keys * Apdu command implementation added * NfcScanner moved to helpers and command now uses it * Helper now can format protocol names * Dump command added * Added some more functions to scanner helper * Dump main logic simplified * Dump handlers moved to protocols folder * Protocol parser added to simplify searching protocol by name * Protocol and key arguments added to dump command * Cleanups * Apdu now parses protocol using helper parser * Raw now parses protocol using helper parser * Wrong naming fix * Emulate command added to cli * Description added to action descriptor and command macros * Description field added to all commands * Removed unnecessary enum for commands * Added functions for formatting command and action info * Proper error messages and help added * Fix for unsupported single action command * Function renamed to more appropriate * Field command moved to all other commands * Cleanups * Nfc commands modified with new cli shell * Removed previous nfc_cli.c after merge * Removed nfc_cli.h header * Some renamings and cleanups * Some comments and instructions added * Some comments and instructions added * TODOs removed * Fix for missing parse callback * Added not implemented dummy for mfu actions, for now * Fix name mismatch * Remove unneeded header * Mfu command moved to separate folder, also raw info action logic added * Dictionary with id/vendors added to assets. It is used by nfc_cli_mfu_info_get_vendor function * One more unneeded header removed * Moved mfu info action to a separate file * Info action now uses sync mfu poller * mfu rdbl action added * wrbl action added for mfu command * Some formatting for rdbl command * Function for formatting mfu errors added * All mfu actions now show errors in the same way * Fix error with sync poller. Previously when read failed function returned ErrorNone, now it processes iso14a error to get proper value * Make PVS happy * Nfc cli now doesn't start if desktop app is running * Make action description look more common * Scanner now has -t key and can show detected protocol hierarchies * Apdu now checks max input payload data * Proper format * Proper error handling added to dump command * Timeout key added dump command * Fix merge issue * formatting * Pragma pack replaced with FURI_PACKED * Fix felica memory leak --------- Co-authored-by: Anna Antonenko Co-authored-by: Georgii Surkov Co-authored-by: あく Co-authored-by: hedger Co-authored-by: hedger --- applications/main/nfc/application.fam | 47 ++- .../cli/commands/apdu/nfc_cli_command_apdu.c | 311 ++++++++++++++ .../cli/commands/apdu/nfc_cli_command_apdu.h | 5 + .../iso14443_4a/nfc_cli_apdu_iso14443_4a.c | 37 ++ .../iso14443_4a/nfc_cli_apdu_iso14443_4a.h | 6 + .../iso14443_4b/nfc_cli_apdu_iso14443_4b.c | 37 ++ .../iso14443_4b/nfc_cli_apdu_iso14443_4b.h | 6 + .../iso15693_3/nfc_cli_apdu_iso15693_3.c | 37 ++ .../iso15693_3/nfc_cli_apdu_iso15693_3.h | 6 + .../nfc_cli_apdu_common_types.h | 20 + .../cli/commands/dump/nfc_cli_command_dump.c | 319 ++++++++++++++ .../cli/commands/dump/nfc_cli_command_dump.h | 5 + .../protocols/felica/nfc_cli_dump_felica.c | 32 ++ .../protocols/felica/nfc_cli_dump_felica.h | 5 + .../iso14443_3a/nfc_cli_dump_iso14443_3a.c | 25 ++ .../iso14443_3a/nfc_cli_dump_iso14443_3a.h | 5 + .../iso14443_3b/nfc_cli_dump_iso14443_3b.c | 27 ++ .../iso14443_3b/nfc_cli_dump_iso14443_3b.h | 5 + .../iso14443_4a/nfc_cli_dump_iso14443_4a.c | 26 ++ .../iso14443_4a/nfc_cli_dump_iso14443_4a.h | 5 + .../iso14443_4b/nfc_cli_dump_iso14443_4b.c | 25 ++ .../iso14443_4b/nfc_cli_dump_iso14443_4b.h | 5 + .../iso15693_3/nfc_cli_dump_iso15693_3.c | 26 ++ .../iso15693_3/nfc_cli_dump_iso15693_3.h | 5 + .../mf_classic/nfc_cli_dump_mf_classic.c | 54 +++ .../mf_classic/nfc_cli_dump_mf_classic.h | 5 + .../mf_desfire/nfc_cli_dump_mf_desfire.c | 27 ++ .../mf_desfire/nfc_cli_dump_mf_desfire.h | 5 + .../protocols/mf_plus/nfc_cli_dump_mf_plus.c | 31 ++ .../protocols/mf_plus/nfc_cli_dump_mf_plus.h | 5 + .../nfc_cli_dump_mf_ultralight.c | 39 ++ .../nfc_cli_dump_mf_ultralight.h | 5 + .../protocols/nfc_cli_dump_common_types.h | 59 +++ .../dump/protocols/slix/nfc_cli_dump_slix.c | 26 ++ .../dump/protocols/slix/nfc_cli_dump_slix.h | 5 + .../protocols/st25tb/nfc_cli_dump_st25tb.c | 29 ++ .../protocols/st25tb/nfc_cli_dump_st25tb.h | 5 + .../nfc/cli/commands/helpers/nfc_cli_format.c | 61 +++ .../nfc/cli/commands/helpers/nfc_cli_format.h | 16 + .../helpers/nfc_cli_protocol_parser.c | 58 +++ .../helpers/nfc_cli_protocol_parser.h | 21 + .../cli/commands/helpers/nfc_cli_scanner.c | 96 +++++ .../cli/commands/helpers/nfc_cli_scanner.h | 21 + .../cli/commands/mfu/nfc_cli_action_info.c | 227 ++++++++++ .../cli/commands/mfu/nfc_cli_action_info.h | 7 + .../cli/commands/mfu/nfc_cli_action_rdbl.c | 52 +++ .../cli/commands/mfu/nfc_cli_action_rdbl.h | 8 + .../cli/commands/mfu/nfc_cli_action_wrbl.c | 71 ++++ .../cli/commands/mfu/nfc_cli_action_wrbl.h | 9 + .../cli/commands/mfu/nfc_cli_command_mfu.c | 77 ++++ .../cli/commands/mfu/nfc_cli_command_mfu.h | 5 + .../cli/commands/nfc_cli_command_emulate.c | 126 ++++++ .../cli/commands/nfc_cli_command_emulate.h | 5 + .../nfc/cli/commands/nfc_cli_command_field.c | 28 ++ .../nfc/cli/commands/nfc_cli_command_field.h | 5 + .../cli/commands/nfc_cli_command_scanner.c | 97 +++++ .../cli/commands/nfc_cli_command_scanner.h | 5 + .../cli/commands/raw/nfc_cli_command_raw.c | 352 ++++++++++++++++ .../cli/commands/raw/nfc_cli_command_raw.h | 5 + .../felica/nfc_cli_raw_felica.c | 101 +++++ .../felica/nfc_cli_raw_felica.h | 8 + .../iso14443_3a/nfc_cli_raw_iso14443_3a.c | 81 ++++ .../iso14443_3a/nfc_cli_raw_iso14443_3a.h | 8 + .../iso14443_3b/nfc_cli_raw_iso14443_3b.c | 121 ++++++ .../iso14443_3b/nfc_cli_raw_iso14443_3b.h | 8 + .../iso15693_3/nfc_cli_raw_iso15693_3.c | 102 +++++ .../iso15693_3/nfc_cli_raw_iso15693_3.h | 8 + .../nfc_cli_raw_common_types.h | 28 ++ applications/main/nfc/cli/nfc_cli.c | 124 ++++++ .../main/nfc/cli/nfc_cli_command_base.h | 69 ++++ .../main/nfc/cli/nfc_cli_command_base_i.h | 130 ++++++ .../main/nfc/cli/nfc_cli_command_processor.c | 388 ++++++++++++++++++ .../main/nfc/cli/nfc_cli_command_processor.h | 15 + applications/main/nfc/cli/nfc_cli_commands.c | 161 ++++++++ applications/main/nfc/cli/nfc_cli_commands.h | 27 ++ applications/main/nfc/nfc_cli.c | 67 --- .../main/nfc/resources/nfc/assets/vendors.nfc | 115 ++++++ applications/services/cli/cli_vcp.h | 1 - lib/nfc/SConscript | 2 + lib/nfc/protocols/felica/felica.c | 1 + lib/nfc/protocols/felica/felica.h | 4 +- .../mf_ultralight/mf_ultralight_poller_sync.c | 3 +- lib/toolbox/cli/shell/cli_shell.c | 1 - targets/f7/api_symbols.csv | 12 + 84 files changed, 4180 insertions(+), 79 deletions(-) create mode 100644 applications/main/nfc/cli/commands/apdu/nfc_cli_command_apdu.c create mode 100644 applications/main/nfc/cli/commands/apdu/nfc_cli_command_apdu.h create mode 100644 applications/main/nfc/cli/commands/apdu/protocol_handlers/iso14443_4a/nfc_cli_apdu_iso14443_4a.c create mode 100644 applications/main/nfc/cli/commands/apdu/protocol_handlers/iso14443_4a/nfc_cli_apdu_iso14443_4a.h create mode 100644 applications/main/nfc/cli/commands/apdu/protocol_handlers/iso14443_4b/nfc_cli_apdu_iso14443_4b.c create mode 100644 applications/main/nfc/cli/commands/apdu/protocol_handlers/iso14443_4b/nfc_cli_apdu_iso14443_4b.h create mode 100644 applications/main/nfc/cli/commands/apdu/protocol_handlers/iso15693_3/nfc_cli_apdu_iso15693_3.c create mode 100644 applications/main/nfc/cli/commands/apdu/protocol_handlers/iso15693_3/nfc_cli_apdu_iso15693_3.h create mode 100644 applications/main/nfc/cli/commands/apdu/protocol_handlers/nfc_cli_apdu_common_types.h create mode 100644 applications/main/nfc/cli/commands/dump/nfc_cli_command_dump.c create mode 100644 applications/main/nfc/cli/commands/dump/nfc_cli_command_dump.h create mode 100644 applications/main/nfc/cli/commands/dump/protocols/felica/nfc_cli_dump_felica.c create mode 100644 applications/main/nfc/cli/commands/dump/protocols/felica/nfc_cli_dump_felica.h create mode 100644 applications/main/nfc/cli/commands/dump/protocols/iso14443_3a/nfc_cli_dump_iso14443_3a.c create mode 100644 applications/main/nfc/cli/commands/dump/protocols/iso14443_3a/nfc_cli_dump_iso14443_3a.h create mode 100644 applications/main/nfc/cli/commands/dump/protocols/iso14443_3b/nfc_cli_dump_iso14443_3b.c create mode 100644 applications/main/nfc/cli/commands/dump/protocols/iso14443_3b/nfc_cli_dump_iso14443_3b.h create mode 100644 applications/main/nfc/cli/commands/dump/protocols/iso14443_4a/nfc_cli_dump_iso14443_4a.c create mode 100644 applications/main/nfc/cli/commands/dump/protocols/iso14443_4a/nfc_cli_dump_iso14443_4a.h create mode 100644 applications/main/nfc/cli/commands/dump/protocols/iso14443_4b/nfc_cli_dump_iso14443_4b.c create mode 100644 applications/main/nfc/cli/commands/dump/protocols/iso14443_4b/nfc_cli_dump_iso14443_4b.h create mode 100644 applications/main/nfc/cli/commands/dump/protocols/iso15693_3/nfc_cli_dump_iso15693_3.c create mode 100644 applications/main/nfc/cli/commands/dump/protocols/iso15693_3/nfc_cli_dump_iso15693_3.h create mode 100644 applications/main/nfc/cli/commands/dump/protocols/mf_classic/nfc_cli_dump_mf_classic.c create mode 100644 applications/main/nfc/cli/commands/dump/protocols/mf_classic/nfc_cli_dump_mf_classic.h create mode 100644 applications/main/nfc/cli/commands/dump/protocols/mf_desfire/nfc_cli_dump_mf_desfire.c create mode 100644 applications/main/nfc/cli/commands/dump/protocols/mf_desfire/nfc_cli_dump_mf_desfire.h create mode 100644 applications/main/nfc/cli/commands/dump/protocols/mf_plus/nfc_cli_dump_mf_plus.c create mode 100644 applications/main/nfc/cli/commands/dump/protocols/mf_plus/nfc_cli_dump_mf_plus.h create mode 100644 applications/main/nfc/cli/commands/dump/protocols/mf_ultralight/nfc_cli_dump_mf_ultralight.c create mode 100644 applications/main/nfc/cli/commands/dump/protocols/mf_ultralight/nfc_cli_dump_mf_ultralight.h create mode 100644 applications/main/nfc/cli/commands/dump/protocols/nfc_cli_dump_common_types.h create mode 100644 applications/main/nfc/cli/commands/dump/protocols/slix/nfc_cli_dump_slix.c create mode 100644 applications/main/nfc/cli/commands/dump/protocols/slix/nfc_cli_dump_slix.h create mode 100644 applications/main/nfc/cli/commands/dump/protocols/st25tb/nfc_cli_dump_st25tb.c create mode 100644 applications/main/nfc/cli/commands/dump/protocols/st25tb/nfc_cli_dump_st25tb.h create mode 100644 applications/main/nfc/cli/commands/helpers/nfc_cli_format.c create mode 100644 applications/main/nfc/cli/commands/helpers/nfc_cli_format.h create mode 100644 applications/main/nfc/cli/commands/helpers/nfc_cli_protocol_parser.c create mode 100644 applications/main/nfc/cli/commands/helpers/nfc_cli_protocol_parser.h create mode 100644 applications/main/nfc/cli/commands/helpers/nfc_cli_scanner.c create mode 100644 applications/main/nfc/cli/commands/helpers/nfc_cli_scanner.h create mode 100644 applications/main/nfc/cli/commands/mfu/nfc_cli_action_info.c create mode 100644 applications/main/nfc/cli/commands/mfu/nfc_cli_action_info.h create mode 100644 applications/main/nfc/cli/commands/mfu/nfc_cli_action_rdbl.c create mode 100644 applications/main/nfc/cli/commands/mfu/nfc_cli_action_rdbl.h create mode 100644 applications/main/nfc/cli/commands/mfu/nfc_cli_action_wrbl.c create mode 100644 applications/main/nfc/cli/commands/mfu/nfc_cli_action_wrbl.h create mode 100644 applications/main/nfc/cli/commands/mfu/nfc_cli_command_mfu.c create mode 100644 applications/main/nfc/cli/commands/mfu/nfc_cli_command_mfu.h create mode 100644 applications/main/nfc/cli/commands/nfc_cli_command_emulate.c create mode 100644 applications/main/nfc/cli/commands/nfc_cli_command_emulate.h create mode 100644 applications/main/nfc/cli/commands/nfc_cli_command_field.c create mode 100644 applications/main/nfc/cli/commands/nfc_cli_command_field.h create mode 100644 applications/main/nfc/cli/commands/nfc_cli_command_scanner.c create mode 100644 applications/main/nfc/cli/commands/nfc_cli_command_scanner.h create mode 100644 applications/main/nfc/cli/commands/raw/nfc_cli_command_raw.c create mode 100644 applications/main/nfc/cli/commands/raw/nfc_cli_command_raw.h create mode 100644 applications/main/nfc/cli/commands/raw/protocol_handlers/felica/nfc_cli_raw_felica.c create mode 100644 applications/main/nfc/cli/commands/raw/protocol_handlers/felica/nfc_cli_raw_felica.h create mode 100644 applications/main/nfc/cli/commands/raw/protocol_handlers/iso14443_3a/nfc_cli_raw_iso14443_3a.c create mode 100644 applications/main/nfc/cli/commands/raw/protocol_handlers/iso14443_3a/nfc_cli_raw_iso14443_3a.h create mode 100644 applications/main/nfc/cli/commands/raw/protocol_handlers/iso14443_3b/nfc_cli_raw_iso14443_3b.c create mode 100644 applications/main/nfc/cli/commands/raw/protocol_handlers/iso14443_3b/nfc_cli_raw_iso14443_3b.h create mode 100644 applications/main/nfc/cli/commands/raw/protocol_handlers/iso15693_3/nfc_cli_raw_iso15693_3.c create mode 100644 applications/main/nfc/cli/commands/raw/protocol_handlers/iso15693_3/nfc_cli_raw_iso15693_3.h create mode 100644 applications/main/nfc/cli/commands/raw/protocol_handlers/nfc_cli_raw_common_types.h create mode 100644 applications/main/nfc/cli/nfc_cli.c create mode 100644 applications/main/nfc/cli/nfc_cli_command_base.h create mode 100644 applications/main/nfc/cli/nfc_cli_command_base_i.h create mode 100644 applications/main/nfc/cli/nfc_cli_command_processor.c create mode 100644 applications/main/nfc/cli/nfc_cli_command_processor.h create mode 100644 applications/main/nfc/cli/nfc_cli_commands.c create mode 100644 applications/main/nfc/cli/nfc_cli_commands.h delete mode 100644 applications/main/nfc/nfc_cli.c create mode 100644 applications/main/nfc/resources/nfc/assets/vendors.nfc diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index a9c8808a8..461cc6362 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -8,11 +8,7 @@ App( stack_size=5 * 1024, order=30, resources="resources", - sources=[ - "*.c*", - "!plugins", - "!nfc_cli.c", - ], + sources=["*.c*", "!plugins", "!nfc_cli.c", "!cli"], fap_libs=["assets", "mbedtls"], fap_icon="icon.png", fap_category="NFC", @@ -263,7 +259,46 @@ App( apptype=FlipperAppType.PLUGIN, entry_point="cli_nfc_ep", requires=["cli"], - sources=["nfc_cli.c"], + sources=[ + "helpers/mf_classic_key_cache.c", + "helpers/protocol_support/iso14443_3a/iso14443_3a_render.c", + "helpers/protocol_support/mf_ultralight/mf_ultralight_render.c", + "cli/nfc_cli.c", + "cli/nfc_cli_commands.c", + "cli/nfc_cli_command_processor.c", + "cli/commands/helpers/nfc_cli_format.c", + "cli/commands/helpers/nfc_cli_scanner.c", + "cli/commands/helpers/nfc_cli_protocol_parser.c", + "cli/commands/raw/nfc_cli_command_raw.c", + "cli/commands/raw/protocol_handlers/iso14443_3a/nfc_cli_raw_iso14443_3a.c", + "cli/commands/raw/protocol_handlers/iso14443_3b/nfc_cli_raw_iso14443_3b.c", + "cli/commands/raw/protocol_handlers/iso15693_3/nfc_cli_raw_iso15693_3.c", + "cli/commands/raw/protocol_handlers/felica/nfc_cli_raw_felica.c", + "cli/commands/apdu/nfc_cli_command_apdu.c", + "cli/commands/apdu/protocol_handlers/iso14443_4a/nfc_cli_apdu_iso14443_4a.c", + "cli/commands/apdu/protocol_handlers/iso14443_4b/nfc_cli_apdu_iso14443_4b.c", + "cli/commands/apdu/protocol_handlers/iso15693_3/nfc_cli_apdu_iso15693_3.c", + "cli/commands/dump/nfc_cli_command_dump.c", + "cli/commands/dump/protocols/iso14443_3a/nfc_cli_dump_iso14443_3a.c", + "cli/commands/dump/protocols/iso14443_3b/nfc_cli_dump_iso14443_3b.c", + "cli/commands/dump/protocols/iso14443_4a/nfc_cli_dump_iso14443_4a.c", + "cli/commands/dump/protocols/iso14443_4b/nfc_cli_dump_iso14443_4b.c", + "cli/commands/dump/protocols/iso15693_3/nfc_cli_dump_iso15693_3.c", + "cli/commands/dump/protocols/mf_ultralight/nfc_cli_dump_mf_ultralight.c", + "cli/commands/dump/protocols/mf_classic/nfc_cli_dump_mf_classic.c", + "cli/commands/dump/protocols/mf_plus/nfc_cli_dump_mf_plus.c", + "cli/commands/dump/protocols/mf_desfire/nfc_cli_dump_mf_desfire.c", + "cli/commands/dump/protocols/slix/nfc_cli_dump_slix.c", + "cli/commands/dump/protocols/st25tb/nfc_cli_dump_st25tb.c", + "cli/commands/dump/protocols/felica/nfc_cli_dump_felica.c", + "cli/commands/mfu/nfc_cli_command_mfu.c", + "cli/commands/mfu/nfc_cli_action_info.c", + "cli/commands/mfu/nfc_cli_action_rdbl.c", + "cli/commands/mfu/nfc_cli_action_wrbl.c", + "cli/commands/nfc_cli_command_emulate.c", + "cli/commands/nfc_cli_command_scanner.c", + "cli/commands/nfc_cli_command_field.c", + ], ) App( diff --git a/applications/main/nfc/cli/commands/apdu/nfc_cli_command_apdu.c b/applications/main/nfc/cli/commands/apdu/nfc_cli_command_apdu.c new file mode 100644 index 000000000..cbbb86681 --- /dev/null +++ b/applications/main/nfc/cli/commands/apdu/nfc_cli_command_apdu.c @@ -0,0 +1,311 @@ +#include "nfc_cli_command_apdu.h" +#include "../helpers/nfc_cli_format.h" +#include "../helpers/nfc_cli_protocol_parser.h" + +#include "protocol_handlers/iso14443_4a/nfc_cli_apdu_iso14443_4a.h" +#include "protocol_handlers/iso14443_4b/nfc_cli_apdu_iso14443_4b.h" +#include "protocol_handlers/iso15693_3/nfc_cli_apdu_iso15693_3.h" + +#include +#include +#include + +#include +#include +#include + +#define TAG "APDU" + +#define NFC_CLI_PROTOCOL_SUPPORT_MAX_BUFFER_SIZE (256) + +typedef NfcCommand ( + *NfcCliApduProtocolHandler)(NfcGenericEvent event, NfcCliApduRequestResponse* instance); + +static const char* raw_error_names[] = { + [NfcCliApduErrorNone] = "None", + [NfcCliApduErrorTimeout] = "Timeout", + [NfcCliApduErrorProtocol] = "Internal protocol", + [NfcCliApduErrorWrongCrc] = "Wrong CRC", + [NfcCliApduErrorNotPresent] = "No card", +}; + +typedef enum { + NfcCliProtocolRequestTypeNormalExecute, + NfcCliProtocolRequestTypeAbort, +} NfcCliProtocolRequestType; + +typedef struct { + uint8_t* data; + size_t size; +} NfcCliApduData; + +static void ApduItem_init(NfcCliApduData* item) { + item->size = 0; + item->data = NULL; +} + +static void ApduItem_init_set(NfcCliApduData* item, const NfcCliApduData* src) { + item->data = malloc(src->size); + item->size = src->size; + memcpy(item->data, src->data, src->size); +} + +static void ApduItem_set(NfcCliApduData* item, const NfcCliApduData* src) { + if(item->data == NULL) { + item->data = malloc(src->size); + } else if(item->size != src->size) { + uint8_t* buf = realloc(item->data, src->size); + furi_check(buf); + item->data = buf; + } + + item->size = src->size; + memcpy(item->data, src->data, src->size); +} + +static void ApduItem_clear(NfcCliApduData* item) { + if(item->data) free(item->data); + item->data = NULL; + item->size = 0; +} + +ARRAY_DEF( + NfcCliApduItemArray, + NfcCliApduData, + (INIT(API_2(ApduItem_init)), + SET(API_6(ApduItem_set)), + INIT_SET(API_6(ApduItem_init_set)), + CLEAR(API_2(ApduItem_clear)))) + +typedef struct { + Nfc* nfc; + bool auto_detect; + NfcCliApduItemArray_t apdu; + NfcCliApduRequestResponse data; + FuriSemaphore* sem_done; + FuriMessageQueue* input_queue; +} NfcCliApduContext; + +static NfcCliActionContext* nfc_cli_apdu_alloc_ctx(Nfc* nfc) { + furi_assert(nfc); + NfcCliApduContext* instance = malloc(sizeof(NfcCliApduContext)); + instance->nfc = nfc; + instance->data.protocol = NfcProtocolInvalid; + instance->auto_detect = true; + NfcCliApduItemArray_init(instance->apdu); + instance->data.rx_buffer = bit_buffer_alloc(NFC_CLI_PROTOCOL_SUPPORT_MAX_BUFFER_SIZE); + instance->data.tx_buffer = bit_buffer_alloc(NFC_CLI_PROTOCOL_SUPPORT_MAX_BUFFER_SIZE); + + instance->sem_done = furi_semaphore_alloc(1, 0); + instance->input_queue = furi_message_queue_alloc(1, sizeof(NfcCliProtocolRequestType)); + return instance; +} + +static void nfc_cli_apdu_free_ctx(NfcCliActionContext* action_ctx) { + furi_assert(action_ctx); + NfcCliApduContext* instance = action_ctx; + + instance->nfc = NULL; + NfcCliApduItemArray_clear(instance->apdu); + + bit_buffer_free(instance->data.rx_buffer); + bit_buffer_free(instance->data.tx_buffer); + furi_semaphore_free(instance->sem_done); + furi_message_queue_free(instance->input_queue); + free(instance); +} + +static inline void nfc_cli_apdu_print_result(const NfcCliApduContext* instance) { + nfc_cli_printf_array( + bit_buffer_get_data(instance->data.tx_buffer), + bit_buffer_get_size_bytes(instance->data.tx_buffer), + "\r\nTx: "); + + if(instance->data.result != NfcCliApduErrorNone) + printf("\r\nError: \"%s\"\r\n", raw_error_names[instance->data.result]); + + size_t rx_size = bit_buffer_get_size_bytes(instance->data.rx_buffer); + if(rx_size > 0) { + nfc_cli_printf_array( + bit_buffer_get_data(instance->data.rx_buffer), + bit_buffer_get_size_bytes(instance->data.rx_buffer), + "\r\nRx: "); + printf("\r\n"); + } +} + +static NfcProtocol nfc_cli_apdu_protocol_autodetect(Nfc* nfc) { + const NfcProtocol supported_protocols[] = { + NfcProtocolIso14443_4a, + NfcProtocolIso14443_4b, + NfcProtocolIso15693_3, + }; + + const char* supported_names[] = {"Iso14443_4a", "Iso14443_4b", "Iso15693_3"}; + + NfcProtocol protocol = NfcProtocolInvalid; + for(uint8_t i = 0; i < COUNT_OF(supported_protocols); i++) { + NfcPoller* poller = nfc_poller_alloc(nfc, supported_protocols[i]); + bool is_detected = nfc_poller_detect(poller); + nfc_poller_free(poller); + if(is_detected) { + protocol = supported_protocols[i]; + printf("Detected tag: %s\r\n", supported_names[i]); + break; + } + } + return protocol; +} + +static NfcCliApduProtocolHandler nfc_cli_apdu_poller_get_handler(NfcProtocol protocol) { + if(protocol == NfcProtocolIso14443_4a) + return nfc_cli_apdu_iso14443_4a_handler; + else if(protocol == NfcProtocolIso14443_4b) + return nfc_cli_apdu_iso14443_4b_handler; + else if(protocol == NfcProtocolIso15693_3) + return nfc_cli_apdu_iso15693_3_handler; + else + return NULL; +} + +static NfcCommand nfc_cli_apdu_poller_callback(NfcGenericEvent event, void* context) { + NfcCliApduContext* instance = context; + + FURI_LOG_D(TAG, "Poller callback"); + NfcCliProtocolRequestType request_type = NfcCliProtocolRequestTypeAbort; + furi_message_queue_get(instance->input_queue, &request_type, FuriWaitForever); + + NfcCommand command = NfcCommandStop; + if(request_type == NfcCliProtocolRequestTypeAbort) { + FURI_LOG_D(TAG, "Aborting poller callback"); + } else { + NfcCliApduProtocolHandler handler = + nfc_cli_apdu_poller_get_handler(instance->data.protocol); + if(handler) command = handler(event, &instance->data); + } + furi_semaphore_release(instance->sem_done); + return command; +} + +static void nfc_cli_apdu_execute(PipeSide* pipe, void* context) { + UNUSED(pipe); + furi_assert(context); + NfcCliApduContext* instance = context; + + if(instance->auto_detect) { + instance->data.protocol = nfc_cli_apdu_protocol_autodetect(instance->nfc); + } + + if(instance->data.protocol != NfcProtocolInvalid) { + NfcPoller* poller = nfc_poller_alloc(instance->nfc, instance->data.protocol); + + NfcCliProtocolRequestType request_type = NfcCliProtocolRequestTypeNormalExecute; + nfc_poller_start(poller, nfc_cli_apdu_poller_callback, instance); + + NfcCliApduItemArray_it_t it; + for(NfcCliApduItemArray_it(it, instance->apdu); !NfcCliApduItemArray_end_p(it); + NfcCliApduItemArray_next(it)) { + const NfcCliApduData* item = NfcCliApduItemArray_cref(it); + bit_buffer_copy_bytes(instance->data.tx_buffer, item->data, item->size); + bit_buffer_reset(instance->data.rx_buffer); + + furi_message_queue_put(instance->input_queue, &request_type, FuriWaitForever); + furi_semaphore_acquire(instance->sem_done, FuriWaitForever); + nfc_cli_apdu_print_result(instance); + if(instance->data.result != NfcCliApduErrorNone) break; + } + + request_type = NfcCliProtocolRequestTypeAbort; + furi_message_queue_put(instance->input_queue, &request_type, FuriWaitForever); + nfc_poller_stop(poller); + nfc_poller_free(poller); + } +} + +static const NfcProtocolNameValuePair supported_protocols[] = { + {.name = "4a", .value = NfcProtocolIso14443_4a}, + {.name = "4b", .value = NfcProtocolIso14443_4b}, + {.name = "15", .value = NfcProtocolIso15693_3}, +}; + +static bool nfc_cli_apdu_parse_protocol(FuriString* value, void* output) { + NfcCliApduContext* ctx = output; + ctx->auto_detect = false; + + NfcCliProtocolParser* parser = + nfc_cli_protocol_parser_alloc(supported_protocols, COUNT_OF(supported_protocols)); + + bool result = nfc_cli_protocol_parser_get(parser, value, &ctx->data.protocol); + + nfc_cli_protocol_parser_free(parser); + return result; +} + +static bool nfc_cli_apdu_parse_data(FuriString* value, void* output) { + NfcCliApduContext* ctx = output; + + bool result = false; + FuriString* word = furi_string_alloc(); + + while(args_read_string_and_trim(value, word)) { + size_t len = furi_string_size(word); + if(len % 2 != 0) break; + + size_t data_length = len / 2; + + const size_t max_len = UINT16_MAX; + if(data_length > max_len) { + printf( + ANSI_FG_RED "\r\nData payload is too long, max length = %d bytes\r\n" ANSI_RESET, + max_len); + break; + } + + NfcCliApduData* item = NfcCliApduItemArray_push_new(ctx->apdu); + item->size = data_length; + item->data = malloc(data_length); + result = args_read_hex_bytes(word, item->data, item->size); + } + furi_string_free(word); + + return result; +} + +const NfcCliKeyDescriptor apdu_keys[] = { + { + .long_name = "protocol", + .short_name = "p", + .description = "set protocol (4a, 4b, 15) directly, otherwise autodetected", + .features = {.parameter = true, .required = false}, + .parse = nfc_cli_apdu_parse_protocol, + }, + { + .long_name = "data", + .short_name = "d", + .description = "apdu payloads in format p1 p2 p3", + .features = {.parameter = true, .multivalue = true, .required = true}, + .parse = nfc_cli_apdu_parse_data, + }, +}; + +const NfcCliActionDescriptor apdu_action = { + .name = "apdu", + .description = "Send APDU data to iso14443_4a, iso14443_4b or iso15693_3", + .alloc = nfc_cli_apdu_alloc_ctx, + .free = nfc_cli_apdu_free_ctx, + .execute = nfc_cli_apdu_execute, + .key_count = COUNT_OF(apdu_keys), + .keys = apdu_keys, +}; + +const NfcCliActionDescriptor* apdu_actions_collection[] = {&apdu_action}; + +//Command descriptor +ADD_NFC_CLI_COMMAND(apdu, "", apdu_actions_collection); + +//Command usage: apdu +//Command examples: +//apdu -d 00a404000e325041592e5359532e444446303100 00A4040008A000000333010102 +//apdu -p 4a -d 00a404000e325041592e5359532e444446303100 00A4040008A000000333010102 +//apdu -p 4b -d 00a404000e325041592e5359532e444446303100 00A4040008A000000333010102 +//apdu -p 15 -d 00a404000e325041592e5359532e444446303100 00A4040008A000000333010102 diff --git a/applications/main/nfc/cli/commands/apdu/nfc_cli_command_apdu.h b/applications/main/nfc/cli/commands/apdu/nfc_cli_command_apdu.h new file mode 100644 index 000000000..84e4ba5f6 --- /dev/null +++ b/applications/main/nfc/cli/commands/apdu/nfc_cli_command_apdu.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../../nfc_cli_command_base_i.h" + +extern const NfcCliCommandDescriptor apdu_cmd; diff --git a/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso14443_4a/nfc_cli_apdu_iso14443_4a.c b/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso14443_4a/nfc_cli_apdu_iso14443_4a.c new file mode 100644 index 000000000..124ba46e3 --- /dev/null +++ b/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso14443_4a/nfc_cli_apdu_iso14443_4a.c @@ -0,0 +1,37 @@ +#include "nfc_cli_apdu_iso14443_4a.h" +#include "../../../helpers/nfc_cli_format.h" + +#include +#include + +#define TAG "ISO14A_4A" + +#define BIT_BUFFER_EMPTY(buffer) ((bit_buffer_get_size_bytes(buffer) == 0)) + +static NfcCliApduError nfc_cli_apdu_iso14443_4a_process_error(Iso14443_4aError error) { + switch(error) { + case Iso14443_4aErrorNone: + return NfcCliApduErrorNone; + case Iso14443_4aErrorTimeout: + return NfcCliApduErrorTimeout; + case Iso14443_4aErrorNotPresent: + return NfcCliApduErrorNotPresent; + default: + return NfcCliApduErrorProtocol; + } +} + +NfcCommand + nfc_cli_apdu_iso14443_4a_handler(NfcGenericEvent event, NfcCliApduRequestResponse* instance) { + Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { + Iso14443_4aError err = iso14443_4a_poller_send_block( + event.instance, instance->tx_buffer, instance->rx_buffer); + instance->result = nfc_cli_apdu_iso14443_4a_process_error(err); + if(err != Iso14443_4aErrorNone) command = NfcCommandStop; + } + + return command; +} diff --git a/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso14443_4a/nfc_cli_apdu_iso14443_4a.h b/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso14443_4a/nfc_cli_apdu_iso14443_4a.h new file mode 100644 index 000000000..907aafeaf --- /dev/null +++ b/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso14443_4a/nfc_cli_apdu_iso14443_4a.h @@ -0,0 +1,6 @@ +#pragma once + +#include "../nfc_cli_apdu_common_types.h" + +NfcCommand + nfc_cli_apdu_iso14443_4a_handler(NfcGenericEvent event, NfcCliApduRequestResponse* instance); diff --git a/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso14443_4b/nfc_cli_apdu_iso14443_4b.c b/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso14443_4b/nfc_cli_apdu_iso14443_4b.c new file mode 100644 index 000000000..eee635515 --- /dev/null +++ b/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso14443_4b/nfc_cli_apdu_iso14443_4b.c @@ -0,0 +1,37 @@ +#include "nfc_cli_apdu_iso14443_4b.h" +#include "../../../helpers/nfc_cli_format.h" + +#include +#include + +#define TAG "ISO14A_4B" + +#define BIT_BUFFER_EMPTY(buffer) ((bit_buffer_get_size_bytes(buffer) == 0)) + +static NfcCliApduError nfc_cli_apdu_iso14443_4b_process_error(Iso14443_4bError error) { + switch(error) { + case Iso14443_4bErrorNone: + return NfcCliApduErrorNone; + case Iso14443_4bErrorTimeout: + return NfcCliApduErrorTimeout; + case Iso14443_4bErrorNotPresent: + return NfcCliApduErrorNotPresent; + default: + return NfcCliApduErrorProtocol; + } +} + +NfcCommand + nfc_cli_apdu_iso14443_4b_handler(NfcGenericEvent event, NfcCliApduRequestResponse* instance) { + Iso14443_4bPollerEvent* iso14443_4b_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(iso14443_4b_event->type == Iso14443_4bPollerEventTypeReady) { + Iso14443_4bError err = iso14443_4b_poller_send_block( + event.instance, instance->tx_buffer, instance->rx_buffer); + instance->result = nfc_cli_apdu_iso14443_4b_process_error(err); + if(err != Iso14443_4bErrorNone) command = NfcCommandStop; + } + + return command; +} diff --git a/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso14443_4b/nfc_cli_apdu_iso14443_4b.h b/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso14443_4b/nfc_cli_apdu_iso14443_4b.h new file mode 100644 index 000000000..e8304f497 --- /dev/null +++ b/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso14443_4b/nfc_cli_apdu_iso14443_4b.h @@ -0,0 +1,6 @@ +#pragma once + +#include "../nfc_cli_apdu_common_types.h" + +NfcCommand + nfc_cli_apdu_iso14443_4b_handler(NfcGenericEvent event, NfcCliApduRequestResponse* instance); diff --git a/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso15693_3/nfc_cli_apdu_iso15693_3.c b/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso15693_3/nfc_cli_apdu_iso15693_3.c new file mode 100644 index 000000000..bd817d1f6 --- /dev/null +++ b/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso15693_3/nfc_cli_apdu_iso15693_3.c @@ -0,0 +1,37 @@ +#include "nfc_cli_apdu_iso15693_3.h" +#include "../../../helpers/nfc_cli_format.h" + +#include +#include + +#define TAG "ISO15" + +#define BIT_BUFFER_EMPTY(buffer) ((bit_buffer_get_size_bytes(buffer) == 0)) + +static NfcCliApduError nfc_cli_apdu_iso15693_3_process_error(Iso15693_3Error error) { + switch(error) { + case Iso15693_3ErrorNone: + return NfcCliApduErrorNone; + case Iso15693_3ErrorTimeout: + return NfcCliApduErrorTimeout; + case Iso15693_3ErrorNotPresent: + return NfcCliApduErrorNotPresent; + default: + return NfcCliApduErrorProtocol; + } +} + +NfcCommand + nfc_cli_apdu_iso15693_3_handler(NfcGenericEvent event, NfcCliApduRequestResponse* instance) { + Iso15693_3PollerEvent* iso15693_3_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(iso15693_3_event->type == Iso15693_3PollerEventTypeReady) { + Iso15693_3Error err = iso15693_3_poller_send_frame( + event.instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_POLL_FC); + instance->result = nfc_cli_apdu_iso15693_3_process_error(err); + if(err != Iso15693_3ErrorNone) command = NfcCommandStop; + } + + return command; +} diff --git a/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso15693_3/nfc_cli_apdu_iso15693_3.h b/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso15693_3/nfc_cli_apdu_iso15693_3.h new file mode 100644 index 000000000..d93626918 --- /dev/null +++ b/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso15693_3/nfc_cli_apdu_iso15693_3.h @@ -0,0 +1,6 @@ +#pragma once + +#include "../nfc_cli_apdu_common_types.h" + +NfcCommand + nfc_cli_apdu_iso15693_3_handler(NfcGenericEvent event, NfcCliApduRequestResponse* instance); diff --git a/applications/main/nfc/cli/commands/apdu/protocol_handlers/nfc_cli_apdu_common_types.h b/applications/main/nfc/cli/commands/apdu/protocol_handlers/nfc_cli_apdu_common_types.h new file mode 100644 index 000000000..125d26ca5 --- /dev/null +++ b/applications/main/nfc/cli/commands/apdu/protocol_handlers/nfc_cli_apdu_common_types.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include + +typedef enum { + NfcCliApduErrorNone, + NfcCliApduErrorTimeout, + NfcCliApduErrorNotPresent, + NfcCliApduErrorWrongCrc, + NfcCliApduErrorProtocol, +} NfcCliApduError; + +typedef struct { + NfcProtocol protocol; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + NfcCliApduError result; +} NfcCliApduRequestResponse; diff --git a/applications/main/nfc/cli/commands/dump/nfc_cli_command_dump.c b/applications/main/nfc/cli/commands/dump/nfc_cli_command_dump.c new file mode 100644 index 000000000..2f968b58e --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/nfc_cli_command_dump.c @@ -0,0 +1,319 @@ +#include "nfc_cli_command_dump.h" +#include "protocols/nfc_cli_dump_common_types.h" +#include "../helpers/nfc_cli_format.h" +#include "../helpers/nfc_cli_protocol_parser.h" +#include "../helpers/nfc_cli_scanner.h" + +#include "protocols/iso14443_3a/nfc_cli_dump_iso14443_3a.h" +#include "protocols/iso14443_3b/nfc_cli_dump_iso14443_3b.h" +#include "protocols/iso14443_4a/nfc_cli_dump_iso14443_4a.h" +#include "protocols/iso14443_4b/nfc_cli_dump_iso14443_4b.h" +#include "protocols/iso15693_3/nfc_cli_dump_iso15693_3.h" +#include "protocols/mf_classic/nfc_cli_dump_mf_classic.h" +#include "protocols/mf_desfire/nfc_cli_dump_mf_desfire.h" +#include "protocols/mf_plus/nfc_cli_dump_mf_plus.h" +#include "protocols/mf_ultralight/nfc_cli_dump_mf_ultralight.h" +#include "protocols/slix/nfc_cli_dump_slix.h" +#include "protocols/st25tb/nfc_cli_dump_st25tb.h" +#include "protocols/felica/nfc_cli_dump_felica.h" + +#include +#include +#include +#include +#include + +#define NFC_DEFAULT_FOLDER EXT_PATH("nfc") +#define NFC_FILE_EXTENSION ".nfc" +#define NFC_CLI_DEFAULT_FILENAME_PREFIX "dump" + +#define NFC_CLI_DUMP_DEFAULT_TIMEOUT (5000) + +#define TAG "DUMP" + +static const char* nfc_cli_dump_error_names[NfcCliDumpErrorNum] = { + [NfcCliDumpErrorNone] = "", + [NfcCliDumpErrorNotPresent] = "card not present", + [NfcCliDumpErrorAuthFailed] = "authentication failed", + [NfcCliDumpErrorTimeout] = "timeout", + [NfcCliDumpErrorFailedToRead] = "failed to read", +}; + +static NfcCliActionContext* nfc_cli_dump_alloc_ctx(Nfc* nfc) { + furi_assert(nfc); + NfcCliDumpContext* instance = malloc(sizeof(NfcCliDumpContext)); + instance->nfc = nfc; + instance->file_path = furi_string_alloc(); + instance->storage = furi_record_open(RECORD_STORAGE); + instance->sem_done = furi_semaphore_alloc(1, 0); + instance->nfc_device = nfc_device_alloc(); + instance->desired_protocol = NfcProtocolInvalid; + instance->auth_ctx.skip_auth = true; + instance->auth_ctx.key_size = 0; + instance->timeout = NFC_CLI_DUMP_DEFAULT_TIMEOUT; + + instance->mfc_key_cache = mf_classic_key_cache_alloc(); + instance->scanner = nfc_cli_scanner_alloc(nfc); + return instance; +} + +static void nfc_cli_dump_free_ctx(NfcCliActionContext* ctx) { + furi_assert(ctx); + NfcCliDumpContext* instance = ctx; + instance->desired_protocol = NfcProtocolInvalid; + furi_string_free(instance->file_path); + instance->nfc = NULL; + furi_record_close(RECORD_STORAGE); + furi_semaphore_free(instance->sem_done); + nfc_device_free(instance->nfc_device); + + mf_classic_key_cache_free(instance->mfc_key_cache); + nfc_cli_scanner_free(instance->scanner); + free(instance); +} + +static bool nfc_cli_dump_parse_filename_key(FuriString* value, void* output) { + furi_assert(value); + furi_assert(output); + NfcCliDumpContext* ctx = output; + furi_string_set(ctx->file_path, value); + return true; +} + +NfcGenericCallback protocol_poller_callbacks[NfcProtocolNum] = { + [NfcProtocolMfUltralight] = nfc_cli_dump_poller_callback_mf_ultralight, + [NfcProtocolMfClassic] = nfc_cli_dump_poller_callback_mf_classic, + [NfcProtocolFelica] = nfc_cli_dump_poller_callback_felica, + [NfcProtocolIso14443_3a] = nfc_cli_dump_poller_callback_iso14443_3a, + [NfcProtocolIso14443_3b] = nfc_cli_dump_poller_callback_iso14443_3b, + [NfcProtocolIso14443_4a] = nfc_cli_dump_poller_callback_iso14443_4a, + [NfcProtocolIso14443_4b] = nfc_cli_dump_poller_callback_iso14443_4b, + [NfcProtocolIso15693_3] = nfc_cli_dump_poller_callback_iso15693_3, + [NfcProtocolSlix] = nfc_cli_dump_poller_callback_slix, + [NfcProtocolMfDesfire] = nfc_cli_dump_poller_callback_mf_desfire, + [NfcProtocolMfPlus] = nfc_cli_dump_poller_callback_mf_plus, + [NfcProtocolSt25tb] = nfc_cli_dump_poller_callback_st25tb, +}; + +static void nfc_cli_dump_generate_filename(FuriString* file_path) { + furi_string_set_str(file_path, NFC_DEFAULT_FOLDER); + DateTime dt; + furi_hal_rtc_get_datetime(&dt); + furi_string_cat_printf( + file_path, + "/%s-%.4d%.2d%.2d-%.2d%.2d%.2d%s", + NFC_CLI_DEFAULT_FILENAME_PREFIX, + dt.year, + dt.month, + dt.day, + dt.hour, + dt.minute, + dt.second, + NFC_FILE_EXTENSION); +} + +static bool nfc_cli_dump_check_filepath_valid(FuriString* file_path, Storage* storage) { + bool file_exists = false; + bool dir_exists = false; + + FuriString* buf = furi_string_alloc(); + + path_extract_dirname(furi_string_get_cstr(file_path), buf); + dir_exists = storage_dir_exists(storage, furi_string_get_cstr(buf)); + file_exists = storage_file_exists(storage, furi_string_get_cstr(file_path)); + + bool result = true; + if(!dir_exists) { + printf(ANSI_FG_RED "Path \'%s\' doesn't exist\r\n" ANSI_RESET, furi_string_get_cstr(buf)); + result = false; + } else if(file_exists) { + printf( + ANSI_FG_RED "File \'%s\' already exists\r\n" ANSI_RESET, + furi_string_get_cstr(file_path)); + result = false; + } + furi_string_free(buf); + + return result; +} + +static bool nfc_cli_dump_process_filename(NfcCliDumpContext* instance) { + bool result = false; + if(furi_string_empty(instance->file_path)) { + nfc_cli_dump_generate_filename(instance->file_path); + result = true; + } else { + result = nfc_cli_dump_check_filepath_valid(instance->file_path, instance->storage); + } + return result; +} + +static size_t nfc_cli_dump_set_protocol(NfcCliDumpContext* instance) { + size_t protocol_count = 0; + if(instance->desired_protocol != NfcProtocolInvalid) { + protocol_count = 1; + } else { + if(!nfc_cli_scanner_detect_protocol(instance->scanner, instance->timeout)) { + NfcCliDumpError error = NfcCliDumpErrorTimeout; + printf(ANSI_FG_RED "Error: %s\r\n" ANSI_RESET, nfc_cli_dump_error_names[error]); + } else { + nfc_cli_scanner_list_detected_protocols(instance->scanner); + protocol_count = nfc_cli_scanner_detected_protocol_num(instance->scanner); + instance->desired_protocol = nfc_cli_scanner_get_protocol(instance->scanner, 0); + } + } + return protocol_count; +} + +static bool nfc_cli_dump_card(NfcCliDumpContext* instance) { + instance->poller = nfc_poller_alloc(instance->nfc, instance->desired_protocol); + NfcGenericCallback callback = protocol_poller_callbacks[instance->desired_protocol]; + if(callback) { + nfc_poller_start(instance->poller, callback, instance); + FuriStatus status = furi_semaphore_acquire(instance->sem_done, instance->timeout); + + if(status == FuriStatusErrorTimeout) instance->result = NfcCliDumpErrorTimeout; + nfc_poller_stop(instance->poller); + } + nfc_poller_free(instance->poller); + + return instance->result == NfcCliDumpErrorNone; +} + +static void nfc_cli_dump_execute(PipeSide* pipe, NfcCliActionContext* context) { + UNUSED(pipe); + furi_assert(context); + NfcCliDumpContext* instance = context; + do { + if(!nfc_cli_dump_process_filename(instance)) break; + + size_t protocol_count = nfc_cli_dump_set_protocol(instance); + if(instance->desired_protocol == NfcProtocolInvalid) break; + + printf("Dumping as \"%s\"\r\n", nfc_cli_get_protocol_name(instance->desired_protocol)); + if(protocol_count > 1) printf("Use \'-p\' key to specify another protocol\r\n"); + + if(nfc_cli_dump_card(instance)) { + const char* path = furi_string_get_cstr(instance->file_path); + if(nfc_device_save(instance->nfc_device, path)) { + printf("Dump saved to \'%s\'\r\n", path); + } + } else { + printf( + ANSI_FG_RED "Error: %s\r\n" ANSI_RESET, + nfc_cli_dump_error_names[instance->result]); + } + } while(false); +} + +static const NfcProtocolNameValuePair supported_protocols[] = { + {.name = "14_3a", .value = NfcProtocolIso14443_3a}, + {.name = "14_3b", .value = NfcProtocolIso14443_3b}, + {.name = "14_4a", .value = NfcProtocolIso14443_4a}, + {.name = "14_4b", .value = NfcProtocolIso14443_4b}, + {.name = "15", .value = NfcProtocolIso15693_3}, + {.name = "felica", .value = NfcProtocolFelica}, + {.name = "mfu", .value = NfcProtocolMfUltralight}, + {.name = "mfc", .value = NfcProtocolMfClassic}, + {.name = "mfp", .value = NfcProtocolMfPlus}, + {.name = "des", .value = NfcProtocolMfDesfire}, + {.name = "slix", .value = NfcProtocolSlix}, + {.name = "st25", .value = NfcProtocolSt25tb}, +}; + +static bool nfc_cli_dump_parse_protocol(FuriString* value, void* output) { + furi_assert(value); + furi_assert(output); + NfcCliDumpContext* ctx = output; + + NfcCliProtocolParser* parser = + nfc_cli_protocol_parser_alloc(supported_protocols, COUNT_OF(supported_protocols)); + + bool result = nfc_cli_protocol_parser_get(parser, value, &ctx->desired_protocol); + + nfc_cli_protocol_parser_free(parser); + return result; +} + +static bool nfc_cli_dump_parse_key(FuriString* value, void* output) { + furi_assert(value); + furi_assert(output); + NfcCliDumpContext* ctx = output; + NfcCliDumpAuthContext* auth_ctx = &ctx->auth_ctx; + + bool result = false; + do { + size_t len = furi_string_size(value); + if(len % 2 != 0) break; + size_t data_length = len / 2; + + if(data_length != MF_ULTRALIGHT_AUTH_PASSWORD_SIZE && + data_length != MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE) { + printf(ANSI_FG_RED "Error: Wrong key size" ANSI_RESET); + break; + } + + if(!args_read_hex_bytes(value, auth_ctx->key.key, data_length)) break; + auth_ctx->key_size = data_length; + auth_ctx->skip_auth = false; + result = true; + } while(false); + + return result; +} + +bool nfc_cli_dump_parse_timeout(FuriString* value, NfcCliActionContext* output) { + NfcCliDumpContext* ctx = output; + + StrintParseError err = strint_to_uint32(furi_string_get_cstr(value), NULL, &ctx->timeout, 10); + return err == StrintParseNoError; +} + +const NfcCliKeyDescriptor dump_keys[] = { + { + .long_name = "key", + .short_name = "k", + .description = "key to path auth in protocols which requires it", + .features = {.required = false, .parameter = true}, + .parse = nfc_cli_dump_parse_key, + }, + { + .long_name = "protocol", + .short_name = "p", + .description = "desired protocol", + .features = {.required = false, .parameter = true}, + .parse = nfc_cli_dump_parse_protocol, + }, + { + .features = {.required = false, .parameter = true}, + .long_name = "file", + .short_name = "f", + .description = "path to new file", + .parse = nfc_cli_dump_parse_filename_key, + }, + { + .features = {.required = false, .parameter = true}, + .long_name = "timeout", + .short_name = "t", + .description = "timeout value in milliseconds", + .parse = nfc_cli_dump_parse_timeout, + }, +}; + +const NfcCliActionDescriptor dump_action = { + .name = "dump", + .description = "Dump tag to .nfc file", + .alloc = nfc_cli_dump_alloc_ctx, + .free = nfc_cli_dump_free_ctx, + .execute = nfc_cli_dump_execute, + .key_count = COUNT_OF(dump_keys), + .keys = dump_keys, +}; + +const NfcCliActionDescriptor* dump_actions_collection[] = {&dump_action}; + +//Command descriptor +ADD_NFC_CLI_COMMAND(dump, "", dump_actions_collection); + +//Command examples: +//dump -f ext/nfc/test.nfc diff --git a/applications/main/nfc/cli/commands/dump/nfc_cli_command_dump.h b/applications/main/nfc/cli/commands/dump/nfc_cli_command_dump.h new file mode 100644 index 000000000..e8d7ddc09 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/nfc_cli_command_dump.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../../nfc_cli_command_base_i.h" + +extern const NfcCliCommandDescriptor dump_cmd; diff --git a/applications/main/nfc/cli/commands/dump/protocols/felica/nfc_cli_dump_felica.c b/applications/main/nfc/cli/commands/dump/protocols/felica/nfc_cli_dump_felica.c new file mode 100644 index 000000000..b89b75aa6 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/felica/nfc_cli_dump_felica.c @@ -0,0 +1,32 @@ +#include "nfc_cli_dump_felica.h" +#include + +NfcCommand nfc_cli_dump_poller_callback_felica(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolFelica); + + NfcCliDumpContext* instance = context; + const FelicaPollerEvent* felica_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(felica_event->type == FelicaPollerEventTypeReady || + felica_event->type == FelicaPollerEventTypeIncomplete) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolFelica, nfc_poller_get_data(instance->poller)); + command = NfcCommandStop; + instance->result = NfcCliDumpErrorNone; + } else if(felica_event->type == FelicaPollerEventTypeError) { + command = NfcCommandStop; + instance->result = NfcCliDumpErrorFailedToRead; + } else if(felica_event->type == FelicaPollerEventTypeRequestAuthContext) { + FelicaAuthenticationContext* ctx = felica_event->data->auth_context; + const NfcCliDumpAuthContext* dump_auth_ctx = &instance->auth_ctx; + ctx->skip_auth = dump_auth_ctx->skip_auth; + ctx->card_key = dump_auth_ctx->key.felica_key; + } + + if(command == NfcCommandStop) { + furi_semaphore_release(instance->sem_done); + } + + return command; +} diff --git a/applications/main/nfc/cli/commands/dump/protocols/felica/nfc_cli_dump_felica.h b/applications/main/nfc/cli/commands/dump/protocols/felica/nfc_cli_dump_felica.h new file mode 100644 index 000000000..5114ff36e --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/felica/nfc_cli_dump_felica.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_cli_dump_common_types.h" + +NfcCommand nfc_cli_dump_poller_callback_felica(NfcGenericEvent event, void* context); diff --git a/applications/main/nfc/cli/commands/dump/protocols/iso14443_3a/nfc_cli_dump_iso14443_3a.c b/applications/main/nfc/cli/commands/dump/protocols/iso14443_3a/nfc_cli_dump_iso14443_3a.c new file mode 100644 index 000000000..a2dfe3b93 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/iso14443_3a/nfc_cli_dump_iso14443_3a.c @@ -0,0 +1,25 @@ +#include "nfc_cli_dump_iso14443_3a.h" +#include + +NfcCommand nfc_cli_dump_poller_callback_iso14443_3a(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_3a); + + NfcCliDumpContext* instance = context; + const Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data; + + NfcCommand command = NfcCommandContinue; + if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolIso14443_3a, nfc_poller_get_data(instance->poller)); + command = NfcCommandStop; + } else if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeError) { + command = NfcCommandStop; + instance->result = NfcCliDumpErrorFailedToRead; + } + + if(command == NfcCommandStop) { + furi_semaphore_release(instance->sem_done); + } + + return command; +} diff --git a/applications/main/nfc/cli/commands/dump/protocols/iso14443_3a/nfc_cli_dump_iso14443_3a.h b/applications/main/nfc/cli/commands/dump/protocols/iso14443_3a/nfc_cli_dump_iso14443_3a.h new file mode 100644 index 000000000..42bb4b0f4 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/iso14443_3a/nfc_cli_dump_iso14443_3a.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_cli_dump_common_types.h" + +NfcCommand nfc_cli_dump_poller_callback_iso14443_3a(NfcGenericEvent event, void* context); diff --git a/applications/main/nfc/cli/commands/dump/protocols/iso14443_3b/nfc_cli_dump_iso14443_3b.c b/applications/main/nfc/cli/commands/dump/protocols/iso14443_3b/nfc_cli_dump_iso14443_3b.c new file mode 100644 index 000000000..7a67af75f --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/iso14443_3b/nfc_cli_dump_iso14443_3b.c @@ -0,0 +1,27 @@ +#include "nfc_cli_dump_iso14443_3b.h" +#include + +NfcCommand nfc_cli_dump_poller_callback_iso14443_3b(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_3b); + + NfcCliDumpContext* instance = context; + const Iso14443_3bPollerEvent* iso14443_3b_event = event.event_data; + + NfcCommand command = NfcCommandContinue; + if(iso14443_3b_event->type == Iso14443_3bPollerEventTypeReady) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolIso14443_3b, nfc_poller_get_data(instance->poller)); + + instance->result = NfcCliDumpErrorNone; + command = NfcCommandStop; + } else if(iso14443_3b_event->type == Iso14443_3bPollerEventTypeError) { + instance->result = NfcCliDumpErrorFailedToRead; + command = NfcCommandStop; + } + + if(command == NfcCommandStop) { + furi_semaphore_release(instance->sem_done); + } + + return NfcCommandContinue; +} diff --git a/applications/main/nfc/cli/commands/dump/protocols/iso14443_3b/nfc_cli_dump_iso14443_3b.h b/applications/main/nfc/cli/commands/dump/protocols/iso14443_3b/nfc_cli_dump_iso14443_3b.h new file mode 100644 index 000000000..eb374303e --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/iso14443_3b/nfc_cli_dump_iso14443_3b.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_cli_dump_common_types.h" + +NfcCommand nfc_cli_dump_poller_callback_iso14443_3b(NfcGenericEvent event, void* context); diff --git a/applications/main/nfc/cli/commands/dump/protocols/iso14443_4a/nfc_cli_dump_iso14443_4a.c b/applications/main/nfc/cli/commands/dump/protocols/iso14443_4a/nfc_cli_dump_iso14443_4a.c new file mode 100644 index 000000000..380f1672c --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/iso14443_4a/nfc_cli_dump_iso14443_4a.c @@ -0,0 +1,26 @@ +#include "nfc_cli_dump_iso14443_4a.h" +#include + +NfcCommand nfc_cli_dump_poller_callback_iso14443_4a(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_4a); + + NfcCliDumpContext* instance = context; + const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; + + NfcCommand command = NfcCommandContinue; + if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolIso14443_4a, nfc_poller_get_data(instance->poller)); + instance->result = NfcCliDumpErrorNone; + command = NfcCommandStop; + } else if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeError) { + instance->result = NfcCliDumpErrorFailedToRead; + command = NfcCommandStop; + } + + if(command == NfcCommandStop) { + furi_semaphore_release(instance->sem_done); + } + + return command; +} diff --git a/applications/main/nfc/cli/commands/dump/protocols/iso14443_4a/nfc_cli_dump_iso14443_4a.h b/applications/main/nfc/cli/commands/dump/protocols/iso14443_4a/nfc_cli_dump_iso14443_4a.h new file mode 100644 index 000000000..5e2220107 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/iso14443_4a/nfc_cli_dump_iso14443_4a.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_cli_dump_common_types.h" + +NfcCommand nfc_cli_dump_poller_callback_iso14443_4a(NfcGenericEvent event, void* context); diff --git a/applications/main/nfc/cli/commands/dump/protocols/iso14443_4b/nfc_cli_dump_iso14443_4b.c b/applications/main/nfc/cli/commands/dump/protocols/iso14443_4b/nfc_cli_dump_iso14443_4b.c new file mode 100644 index 000000000..eae0669fc --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/iso14443_4b/nfc_cli_dump_iso14443_4b.c @@ -0,0 +1,25 @@ +#include "nfc_cli_dump_iso14443_4b.h" +#include + +NfcCommand nfc_cli_dump_poller_callback_iso14443_4b(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_4b); + + NfcCliDumpContext* instance = context; + const Iso14443_4bPollerEvent* iso14443_4b_event = event.event_data; + + NfcCommand command = NfcCommandContinue; + if(iso14443_4b_event->type == Iso14443_4bPollerEventTypeReady) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolIso14443_4b, nfc_poller_get_data(instance->poller)); + command = NfcCommandStop; + } else if(iso14443_4b_event->type == Iso14443_4bPollerEventTypeError) { + instance->result = NfcCliDumpErrorFailedToRead; + command = NfcCommandStop; + } + + if(command == NfcCommandStop) { + furi_semaphore_release(instance->sem_done); + } + + return command; +} diff --git a/applications/main/nfc/cli/commands/dump/protocols/iso14443_4b/nfc_cli_dump_iso14443_4b.h b/applications/main/nfc/cli/commands/dump/protocols/iso14443_4b/nfc_cli_dump_iso14443_4b.h new file mode 100644 index 000000000..ffa1df346 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/iso14443_4b/nfc_cli_dump_iso14443_4b.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_cli_dump_common_types.h" + +NfcCommand nfc_cli_dump_poller_callback_iso14443_4b(NfcGenericEvent event, void* context); diff --git a/applications/main/nfc/cli/commands/dump/protocols/iso15693_3/nfc_cli_dump_iso15693_3.c b/applications/main/nfc/cli/commands/dump/protocols/iso15693_3/nfc_cli_dump_iso15693_3.c new file mode 100644 index 000000000..e4a6ea753 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/iso15693_3/nfc_cli_dump_iso15693_3.c @@ -0,0 +1,26 @@ +#include "nfc_cli_dump_iso15693_3.h" +#include + +NfcCommand nfc_cli_dump_poller_callback_iso15693_3(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso15693_3); + + NfcCliDumpContext* instance = context; + const Iso15693_3PollerEvent* iso15693_3_event = event.event_data; + + NfcCommand command = NfcCommandContinue; + if(iso15693_3_event->type == Iso15693_3PollerEventTypeReady) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolIso15693_3, nfc_poller_get_data(instance->poller)); + instance->result = NfcCliDumpErrorFailedToRead; + command = NfcCommandStop; + } else if(iso15693_3_event->type == Iso15693_3PollerEventTypeError) { + instance->result = NfcCliDumpErrorFailedToRead; + command = NfcCommandStop; + } + + if(command == NfcCommandStop) { + furi_semaphore_release(instance->sem_done); + } + + return command; +} diff --git a/applications/main/nfc/cli/commands/dump/protocols/iso15693_3/nfc_cli_dump_iso15693_3.h b/applications/main/nfc/cli/commands/dump/protocols/iso15693_3/nfc_cli_dump_iso15693_3.h new file mode 100644 index 000000000..7fa6135ea --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/iso15693_3/nfc_cli_dump_iso15693_3.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_cli_dump_common_types.h" + +NfcCommand nfc_cli_dump_poller_callback_iso15693_3(NfcGenericEvent event, void* context); diff --git a/applications/main/nfc/cli/commands/dump/protocols/mf_classic/nfc_cli_dump_mf_classic.c b/applications/main/nfc/cli/commands/dump/protocols/mf_classic/nfc_cli_dump_mf_classic.c new file mode 100644 index 000000000..525f171eb --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/mf_classic/nfc_cli_dump_mf_classic.c @@ -0,0 +1,54 @@ +#include "nfc_cli_dump_mf_classic.h" +#include + +#define TAG "MFC" + +NfcCommand nfc_cli_dump_poller_callback_mf_classic(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolMfClassic); + + NfcCliDumpContext* instance = context; + const MfClassicPollerEvent* mfc_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(mfc_event->type == MfClassicPollerEventTypeRequestMode) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolMfClassic, nfc_poller_get_data(instance->poller)); + size_t uid_len = 0; + const uint8_t* uid = nfc_device_get_uid(instance->nfc_device, &uid_len); + if(mf_classic_key_cache_load(instance->mfc_key_cache, uid, uid_len)) { + FURI_LOG_D(TAG, "Key cache found"); + mfc_event->data->poller_mode.mode = MfClassicPollerModeRead; + } else { + FURI_LOG_D(TAG, "Key cache not found"); + instance->result = NfcCliDumpErrorFailedToRead; + command = NfcCommandStop; + } + } else if(mfc_event->type == MfClassicPollerEventTypeRequestReadSector) { + uint8_t sector_num = 0; + MfClassicKey key = {}; + MfClassicKeyType key_type = MfClassicKeyTypeA; + if(mf_classic_key_cache_get_next_key( + instance->mfc_key_cache, §or_num, &key, &key_type)) { + mfc_event->data->read_sector_request_data.sector_num = sector_num; + mfc_event->data->read_sector_request_data.key = key; + mfc_event->data->read_sector_request_data.key_type = key_type; + mfc_event->data->read_sector_request_data.key_provided = true; + } else { + mfc_event->data->read_sector_request_data.key_provided = false; + } + } else if(mfc_event->type == MfClassicPollerEventTypeSuccess) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolMfClassic, nfc_poller_get_data(instance->poller)); + instance->result = NfcCliDumpErrorNone; + command = NfcCommandStop; + } else if(mfc_event->type == MfClassicPollerEventTypeFail) { + instance->result = NfcCliDumpErrorFailedToRead; + command = NfcCommandStop; + } + + if(command == NfcCommandStop) { + furi_semaphore_release(instance->sem_done); + } + + return command; +} diff --git a/applications/main/nfc/cli/commands/dump/protocols/mf_classic/nfc_cli_dump_mf_classic.h b/applications/main/nfc/cli/commands/dump/protocols/mf_classic/nfc_cli_dump_mf_classic.h new file mode 100644 index 000000000..c1e3b1de1 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/mf_classic/nfc_cli_dump_mf_classic.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_cli_dump_common_types.h" + +NfcCommand nfc_cli_dump_poller_callback_mf_classic(NfcGenericEvent event, void* context); diff --git a/applications/main/nfc/cli/commands/dump/protocols/mf_desfire/nfc_cli_dump_mf_desfire.c b/applications/main/nfc/cli/commands/dump/protocols/mf_desfire/nfc_cli_dump_mf_desfire.c new file mode 100644 index 000000000..49a5bf04e --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/mf_desfire/nfc_cli_dump_mf_desfire.c @@ -0,0 +1,27 @@ +#include "nfc_cli_dump_mf_desfire.h" +#include + +#define TAG "MFDES" + +NfcCommand nfc_cli_dump_poller_callback_mf_desfire(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolMfDesfire); + + NfcCommand command = NfcCommandContinue; + + NfcCliDumpContext* instance = context; + const MfDesfirePollerEvent* mf_desfire_event = event.event_data; + + if(mf_desfire_event->type == MfDesfirePollerEventTypeReadSuccess) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolMfDesfire, nfc_poller_get_data(instance->poller)); + instance->result = NfcCliDumpErrorNone; + furi_semaphore_release(instance->sem_done); + command = NfcCommandStop; + } else if(mf_desfire_event->type == MfDesfirePollerEventTypeReadFailed) { + instance->result = NfcCliDumpErrorFailedToRead; + command = NfcCommandReset; + furi_semaphore_release(instance->sem_done); + } + + return command; +} diff --git a/applications/main/nfc/cli/commands/dump/protocols/mf_desfire/nfc_cli_dump_mf_desfire.h b/applications/main/nfc/cli/commands/dump/protocols/mf_desfire/nfc_cli_dump_mf_desfire.h new file mode 100644 index 000000000..89cff77cf --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/mf_desfire/nfc_cli_dump_mf_desfire.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_cli_dump_common_types.h" + +NfcCommand nfc_cli_dump_poller_callback_mf_desfire(NfcGenericEvent event, void* context); diff --git a/applications/main/nfc/cli/commands/dump/protocols/mf_plus/nfc_cli_dump_mf_plus.c b/applications/main/nfc/cli/commands/dump/protocols/mf_plus/nfc_cli_dump_mf_plus.c new file mode 100644 index 000000000..8ed421605 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/mf_plus/nfc_cli_dump_mf_plus.c @@ -0,0 +1,31 @@ +#include "nfc_cli_dump_mf_plus.h" +#include + +#define TAG "MFPLUS" + +NfcCommand nfc_cli_dump_poller_callback_mf_plus(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolMfPlus); + furi_assert(event.event_data); + + NfcCliDumpContext* instance = context; + const MfPlusPollerEvent* mf_plus_event = event.event_data; + + NfcCommand command = NfcCommandContinue; + + if(mf_plus_event->type == MfPlusPollerEventTypeReadSuccess) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolMfPlus, nfc_poller_get_data(instance->poller)); + instance->result = NfcCliDumpErrorNone; + command = NfcCommandStop; + } else if(mf_plus_event->type == MfPlusPollerEventTypeReadFailed) { + instance->result = NfcCliDumpErrorFailedToRead; + command = NfcCommandReset; + } + + if(command == NfcCommandStop) { + furi_semaphore_release(instance->sem_done); + } + + return command; +} diff --git a/applications/main/nfc/cli/commands/dump/protocols/mf_plus/nfc_cli_dump_mf_plus.h b/applications/main/nfc/cli/commands/dump/protocols/mf_plus/nfc_cli_dump_mf_plus.h new file mode 100644 index 000000000..63954e268 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/mf_plus/nfc_cli_dump_mf_plus.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_cli_dump_common_types.h" + +NfcCommand nfc_cli_dump_poller_callback_mf_plus(NfcGenericEvent event, void* context); diff --git a/applications/main/nfc/cli/commands/dump/protocols/mf_ultralight/nfc_cli_dump_mf_ultralight.c b/applications/main/nfc/cli/commands/dump/protocols/mf_ultralight/nfc_cli_dump_mf_ultralight.c new file mode 100644 index 000000000..cc607a349 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/mf_ultralight/nfc_cli_dump_mf_ultralight.c @@ -0,0 +1,39 @@ +#include "nfc_cli_dump_mf_ultralight.h" +#include + +#define TAG "MFU" + +NfcCommand nfc_cli_dump_poller_callback_mf_ultralight(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolMfUltralight); + NfcCliDumpContext* instance = context; + const MfUltralightPollerEvent* mf_ultralight_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(mf_ultralight_event->type == MfUltralightPollerEventTypeReadSuccess) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(instance->poller)); + + instance->result = NfcCliDumpErrorNone; + command = NfcCommandStop; + } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthRequest) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(instance->poller)); + + const NfcCliDumpAuthContext* auth_ctx = &instance->auth_ctx; + mf_ultralight_event->data->auth_context.skip_auth = auth_ctx->skip_auth; + mf_ultralight_event->data->auth_context.password = auth_ctx->key.password; + mf_ultralight_event->data->auth_context.tdes_key = auth_ctx->key.tdes_key; + } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthFailed) { + instance->result = NfcCliDumpErrorAuthFailed; + command = NfcCommandStop; + } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeReadFailed) { + instance->result = NfcCliDumpErrorAuthFailed; + command = NfcCommandStop; + } + + if(command == NfcCommandStop) { + furi_semaphore_release(instance->sem_done); + } + + return command; +} diff --git a/applications/main/nfc/cli/commands/dump/protocols/mf_ultralight/nfc_cli_dump_mf_ultralight.h b/applications/main/nfc/cli/commands/dump/protocols/mf_ultralight/nfc_cli_dump_mf_ultralight.h new file mode 100644 index 000000000..a005f6c16 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/mf_ultralight/nfc_cli_dump_mf_ultralight.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_cli_dump_common_types.h" + +NfcCommand nfc_cli_dump_poller_callback_mf_ultralight(NfcGenericEvent event, void* context); diff --git a/applications/main/nfc/cli/commands/dump/protocols/nfc_cli_dump_common_types.h b/applications/main/nfc/cli/commands/dump/protocols/nfc_cli_dump_common_types.h new file mode 100644 index 000000000..c14a31bdd --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/nfc_cli_dump_common_types.h @@ -0,0 +1,59 @@ +#pragma once + +#include + +#include "../../../../helpers/mf_classic_key_cache.h" +#include "../../helpers/nfc_cli_scanner.h" + +#include +#include +#include +#include + +#include +#include + +#include + +#define NFC_CLI_DUMP_KEY_MAX_SIZE (16) + +typedef union { + MfUltralightAuthPassword password; + FelicaCardKey felica_key; + MfUltralightC3DesAuthKey tdes_key; + uint8_t key[NFC_CLI_DUMP_KEY_MAX_SIZE]; +} NfcCliDumpKeyUnion; + +typedef struct { + NfcCliDumpKeyUnion key; + uint8_t key_size; + bool skip_auth; +} NfcCliDumpAuthContext; + +typedef enum { + NfcCliDumpErrorNone, + NfcCliDumpErrorTimeout, + NfcCliDumpErrorNotPresent, + NfcCliDumpErrorFailedToRead, + NfcCliDumpErrorAuthFailed, + + NfcCliDumpErrorNum, +} NfcCliDumpError; + +typedef struct { + Nfc* nfc; + FuriString* file_path; + Storage* storage; + NfcCliScanner* scanner; + NfcProtocol desired_protocol; + uint32_t timeout; + FuriSemaphore* sem_done; + + NfcCliDumpError result; + + NfcCliDumpAuthContext auth_ctx; + MfClassicKeyCache* mfc_key_cache; + + NfcPoller* poller; + NfcDevice* nfc_device; +} NfcCliDumpContext; diff --git a/applications/main/nfc/cli/commands/dump/protocols/slix/nfc_cli_dump_slix.c b/applications/main/nfc/cli/commands/dump/protocols/slix/nfc_cli_dump_slix.c new file mode 100644 index 000000000..4fdd66a44 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/slix/nfc_cli_dump_slix.c @@ -0,0 +1,26 @@ +#include "nfc_cli_dump_slix.h" +#include + +NfcCommand nfc_cli_dump_poller_callback_slix(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolSlix); + + NfcCliDumpContext* instance = context; + const SlixPollerEvent* slix_event = event.event_data; + + NfcCommand command = NfcCommandContinue; + + if(slix_event->type == SlixPollerEventTypeReady) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolSlix, nfc_poller_get_data(instance->poller)); + command = NfcCommandStop; + } else if(slix_event->type == SlixPollerEventTypeError) { + instance->result = NfcCliDumpErrorFailedToRead; + command = NfcCommandStop; + } + + if(command == NfcCommandStop) { + furi_semaphore_release(instance->sem_done); + } + + return command; +} diff --git a/applications/main/nfc/cli/commands/dump/protocols/slix/nfc_cli_dump_slix.h b/applications/main/nfc/cli/commands/dump/protocols/slix/nfc_cli_dump_slix.h new file mode 100644 index 000000000..91b01d5d1 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/slix/nfc_cli_dump_slix.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_cli_dump_common_types.h" + +NfcCommand nfc_cli_dump_poller_callback_slix(NfcGenericEvent event, void* context); diff --git a/applications/main/nfc/cli/commands/dump/protocols/st25tb/nfc_cli_dump_st25tb.c b/applications/main/nfc/cli/commands/dump/protocols/st25tb/nfc_cli_dump_st25tb.c new file mode 100644 index 000000000..c6fd0ea19 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/st25tb/nfc_cli_dump_st25tb.c @@ -0,0 +1,29 @@ +#include "nfc_cli_dump_st25tb.h" +#include + +NfcCommand nfc_cli_dump_poller_callback_st25tb(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolSt25tb); + + NfcCliDumpContext* instance = context; + const St25tbPollerEvent* st25tb_event = event.event_data; + + NfcCommand command = NfcCommandContinue; + + if(st25tb_event->type == St25tbPollerEventTypeRequestMode) { + st25tb_event->data->mode_request.mode = St25tbPollerModeRead; + } else if(st25tb_event->type == St25tbPollerEventTypeSuccess) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolSt25tb, nfc_poller_get_data(instance->poller)); + instance->result = NfcCliDumpErrorNone; + command = NfcCommandStop; + } else if(st25tb_event->type == St25tbPollerEventTypeFailure) { + instance->result = NfcCliDumpErrorFailedToRead; + command = NfcCommandStop; + } + + if(command == NfcCommandStop) { + furi_semaphore_release(instance->sem_done); + } + + return command; +} diff --git a/applications/main/nfc/cli/commands/dump/protocols/st25tb/nfc_cli_dump_st25tb.h b/applications/main/nfc/cli/commands/dump/protocols/st25tb/nfc_cli_dump_st25tb.h new file mode 100644 index 000000000..e1512864f --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/st25tb/nfc_cli_dump_st25tb.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_cli_dump_common_types.h" + +NfcCommand nfc_cli_dump_poller_callback_st25tb(NfcGenericEvent event, void* context); diff --git a/applications/main/nfc/cli/commands/helpers/nfc_cli_format.c b/applications/main/nfc/cli/commands/helpers/nfc_cli_format.c new file mode 100644 index 000000000..abe153938 --- /dev/null +++ b/applications/main/nfc/cli/commands/helpers/nfc_cli_format.c @@ -0,0 +1,61 @@ +#include "nfc_cli_format.h" + +static const char* protocol_names[NfcProtocolNum] = { + [NfcProtocolIso14443_3a] = "Iso14443-3a", + [NfcProtocolIso14443_3b] = "Iso14443-3b", + [NfcProtocolIso14443_4a] = "Iso14443-4a", + [NfcProtocolIso14443_4b] = "Iso14443-4b", + [NfcProtocolIso15693_3] = "Iso15693-3", + [NfcProtocolFelica] = "FeliCa", + [NfcProtocolMfUltralight] = "Mifare Ultralight", + [NfcProtocolMfClassic] = "Mifare Classic", + [NfcProtocolMfDesfire] = "Mifare DESFire", + [NfcProtocolMfPlus] = "Mifare Plus", + [NfcProtocolSlix] = "Slix", + [NfcProtocolSt25tb] = "St25tb", +}; + +const char* nfc_cli_get_protocol_name(NfcProtocol protocol) { + furi_assert(protocol < NfcProtocolNum); + return protocol_names[protocol]; +} + +static const char* mf_ultralight_error_names[] = { + [MfUltralightErrorNone] = "OK", + [MfUltralightErrorNotPresent] = "Card not present", + [MfUltralightErrorProtocol] = "Protocol failure", + [MfUltralightErrorAuth] = "Auth failed", + [MfUltralightErrorTimeout] = "Timeout", +}; + +const char* nfc_cli_mf_ultralight_get_error(MfUltralightError error) { + furi_assert(error < COUNT_OF(mf_ultralight_error_names)); + return mf_ultralight_error_names[error]; +} + +void nfc_cli_format_array( + const uint8_t* data, + const size_t data_size, + const char* header, + FuriString* output) { + furi_assert(data); + furi_assert(data_size > 0); + furi_assert(header); + furi_assert(output); + + furi_string_cat_printf(output, "%s", header); + for(size_t i = 0; i < data_size; i++) { + furi_string_cat_printf(output, "%02X ", data[i]); + } +} + +void nfc_cli_printf_array(const uint8_t* data, const size_t data_size, const char* header) { + furi_assert(data); + furi_assert(data_size > 0); + furi_assert(header); + + printf("%s", header); + for(size_t i = 0; i < data_size; i++) { + printf("%02X ", data[i]); + } +} diff --git a/applications/main/nfc/cli/commands/helpers/nfc_cli_format.h b/applications/main/nfc/cli/commands/helpers/nfc_cli_format.h new file mode 100644 index 000000000..5f9c2e574 --- /dev/null +++ b/applications/main/nfc/cli/commands/helpers/nfc_cli_format.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include + +const char* nfc_cli_get_protocol_name(NfcProtocol protocol); +const char* nfc_cli_mf_ultralight_get_error(MfUltralightError error); + +void nfc_cli_format_array( + const uint8_t* data, + const size_t data_size, + const char* header, + FuriString* output); + +void nfc_cli_printf_array(const uint8_t* data, const size_t data_size, const char* header); diff --git a/applications/main/nfc/cli/commands/helpers/nfc_cli_protocol_parser.c b/applications/main/nfc/cli/commands/helpers/nfc_cli_protocol_parser.c new file mode 100644 index 000000000..21a8d29b4 --- /dev/null +++ b/applications/main/nfc/cli/commands/helpers/nfc_cli_protocol_parser.c @@ -0,0 +1,58 @@ +#include "nfc_cli_protocol_parser.h" + +#include + +#define PROTOCOLS_TREE_RANK 4 + +BPTREE_DEF2( + ProtocolTree, + PROTOCOLS_TREE_RANK, + FuriString*, + FURI_STRING_OPLIST, + NfcProtocol, + M_POD_OPLIST); + +#define M_OPL_ProtocolTree_t() BPTREE_OPLIST(ProtocolTree, M_POD_OPLIST) + +struct NfcCliProtocolParser { + ProtocolTree_t protocols; +}; + +NfcCliProtocolParser* nfc_cli_protocol_parser_alloc( + const NfcProtocolNameValuePair* valid_protocols, + const size_t valid_count) { + furi_assert(valid_protocols); + furi_assert(valid_count > 0); + + NfcCliProtocolParser* instance = malloc(sizeof(NfcCliProtocolParser)); + + FuriString* name = furi_string_alloc(); + ProtocolTree_init(instance->protocols); + for(size_t i = 0; i < valid_count; i++) { + const NfcProtocolNameValuePair* item = &valid_protocols[i]; + furi_string_set_str(name, item->name); + ProtocolTree_set_at(instance->protocols, name, item->value); + } + + furi_string_free(name); + return instance; +} + +void nfc_cli_protocol_parser_free(NfcCliProtocolParser* instance) { + furi_assert(instance); + ProtocolTree_clear(instance->protocols); + free(instance); +} + +bool nfc_cli_protocol_parser_get( + NfcCliProtocolParser* instance, + FuriString* key, + NfcProtocol* result) { + furi_assert(instance); + furi_assert(key); + + NfcProtocol* protocol = ProtocolTree_get(instance->protocols, key); + if(protocol) *result = *protocol; + + return protocol != NULL; +} diff --git a/applications/main/nfc/cli/commands/helpers/nfc_cli_protocol_parser.h b/applications/main/nfc/cli/commands/helpers/nfc_cli_protocol_parser.h new file mode 100644 index 000000000..a87b26fe5 --- /dev/null +++ b/applications/main/nfc/cli/commands/helpers/nfc_cli_protocol_parser.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +typedef struct { + const char* name; + NfcProtocol value; +} NfcProtocolNameValuePair; + +typedef struct NfcCliProtocolParser NfcCliProtocolParser; + +NfcCliProtocolParser* nfc_cli_protocol_parser_alloc( + const NfcProtocolNameValuePair* valid_protocols, + const size_t valid_count); + +void nfc_cli_protocol_parser_free(NfcCliProtocolParser* instance); +bool nfc_cli_protocol_parser_get( + NfcCliProtocolParser* instance, + FuriString* key, + NfcProtocol* result); diff --git a/applications/main/nfc/cli/commands/helpers/nfc_cli_scanner.c b/applications/main/nfc/cli/commands/helpers/nfc_cli_scanner.c new file mode 100644 index 000000000..12d043cdc --- /dev/null +++ b/applications/main/nfc/cli/commands/helpers/nfc_cli_scanner.c @@ -0,0 +1,96 @@ +#include "nfc_cli_scanner.h" +#include +#include "nfc_cli_format.h" + +#define NFC_CLI_SCANNER_FLAG_DETECTED (1UL << 0) + +struct NfcCliScanner { + Nfc* nfc; + size_t protocols_detected_num; + NfcProtocol protocols_detected[NfcProtocolNum]; + FuriThreadId thread_id; + NfcScanner* scanner; +}; + +NfcCliScanner* nfc_cli_scanner_alloc(Nfc* nfc) { + NfcCliScanner* instance = malloc(sizeof(NfcCliScanner)); + instance->nfc = nfc; + instance->thread_id = furi_thread_get_current_id(); + return instance; +} + +void nfc_cli_scanner_free(NfcCliScanner* instance) { + furi_assert(instance); + free(instance); +} + +static void nfc_cli_scanner_detect_callback(NfcScannerEvent event, void* context) { + furi_assert(context); + NfcCliScanner* instance = context; + + if(event.type == NfcScannerEventTypeDetected) { + instance->protocols_detected_num = event.data.protocol_num; + memcpy( + instance->protocols_detected, + event.data.protocols, + event.data.protocol_num * sizeof(NfcProtocol)); + furi_thread_flags_set(instance->thread_id, NFC_CLI_SCANNER_FLAG_DETECTED); + } +} + +bool nfc_cli_scanner_detect_protocol(NfcCliScanner* instance, uint32_t timeout) { + instance->scanner = nfc_scanner_alloc(instance->nfc); + nfc_scanner_start(instance->scanner, nfc_cli_scanner_detect_callback, instance); + uint32_t event = + furi_thread_flags_wait(NFC_CLI_SCANNER_FLAG_DETECTED, FuriFlagWaitAny, timeout); + nfc_scanner_stop(instance->scanner); + nfc_scanner_free(instance->scanner); + return (event == NFC_CLI_SCANNER_FLAG_DETECTED); +} + +void nfc_cli_scanner_begin_scan(NfcCliScanner* instance) { + instance->scanner = nfc_scanner_alloc(instance->nfc); + nfc_scanner_start(instance->scanner, nfc_cli_scanner_detect_callback, instance); +} + +bool nfc_cli_scanner_wait_scan(NfcCliScanner* instance, uint32_t timeout) { + UNUSED(instance); + uint32_t event = + furi_thread_flags_wait(NFC_CLI_SCANNER_FLAG_DETECTED, FuriFlagWaitAny, timeout); + return (event == NFC_CLI_SCANNER_FLAG_DETECTED); +} + +void nfc_cli_scanner_end_scan(NfcCliScanner* instance) { + nfc_scanner_stop(instance->scanner); + nfc_scanner_free(instance->scanner); +} + +void nfc_cli_scanner_list_detected_protocols(NfcCliScanner* instance) { + printf("Protocols detected: "); + size_t n = instance->protocols_detected_num; + for(size_t i = 0; i < n; i++) { + const char* name = nfc_cli_get_protocol_name(instance->protocols_detected[i]); + printf((i == (n - 1)) ? "%s\r\n" : "%s, ", name); + } +} + +bool nfc_cli_scanner_protocol_was_detected(NfcCliScanner* instance, NfcProtocol protocol) { + furi_assert(instance); + furi_assert(protocol < NfcProtocolNum); + + for(size_t i = 0; i < instance->protocols_detected_num; i++) { + if(instance->protocols_detected[i] == protocol) return true; + } + return false; +} + +NfcProtocol nfc_cli_scanner_get_protocol(NfcCliScanner* instance, size_t idx) { + furi_assert(instance); + furi_assert(idx < instance->protocols_detected_num); + return instance->protocols_detected[idx]; +} + +size_t nfc_cli_scanner_detected_protocol_num(NfcCliScanner* instance) { + furi_assert(instance); + return instance->protocols_detected_num; +} diff --git a/applications/main/nfc/cli/commands/helpers/nfc_cli_scanner.h b/applications/main/nfc/cli/commands/helpers/nfc_cli_scanner.h new file mode 100644 index 000000000..9ca9a5705 --- /dev/null +++ b/applications/main/nfc/cli/commands/helpers/nfc_cli_scanner.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include + +typedef struct NfcCliScanner NfcCliScanner; + +NfcCliScanner* nfc_cli_scanner_alloc(Nfc* nfc); +void nfc_cli_scanner_free(NfcCliScanner* instance); + +bool nfc_cli_scanner_detect_protocol(NfcCliScanner* instance, uint32_t timeout); + +void nfc_cli_scanner_begin_scan(NfcCliScanner* instance); +bool nfc_cli_scanner_wait_scan(NfcCliScanner* instance, uint32_t timeout); +void nfc_cli_scanner_end_scan(NfcCliScanner* instance); + +void nfc_cli_scanner_list_detected_protocols(NfcCliScanner* instance); +size_t nfc_cli_scanner_detected_protocol_num(NfcCliScanner* instance); +bool nfc_cli_scanner_protocol_was_detected(NfcCliScanner* instance, NfcProtocol protocol); +NfcProtocol nfc_cli_scanner_get_protocol(NfcCliScanner* instance, size_t idx); diff --git a/applications/main/nfc/cli/commands/mfu/nfc_cli_action_info.c b/applications/main/nfc/cli/commands/mfu/nfc_cli_action_info.c new file mode 100644 index 000000000..3d3ad1125 --- /dev/null +++ b/applications/main/nfc/cli/commands/mfu/nfc_cli_action_info.c @@ -0,0 +1,227 @@ + +#include "nfc_cli_action_info.h" + +#include "../../../helpers/protocol_support/mf_ultralight/mf_ultralight_render.h" +#include "../helpers/nfc_cli_format.h" + +#include +#include +#include +#include + +#define TAG "INFO" + +typedef struct { + uint8_t magic; + union { + uint8_t value; + struct { + uint8_t minor : 4; + uint8_t major : 4; + }; + } version; + + uint8_t size; + + union { + uint8_t value; + struct { + uint8_t write : 4; + uint8_t read : 4; + }; + } access; +} FURI_PACKED MfUltralightCapabilityContainer; + +typedef struct { + Nfc* nfc; + MfUltralightData* data; +} NfcCliMfuContext; + +static void nfc_cli_mfu_info_get_vendor(const uint8_t vendor_key, FuriString* output) { + furi_assert(output); + + FuriString* buf = furi_string_alloc(); + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* ff = flipper_format_file_alloc(storage); + + do { + if(!flipper_format_file_open_existing(ff, EXT_PATH("nfc/assets/vendors.nfc"))) { + FURI_LOG_W(TAG, "NFC Vendors dict not found"); + break; + } + + char uid_str[5]; + snprintf(uid_str, sizeof(uid_str), "%d", vendor_key); + + if(flipper_format_read_string(ff, uid_str, buf)) + furi_string_printf(output, "%s, %s", uid_str, furi_string_get_cstr(buf)); + else + furi_string_printf(output, "unknown"); + } while(false); + + flipper_format_free(ff); + furi_record_close(RECORD_STORAGE); + furi_string_free(buf); +} + +const char* + nfc_cli_mfu_capability_container_get_access_description(const uint8_t value, bool read) { + const char* description = "RFU"; //value 0x01 - 0x07, and 0xF when read + if(value == 0x00) + description = "access fully granted"; + else if(value >= 0x08 && value <= 0x0E) + description = "proprietary"; + else if(value == 0x0F && !read) + description = "no access granted at all"; + + return description; +} + +static void nfc_cli_mfu_info_print_common(const MfUltralightData* data) { + FuriString* str = furi_string_alloc(); + + printf(ANSI_FG_GREEN "\r\n\tTag information\r\n" ANSI_RESET); + printf( + "Type: " ANSI_FG_YELLOW "%s\r\n" ANSI_RESET, + mf_ultralight_get_device_name(data, NfcDeviceNameTypeFull)); + + nfc_cli_mfu_info_get_vendor(data->iso14443_3a_data->uid[0], str); + printf("Vendor ID: %s\r\n", furi_string_get_cstr(str)); + + furi_string_reset(str); + nfc_render_mf_ultralight_info(data, NfcProtocolFormatTypeFull, str); + printf("%s\r\n", furi_string_get_cstr(str)); + printf("BCC0: %02X\r\nBCC1: %02X\r\n", data->page[0].data[3], data->page[2].data[0]); + + furi_string_free(str); +} + +static void nfc_cli_mfu_info_print_ndef(const MfUltralightData* data) { + const MfUltralightCapabilityContainer* cc = + (const MfUltralightCapabilityContainer*)data->page[3].data; + if(cc->magic == 0xE1) { + printf(ANSI_FG_GREEN "\r\n\tNDEF Message\r\n" ANSI_RESET); + nfc_cli_printf_array(data->page[3].data, 4, "Capability container: "); + printf( + "\r\nMagic number: %02X\r\nVersion %d.%d\r\nSize: [%02X] - %d bytes\r\n", + cc->magic, + cc->version.major, + cc->version.minor, + cc->size, + cc->size * 8); + printf( + "Access read: [%02X] - %s", + cc->access.read, + nfc_cli_mfu_capability_container_get_access_description(cc->access.read, true)); + printf( + "Access write: [%02X] - %s", + cc->access.write, + nfc_cli_mfu_capability_container_get_access_description(cc->access.write, false)); + } +} + +static void nfc_cli_mfu_info_print_counter(const MfUltralightData* data) { + uint32_t features = mf_ultralight_get_feature_support_set(data->type); + if(!mf_ultralight_support_feature(features, MfUltralightFeatureSupportReadCounter)) return; + + printf(ANSI_FG_GREEN "\r\n\n\tTag counters\r\n" ANSI_RESET); + uint8_t i = + mf_ultralight_support_feature(features, MfUltralightFeatureSupportSingleCounter) ? 2 : 0; + + for(; i < MF_ULTRALIGHT_COUNTER_NUM; i++) { + printf("Counter [%d]: ", i); + nfc_cli_printf_array(data->counter[i].data, MF_ULTRALIGHT_COUNTER_SIZE, ""); + printf(" Value: %lu\r\n", data->counter[i].counter); + + const uint8_t tf = data->tearing_flag[i].data; + printf( + "Tearing [%d]: [%02X] %s", + i, + tf, + tf == MF_ULTRALIGHT_TEARING_FLAG_DEFAULT ? "(ok)" : ""); + } +} + +static void nfc_cli_mfu_info_print_signature(const MfUltralightData* data) { + uint32_t features = mf_ultralight_get_feature_support_set(data->type); + if(!mf_ultralight_support_feature(features, MfUltralightFeatureSupportReadSignature)) return; + + const MfUltralightSignature* signature = &data->signature; + printf(ANSI_FG_GREEN "\r\n\n\tTag signature\r\n" ANSI_RESET); + nfc_cli_printf_array(signature->data, sizeof(signature->data), "ECC signature: "); +} + +static void nfc_cli_mfu_info_print_version_storage_size(uint8_t storage_size) { + uint16_t max_size = 1 << ((storage_size >> 1) + 1); + uint16_t min_exact_size = 1 << (storage_size >> 1); + + bool exact_size = !(storage_size & 0x01); + if(exact_size) + printf("[%02X], (%u bytes)", storage_size, min_exact_size); + else + printf("[%02X], (%u <-> %u bytes)", storage_size, min_exact_size, max_size); +} + +static void nfc_cli_mfu_info_print_version(const MfUltralightData* data) { + uint32_t features = mf_ultralight_get_feature_support_set(data->type); + if(!mf_ultralight_support_feature(features, MfUltralightFeatureSupportReadVersion)) return; + + const MfUltralightVersion* version = &data->version; + printf(ANSI_FG_GREEN "\r\n\n\tTag Version\r\n" ANSI_RESET); + nfc_cli_printf_array((uint8_t*)version, sizeof(MfUltralightVersion), "Raw bytes: "); + + FuriString* str = furi_string_alloc(); + nfc_cli_mfu_info_get_vendor(version->vendor_id, str); + printf("\r\nVendor ID: %s\r\n", furi_string_get_cstr(str)); + furi_string_free(str); + + printf("Product type: %02X\r\n", version->prod_type); + + printf( + "Protocol type: %02X%s\r\n", + version->protocol_type, + (version->protocol_type == 0x3) ? ", ISO14443-3 Compliant" : ""); + + printf( + "Product subtype: [%02X], %s\r\n", + version->prod_subtype, + (version->prod_subtype == 1) ? "17 pF" : "50pF"); + printf( + "Major version: %02X\r\nMinor version: %02X\r\nSize: ", + version->prod_ver_major, + version->prod_ver_minor); + nfc_cli_mfu_info_print_version_storage_size(version->storage_size); +} + +NfcCliActionContext* nfc_cli_mfu_info_alloc_ctx(Nfc* nfc) { + NfcCliMfuContext* instance = malloc(sizeof(NfcCliMfuContext)); + instance->nfc = nfc; + instance->data = mf_ultralight_alloc(); + return instance; +} + +void nfc_cli_mfu_info_free_ctx(NfcCliActionContext* ctx) { + NfcCliMfuContext* instance = ctx; + mf_ultralight_free(instance->data); + free(instance); +} + +void nfc_cli_mfu_info_execute(PipeSide* pipe, NfcCliActionContext* ctx) { + furi_assert(pipe); + furi_assert(ctx); + + NfcCliMfuContext* instance = ctx; + + MfUltralightError error = + mf_ultralight_poller_sync_read_card(instance->nfc, instance->data, NULL); + if(error == MfUltralightErrorNone) { + const MfUltralightData* data = instance->data; + nfc_cli_mfu_info_print_common(data); + nfc_cli_mfu_info_print_ndef(data); + nfc_cli_mfu_info_print_counter(data); + nfc_cli_mfu_info_print_signature(data); + nfc_cli_mfu_info_print_version(data); + } else { + printf(ANSI_FG_RED "Error: %s" ANSI_RESET, nfc_cli_mf_ultralight_get_error(error)); + } +} diff --git a/applications/main/nfc/cli/commands/mfu/nfc_cli_action_info.h b/applications/main/nfc/cli/commands/mfu/nfc_cli_action_info.h new file mode 100644 index 000000000..998dd8def --- /dev/null +++ b/applications/main/nfc/cli/commands/mfu/nfc_cli_action_info.h @@ -0,0 +1,7 @@ +#pragma once + +#include "../../nfc_cli_command_base_i.h" + +NfcCliActionContext* nfc_cli_mfu_info_alloc_ctx(Nfc* nfc); +void nfc_cli_mfu_info_free_ctx(NfcCliActionContext* ctx); +void nfc_cli_mfu_info_execute(PipeSide* pipe, NfcCliActionContext* ctx); diff --git a/applications/main/nfc/cli/commands/mfu/nfc_cli_action_rdbl.c b/applications/main/nfc/cli/commands/mfu/nfc_cli_action_rdbl.c new file mode 100644 index 000000000..d8a43ab71 --- /dev/null +++ b/applications/main/nfc/cli/commands/mfu/nfc_cli_action_rdbl.c @@ -0,0 +1,52 @@ +#include "nfc_cli_action_rdbl.h" + +#include "../helpers/nfc_cli_format.h" + +#include +#include +#include + +#define MF_ULTRALIGHT_POLLER_COMPLETE_EVENT (1UL << 0) + +typedef struct { + Nfc* nfc; + uint16_t block; +} NfcCliMfuRdblContext; + +NfcCliActionContext* nfc_cli_mfu_rdbl_alloc_ctx(Nfc* nfc) { + furi_assert(nfc); + NfcCliMfuRdblContext* instance = malloc(sizeof(NfcCliMfuRdblContext)); + instance->nfc = nfc; + return instance; +} + +void nfc_cli_mfu_rdbl_free_ctx(NfcCliActionContext* ctx) { + furi_assert(ctx); + NfcCliMfuRdblContext* instance = ctx; + free(instance); +} + +void nfc_cli_mfu_rdbl_execute(PipeSide* pipe, NfcCliActionContext* ctx) { + furi_assert(pipe); + + NfcCliMfuRdblContext* instance = ctx; + MfUltralightPage page = {0}; + + MfUltralightError error = + mf_ultralight_poller_sync_read_page(instance->nfc, instance->block, &page); + + if(error == MfUltralightErrorNone) { + printf("\r\nBlock: %d ", instance->block); + nfc_cli_printf_array(page.data, sizeof(MfUltralightPage), "Data: "); + printf("\r\n"); + } else { + printf(ANSI_FG_RED "Error: %s" ANSI_RESET, nfc_cli_mf_ultralight_get_error(error)); + } +} + +bool nfc_cli_mfu_rdbl_parse_block(FuriString* value, NfcCliActionContext* output) { + NfcCliMfuRdblContext* ctx = output; + + StrintParseError err = strint_to_uint16(furi_string_get_cstr(value), NULL, &ctx->block, 10); + return err == StrintParseNoError; +} diff --git a/applications/main/nfc/cli/commands/mfu/nfc_cli_action_rdbl.h b/applications/main/nfc/cli/commands/mfu/nfc_cli_action_rdbl.h new file mode 100644 index 000000000..ec7e19c2d --- /dev/null +++ b/applications/main/nfc/cli/commands/mfu/nfc_cli_action_rdbl.h @@ -0,0 +1,8 @@ +#pragma once + +#include "../../nfc_cli_command_base_i.h" + +NfcCliActionContext* nfc_cli_mfu_rdbl_alloc_ctx(Nfc* nfc); +void nfc_cli_mfu_rdbl_free_ctx(NfcCliActionContext* ctx); +void nfc_cli_mfu_rdbl_execute(PipeSide* pipe, NfcCliActionContext* ctx); +bool nfc_cli_mfu_rdbl_parse_block(FuriString* value, NfcCliActionContext* ctx); diff --git a/applications/main/nfc/cli/commands/mfu/nfc_cli_action_wrbl.c b/applications/main/nfc/cli/commands/mfu/nfc_cli_action_wrbl.c new file mode 100644 index 000000000..bdc20557c --- /dev/null +++ b/applications/main/nfc/cli/commands/mfu/nfc_cli_action_wrbl.c @@ -0,0 +1,71 @@ +#include "nfc_cli_action_rdbl.h" + +#include "../helpers/nfc_cli_format.h" + +#include +#include +#include +#include + +#define MF_ULTRALIGHT_POLLER_COMPLETE_EVENT (1UL << 0) + +typedef struct { + Nfc* nfc; + uint16_t block; + MfUltralightPage page; +} NfcCliMfuWrblContext; + +NfcCliActionContext* nfc_cli_mfu_wrbl_alloc_ctx(Nfc* nfc) { + furi_assert(nfc); + NfcCliMfuWrblContext* instance = malloc(sizeof(NfcCliMfuWrblContext)); + instance->nfc = nfc; + return instance; +} + +void nfc_cli_mfu_wrbl_free_ctx(NfcCliActionContext* ctx) { + furi_assert(ctx); + NfcCliMfuWrblContext* instance = ctx; + free(instance); +} + +void nfc_cli_mfu_wrbl_execute(PipeSide* pipe, NfcCliActionContext* ctx) { + furi_assert(pipe); + + NfcCliMfuWrblContext* instance = ctx; + + MfUltralightError error = + mf_ultralight_poller_sync_write_page(instance->nfc, instance->block, &instance->page); + + if(error == MfUltralightErrorNone) { + printf(ANSI_FG_BR_GREEN "\r\nSuccess\r\n" ANSI_RESET); + printf("Block: %d ", instance->block); + nfc_cli_printf_array(instance->page.data, sizeof(MfUltralightPage), "Data: "); + printf("\r\n"); + } else { + printf(ANSI_FG_RED "Error: %s" ANSI_RESET, nfc_cli_mf_ultralight_get_error(error)); + } +} + +bool nfc_cli_mfu_wrbl_parse_block(FuriString* value, NfcCliActionContext* output) { + NfcCliMfuWrblContext* ctx = output; + + StrintParseError err = strint_to_uint16(furi_string_get_cstr(value), NULL, &ctx->block, 10); + return err == StrintParseNoError; +} + +bool nfc_cli_mfu_wrbl_parse_data(FuriString* value, void* output) { + NfcCliMfuWrblContext* ctx = output; + + bool result = false; + do { + size_t len = furi_string_size(value); + if(len % 2 != 0) break; + + size_t data_length = len / 2; + if(data_length != MF_ULTRALIGHT_PAGE_SIZE) break; + + result = args_read_hex_bytes(value, ctx->page.data, data_length); + } while(false); + + return result; +} diff --git a/applications/main/nfc/cli/commands/mfu/nfc_cli_action_wrbl.h b/applications/main/nfc/cli/commands/mfu/nfc_cli_action_wrbl.h new file mode 100644 index 000000000..abddd367f --- /dev/null +++ b/applications/main/nfc/cli/commands/mfu/nfc_cli_action_wrbl.h @@ -0,0 +1,9 @@ +#pragma once + +#include "../../nfc_cli_command_base_i.h" + +NfcCliActionContext* nfc_cli_mfu_wrbl_alloc_ctx(Nfc* nfc); +void nfc_cli_mfu_wrbl_free_ctx(NfcCliActionContext* ctx); +void nfc_cli_mfu_wrbl_execute(PipeSide* pipe, NfcCliActionContext* ctx); +bool nfc_cli_mfu_wrbl_parse_block(FuriString* value, NfcCliActionContext* ctx); +bool nfc_cli_mfu_wrbl_parse_data(FuriString* value, void* output); diff --git a/applications/main/nfc/cli/commands/mfu/nfc_cli_command_mfu.c b/applications/main/nfc/cli/commands/mfu/nfc_cli_command_mfu.c new file mode 100644 index 000000000..0bfcffe3c --- /dev/null +++ b/applications/main/nfc/cli/commands/mfu/nfc_cli_command_mfu.c @@ -0,0 +1,77 @@ +#include "nfc_cli_command_mfu.h" +#include "nfc_cli_action_info.h" +#include "nfc_cli_action_rdbl.h" +#include "nfc_cli_action_wrbl.h" + +#define TAG "MFU" + +//mfu info +const NfcCliActionDescriptor info_action = { + .name = "info", + .description = "Get basic information about the card", + .alloc = nfc_cli_mfu_info_alloc_ctx, + .free = nfc_cli_mfu_info_free_ctx, + .execute = nfc_cli_mfu_info_execute, + .key_count = 0, + .keys = NULL, +}; + +const NfcCliKeyDescriptor rdbl_action_keys[] = { + { + .short_name = "b", + .long_name = "block", + .features = {.required = true, .parameter = true}, + .description = "desired block number", + .parse = nfc_cli_mfu_rdbl_parse_block, + }, +}; + +//mfu rdbl -b 0 +//mfu rdbl --block 0 +const NfcCliActionDescriptor rdbl_action = { + .name = "rdbl", + .description = "Read block from ultralight card", + .alloc = nfc_cli_mfu_rdbl_alloc_ctx, + .free = nfc_cli_mfu_rdbl_free_ctx, + .execute = nfc_cli_mfu_rdbl_execute, + .key_count = COUNT_OF(rdbl_action_keys), + .keys = rdbl_action_keys, +}; + +const NfcCliKeyDescriptor wrbl_action_keys[] = { + { + .short_name = "b", + .long_name = "block", + .features = {.required = true, .parameter = true}, + .description = "desired block number", + .parse = nfc_cli_mfu_wrbl_parse_block, + }, + { + .short_name = "d", + .long_name = "data", + .features = {.required = true, .parameter = true}, + .description = "new data for block", + .parse = nfc_cli_mfu_wrbl_parse_data, + }, +}; + +//mfu wrbl -b 0 -d DEADBEAF +//mfu rdbl --block 0 -- data DEADBEEF +const NfcCliActionDescriptor wrbl_action = { + .name = "wrbl", + .description = "Read block from ultralight card", + .alloc = nfc_cli_mfu_wrbl_alloc_ctx, + .free = nfc_cli_mfu_wrbl_free_ctx, + .execute = nfc_cli_mfu_wrbl_execute, + .key_count = COUNT_OF(wrbl_action_keys), + .keys = wrbl_action_keys, +}; + +const NfcCliActionDescriptor* mfu_actions[] = { + &rdbl_action, + &info_action, + &wrbl_action, +}; + +//Command descriptor +ADD_NFC_CLI_COMMAND(mfu, "Mifare Ultralight specific commands", mfu_actions); diff --git a/applications/main/nfc/cli/commands/mfu/nfc_cli_command_mfu.h b/applications/main/nfc/cli/commands/mfu/nfc_cli_command_mfu.h new file mode 100644 index 000000000..a0530682c --- /dev/null +++ b/applications/main/nfc/cli/commands/mfu/nfc_cli_command_mfu.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../../nfc_cli_command_base_i.h" + +extern const NfcCliCommandDescriptor mfu_cmd; diff --git a/applications/main/nfc/cli/commands/nfc_cli_command_emulate.c b/applications/main/nfc/cli/commands/nfc_cli_command_emulate.c new file mode 100644 index 000000000..60ff62cc5 --- /dev/null +++ b/applications/main/nfc/cli/commands/nfc_cli_command_emulate.c @@ -0,0 +1,126 @@ +#include "nfc_cli_command_emulate.h" +#include "helpers/nfc_cli_format.h" + +#include +#include +#include + +#include + +typedef struct { + Nfc* nfc; + NfcDevice* nfc_device; + FuriString* file_path; + Storage* storage; +} NfcCliEmulateContext; + +static NfcCliActionContext* nfc_cli_emulate_alloc_ctx(Nfc* nfc) { + furi_assert(nfc); + NfcCliEmulateContext* instance = malloc(sizeof(NfcCliEmulateContext)); + instance->nfc = nfc; + instance->file_path = furi_string_alloc(); + instance->nfc_device = nfc_device_alloc(); + instance->storage = furi_record_open(RECORD_STORAGE); + return instance; +} + +static void nfc_cli_emulate_free_ctx(NfcCliActionContext* ctx) { + furi_assert(ctx); + NfcCliEmulateContext* instance = ctx; + furi_record_close(RECORD_STORAGE); + furi_string_free(instance->file_path); + nfc_device_free(instance->nfc_device); + free(instance); +} + +static const NfcProtocol supported_protocols[] = { + NfcProtocolIso14443_3a, + NfcProtocolIso14443_4a, + NfcProtocolIso15693_3, + NfcProtocolMfUltralight, + NfcProtocolMfClassic, + NfcProtocolSlix, + NfcProtocolFelica, +}; + +static bool nfc_cli_emulate_protocol_supports_emulation(NfcProtocol protocol) { + for(size_t i = 0; i < COUNT_OF(supported_protocols); i++) { + if(supported_protocols[i] == protocol) return true; + } + return false; +} + +static void nfc_cli_emulate_execute(PipeSide* pipe, NfcCliActionContext* context) { + UNUSED(pipe); + furi_assert(context); + NfcCliEmulateContext* instance = context; + do { + const char* path = furi_string_get_cstr(instance->file_path); + if(!storage_common_exists(instance->storage, path)) { + printf(ANSI_FG_RED "Wrong path \'%s\'.\r\n" ANSI_RESET, path); + break; + } + + if(!nfc_device_load(instance->nfc_device, path)) { + printf(ANSI_FG_RED "Failed to load \'%s\'.\r\n" ANSI_RESET, path); + break; + } + + const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); + + if(!nfc_cli_emulate_protocol_supports_emulation(protocol)) { + printf( + ANSI_FG_RED "Error. Emulation for %s is not supported\r\n" ANSI_RESET, + nfc_cli_get_protocol_name(protocol)); + break; + } + + const NfcDeviceData* data = nfc_device_get_data(instance->nfc_device, protocol); + NfcListener* listener = nfc_listener_alloc(instance->nfc, protocol, data); + + nfc_listener_start(listener, NULL, NULL); + printf("\r\nEmulating. Press Ctrl+C to abort\r\n"); + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { + furi_delay_ms(100); + } + nfc_listener_stop(listener); + nfc_listener_free(listener); + } while(false); +} + +static bool nfc_cli_emulate_parse_filename_key(FuriString* value, void* output) { + furi_assert(value); + furi_assert(output); + NfcCliEmulateContext* ctx = output; + furi_string_set(ctx->file_path, value); + return true; +} + +const NfcCliKeyDescriptor emulate_keys[] = { + { + .features = {.required = true, .parameter = true}, + .long_name = "file", + .short_name = "f", + .description = "path to new file", + .parse = nfc_cli_emulate_parse_filename_key, + }, +}; + +const NfcCliActionDescriptor emulate_action = { + .name = "emulate", + .description = "Emulate .nfc file content", + .alloc = nfc_cli_emulate_alloc_ctx, + .free = nfc_cli_emulate_free_ctx, + .execute = nfc_cli_emulate_execute, + .key_count = COUNT_OF(emulate_keys), + .keys = emulate_keys, +}; + +const NfcCliActionDescriptor* emulate_actions_collection[] = {&emulate_action}; + +//Command descriptor +ADD_NFC_CLI_COMMAND(emulate, "", emulate_actions_collection); + +//Command usage: emulate [-f ] +//Command examples: +//emulate -f ext/nfc/test.nfc diff --git a/applications/main/nfc/cli/commands/nfc_cli_command_emulate.h b/applications/main/nfc/cli/commands/nfc_cli_command_emulate.h new file mode 100644 index 000000000..ff1ae5b1a --- /dev/null +++ b/applications/main/nfc/cli/commands/nfc_cli_command_emulate.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_cli_command_base_i.h" + +extern const NfcCliCommandDescriptor emulate_cmd; diff --git a/applications/main/nfc/cli/commands/nfc_cli_command_field.c b/applications/main/nfc/cli/commands/nfc_cli_command_field.c new file mode 100644 index 000000000..753ba451e --- /dev/null +++ b/applications/main/nfc/cli/commands/nfc_cli_command_field.c @@ -0,0 +1,28 @@ +#include "nfc_cli_command_field.h" + +#include + +static void nfc_cli_field(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(args); + UNUSED(context); + + furi_hal_nfc_low_power_mode_stop(); + furi_hal_nfc_poller_field_on(); + + printf("Field is on. Don't leave device in this mode for too long.\r\n"); + printf("Press Ctrl+C to abort\r\n"); + + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { + furi_delay_ms(50); + } + + furi_hal_nfc_low_power_mode_start(); +} + +const NfcCliCommandDescriptor field_cmd = { + .name = "field", + .description = "Turns NFC field on", + .callback = nfc_cli_field, + .action_count = 0, + .actions = NULL, +}; diff --git a/applications/main/nfc/cli/commands/nfc_cli_command_field.h b/applications/main/nfc/cli/commands/nfc_cli_command_field.h new file mode 100644 index 000000000..b96ef3b5a --- /dev/null +++ b/applications/main/nfc/cli/commands/nfc_cli_command_field.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_cli_command_base_i.h" + +extern const NfcCliCommandDescriptor field_cmd; diff --git a/applications/main/nfc/cli/commands/nfc_cli_command_scanner.c b/applications/main/nfc/cli/commands/nfc_cli_command_scanner.c new file mode 100644 index 000000000..fec8d72c1 --- /dev/null +++ b/applications/main/nfc/cli/commands/nfc_cli_command_scanner.c @@ -0,0 +1,97 @@ + +#include "nfc_cli_command_scanner.h" +#include "helpers/nfc_cli_scanner.h" +#include "helpers/nfc_cli_format.h" + +typedef struct { + NfcCliScanner* scanner; + bool display_tree; +} NfcCliCmdScannerContext; + +static NfcCliActionContext* nfc_cli_command_scanner_alloc_ctx(Nfc* nfc) { + furi_assert(nfc); + NfcCliCmdScannerContext* instance = malloc(sizeof(NfcCliCmdScannerContext)); + instance->scanner = nfc_cli_scanner_alloc(nfc); + instance->display_tree = false; + + return instance; +} + +static void nfc_cli_command_scanner_free_ctx(NfcCliActionContext* ctx) { + furi_assert(ctx); + NfcCliCmdScannerContext* instance = ctx; + nfc_cli_scanner_free(instance->scanner); + + free(instance); +} + +static void + nfc_cli_command_scanner_format_protocol_tree(NfcProtocol protocol, FuriString* output) { + const char* names[10] = {0}; + uint8_t cnt = 0; + while(protocol != NfcProtocolInvalid) { + names[cnt++] = nfc_cli_get_protocol_name(protocol); + protocol = nfc_protocol_get_parent(protocol); + } + + for(int8_t i = cnt - 1; i >= 0; i--) { + furi_string_cat_printf(output, (i == 0) ? "%s" : "%s -> ", names[i]); + } +} + +static void nfc_cli_command_scanner_format_detected_protocols(NfcCliScanner* instance) { + FuriString* str = furi_string_alloc(); + printf("Protocols detected: \r\n"); + for(size_t i = 0; i < nfc_cli_scanner_detected_protocol_num(instance); i++) { + furi_string_reset(str); + NfcProtocol protocol = nfc_cli_scanner_get_protocol(instance, i); + nfc_cli_command_scanner_format_protocol_tree(protocol, str); + printf("Protocol [%zu]: %s\r\n", i + 1, furi_string_get_cstr(str)); + } + furi_string_free(str); +} + +static void nfc_cli_command_scanner_execute(PipeSide* pipe, void* context) { + NfcCliCmdScannerContext* instance = context; + + printf("Press Ctrl+C to abort\r\n\n"); + nfc_cli_scanner_begin_scan(instance->scanner); + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe) && + !nfc_cli_scanner_wait_scan(instance->scanner, 50)) + ; + nfc_cli_scanner_end_scan(instance->scanner); + + if(!instance->display_tree) + nfc_cli_scanner_list_detected_protocols(instance->scanner); + else + nfc_cli_command_scanner_format_detected_protocols(instance->scanner); +} + +static bool nfc_cli_command_scanner_parse_tree(FuriString* value, void* output) { + UNUSED(value); + NfcCliCmdScannerContext* ctx = output; + ctx->display_tree = true; + return true; +} + +const NfcCliKeyDescriptor tree_key = { + .short_name = "t", + .long_name = "tree", + .features = {.parameter = false, .required = false, .multivalue = false}, + .description = "displays protocol hierarchy for each detected protocol", + .parse = nfc_cli_command_scanner_parse_tree, +}; + +const NfcCliActionDescriptor scanner_action = { + .name = "scanner", + .description = "Detect tag type", + .key_count = 1, + .keys = &tree_key, + .execute = nfc_cli_command_scanner_execute, + .alloc = nfc_cli_command_scanner_alloc_ctx, + .free = nfc_cli_command_scanner_free_ctx, +}; + +const NfcCliActionDescriptor* scanner_actions_collection[] = {&scanner_action}; + +ADD_NFC_CLI_COMMAND(scanner, "", scanner_actions_collection); diff --git a/applications/main/nfc/cli/commands/nfc_cli_command_scanner.h b/applications/main/nfc/cli/commands/nfc_cli_command_scanner.h new file mode 100644 index 000000000..e41958859 --- /dev/null +++ b/applications/main/nfc/cli/commands/nfc_cli_command_scanner.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_cli_command_base_i.h" + +extern const NfcCliCommandDescriptor scanner_cmd; diff --git a/applications/main/nfc/cli/commands/raw/nfc_cli_command_raw.c b/applications/main/nfc/cli/commands/raw/nfc_cli_command_raw.c new file mode 100644 index 000000000..f90adb4c6 --- /dev/null +++ b/applications/main/nfc/cli/commands/raw/nfc_cli_command_raw.c @@ -0,0 +1,352 @@ +#include "nfc_cli_command_raw.h" +#include "../helpers/nfc_cli_format.h" +#include "../helpers/nfc_cli_protocol_parser.h" + +#include "protocol_handlers/nfc_cli_raw_common_types.h" +#include "protocol_handlers/iso14443_3a/nfc_cli_raw_iso14443_3a.h" +#include "protocol_handlers/iso14443_3b/nfc_cli_raw_iso14443_3b.h" +#include "protocol_handlers/iso15693_3/nfc_cli_raw_iso15693_3.h" +#include "protocol_handlers/felica/nfc_cli_raw_felica.h" + +#include + +#define NFC_CLI_PROTOCOL_SUPPORT_MAX_BUFFER_SIZE (256) + +#define TAG "RAW" + +typedef enum { + NfcCliProtocolRequestTypeNormalExecute, + NfcCliProtocolRequestTypeAbort, +} NfcCliProtocolRequestType; + +typedef enum { + NfcPollerStateStopped, + NfcPollerStateStarted, +} NfcPollerState; + +typedef NfcCommand (*NfcCliRawProtocolSpecificHandler)( + NfcGenericInstance* poller, + const NfcCliRawRequest* request, + NfcCliRawResponse* const response); + +typedef struct { + Nfc* nfc; + NfcCliRawRequest request; + NfcCliRawResponse response; + + NfcPoller* poller; + NfcPollerState poller_state; + + NfcCliProtocolRequestType request_type; + FuriMessageQueue* input_queue; + FuriSemaphore* sem_done; + +} NfcCliRawCmdContext; + +static const char* raw_error_names[] = { + [NfcCliRawErrorNone] = "None", + [NfcCliRawErrorTimeout] = "Timeout", + [NfcCliRawErrorProtocol] = "Internal protocol", + [NfcCliRawErrorWrongCrc] = "Wrong CRC", + [NfcCliRawErrorNotPresent] = "No card", +}; + +static NfcCliActionContext* nfc_cli_raw_alloc_ctx(Nfc* nfc) { + furi_assert(nfc); + NfcCliRawCmdContext* instance = malloc(sizeof(NfcCliRawCmdContext)); + instance->nfc = nfc; + + instance->request.protocol = NfcProtocolInvalid; + + instance->request.tx_buffer = bit_buffer_alloc(NFC_CLI_PROTOCOL_SUPPORT_MAX_BUFFER_SIZE); + instance->response.rx_buffer = bit_buffer_alloc(NFC_CLI_PROTOCOL_SUPPORT_MAX_BUFFER_SIZE); + + instance->input_queue = furi_message_queue_alloc(5, sizeof(NfcCliProtocolRequestType)); + instance->sem_done = furi_semaphore_alloc(1, 0); + instance->response.activation_string = furi_string_alloc(); + instance->request.timeout = 0; + return instance; +} + +static void nfc_cli_raw_abort_nfc_thread(NfcCliRawCmdContext* instance) { + if(instance->poller_state == NfcPollerStateStarted) { + instance->request_type = NfcCliProtocolRequestTypeAbort; + furi_message_queue_put(instance->input_queue, &instance->request_type, FuriWaitForever); + furi_semaphore_acquire(instance->sem_done, FuriWaitForever); + instance->poller_state = NfcPollerStateStopped; + } + if(instance->poller) nfc_poller_stop(instance->poller); +} + +static void nfc_cli_raw_free_ctx(NfcCliActionContext* ctx) { + furi_assert(ctx); + NfcCliRawCmdContext* instance = ctx; + + nfc_cli_raw_abort_nfc_thread(instance); + if(instance->poller) nfc_poller_free(instance->poller); + + furi_message_queue_free(instance->input_queue); + furi_semaphore_free(instance->sem_done); + + furi_string_free(instance->response.activation_string); + bit_buffer_free(instance->response.rx_buffer); + bit_buffer_free(instance->request.tx_buffer); + instance->nfc = NULL; + free(instance); +} + +static bool nfc_cli_raw_can_reuse_ctx(NfcCliActionContext* ctx) { + furi_assert(ctx); + NfcCliRawCmdContext* instance = ctx; + NfcCliRawRequest* request = &instance->request; + + bool result = request->keep_field; + request->keep_field = false; + request->append_crc = false; + request->select = false; + instance->request.timeout = 0; + return result; +} + +const NfcCliRawProtocolSpecificHandler nfc_cli_raw_protocol_handlers[] = { + [NfcProtocolIso14443_3a] = nfc_cli_raw_iso14443_3a_handler, + [NfcProtocolIso14443_3b] = nfc_cli_raw_iso14443_3b_handler, + [NfcProtocolIso14443_4a] = NULL, + [NfcProtocolIso14443_4b] = NULL, + [NfcProtocolIso15693_3] = nfc_cli_raw_iso15693_3_handler, + [NfcProtocolFelica] = nfc_cli_raw_felica_handler, + [NfcProtocolMfUltralight] = NULL, + [NfcProtocolMfClassic] = NULL, + [NfcProtocolMfDesfire] = NULL, + [NfcProtocolSlix] = NULL, + [NfcProtocolSt25tb] = NULL, +}; + +static NfcCommand nfc_cli_raw_poller_callback(NfcGenericEventEx event, void* context) { + NfcEvent* nfc_event = event.parent_event_data; + NfcCliRawCmdContext* instance = context; + + NfcCommand command = NfcCommandContinue; + + if(nfc_event->type == NfcEventTypePollerReady) { + FURI_LOG_D(TAG, "Poller callback"); + NfcCliProtocolRequestType request_type = NfcCliProtocolRequestTypeAbort; + furi_message_queue_get(instance->input_queue, &request_type, FuriWaitForever); + + if(request_type == NfcCliProtocolRequestTypeAbort) { + command = NfcCommandStop; + } else { + const NfcCliRawProtocolSpecificHandler handler = + nfc_cli_raw_protocol_handlers[instance->request.protocol]; + if(handler) handler(event.poller, &instance->request, &instance->response); + } + } + furi_semaphore_release(instance->sem_done); + if(command == NfcCommandStop) { + FURI_LOG_D(TAG, "Aborting poller callback"); + instance->poller_state = NfcPollerStateStopped; + } + return command; +} + +static inline void nfc_cli_raw_print_result(const NfcCliRawCmdContext* instance) { + if(!furi_string_empty(instance->response.activation_string)) + printf("%s\r\n", furi_string_get_cstr(instance->response.activation_string)); + + nfc_cli_printf_array( + bit_buffer_get_data(instance->request.tx_buffer), + bit_buffer_get_size_bytes(instance->request.tx_buffer), + "Tx: "); + + if(instance->response.result != NfcCliRawErrorNone) + printf("\r\nError: \"%s\"\r\n", raw_error_names[instance->response.result]); + + size_t rx_size = bit_buffer_get_size_bytes(instance->response.rx_buffer); + if(rx_size > 0) { + nfc_cli_printf_array( + bit_buffer_get_data(instance->response.rx_buffer), + bit_buffer_get_size_bytes(instance->response.rx_buffer), + "\r\nRx: "); + } +} + +static void nfc_cli_raw_execute(PipeSide* pipe, void* context) { + UNUSED(pipe); + furi_assert(context); + NfcCliRawCmdContext* instance = context; + + furi_string_reset(instance->response.activation_string); + + if(instance->poller_state == NfcPollerStateStopped) { + if(instance->poller == NULL) + instance->poller = nfc_poller_alloc(instance->nfc, instance->request.protocol); + + nfc_poller_start_ex(instance->poller, nfc_cli_raw_poller_callback, instance); + instance->poller_state = NfcPollerStateStarted; + } + + instance->request_type = NfcCliProtocolRequestTypeNormalExecute; + furi_message_queue_put(instance->input_queue, &instance->request_type, FuriWaitForever); + furi_semaphore_acquire(instance->sem_done, FuriWaitForever); + + nfc_cli_raw_print_result(instance); +} + +static const NfcProtocolNameValuePair supported_protocols[] = { + {.name = "14a", .value = NfcProtocolIso14443_3a}, + {.name = "iso14a", .value = NfcProtocolIso14443_3a}, + + {.name = "14b", .value = NfcProtocolIso14443_3b}, + {.name = "iso14b", .value = NfcProtocolIso14443_3b}, + + {.name = "15", .value = NfcProtocolIso15693_3}, + {.name = "felica", .value = NfcProtocolFelica}, +}; + +static bool nfc_cli_raw_parse_protocol(FuriString* value, void* output) { + NfcCliRawCmdContext* ctx = output; + NfcProtocol new_protocol = NfcProtocolInvalid; + + NfcCliProtocolParser* parser = + nfc_cli_protocol_parser_alloc(supported_protocols, COUNT_OF(supported_protocols)); + + bool result = nfc_cli_protocol_parser_get(parser, value, &new_protocol); + + nfc_cli_protocol_parser_free(parser); + + if(result && ctx->request.protocol != NfcProtocolInvalid && + ctx->request.protocol != new_protocol) { + printf( + ANSI_FG_RED "Error: previous %s != new %s. Unable to continue." ANSI_RESET, + nfc_cli_get_protocol_name(ctx->request.protocol), + nfc_cli_get_protocol_name(new_protocol)); + result = false; + } + + if(result) { + ctx->request.protocol = new_protocol; + } + return result; +} + +static bool nfc_cli_raw_parse_data(FuriString* value, void* output) { + NfcCliRawCmdContext* ctx = output; + + bool result = false; + do { + size_t len = furi_string_size(value); + if(len % 2 != 0) break; + + size_t data_length = len / 2; + uint8_t* data = malloc(data_length); + + if(args_read_hex_bytes(value, data, data_length)) { + bit_buffer_reset(ctx->request.tx_buffer); + bit_buffer_copy_bytes(ctx->request.tx_buffer, data, data_length); + result = true; + } + + free(data); + } while(false); + + return result; +} + +static bool nfc_cli_raw_parse_timeout(FuriString* value, void* output) { + furi_assert(value); + furi_assert(output); + NfcCliRawCmdContext* ctx = output; + + bool result = false; + + int timeout = 0; + if(args_read_int_and_trim(value, &timeout)) { + ctx->request.timeout = timeout; + result = true; + } + return result; +} + +static bool nfc_cli_raw_parse_select(FuriString* value, void* output) { + UNUSED(value); + NfcCliRawCmdContext* ctx = output; + ctx->request.select = true; + return true; +} + +static bool nfc_cli_raw_parse_crc(FuriString* value, void* output) { + UNUSED(value); + NfcCliRawCmdContext* ctx = output; + ctx->request.append_crc = true; + return true; +} + +static bool nfc_cli_raw_parse_keep(FuriString* value, void* output) { + UNUSED(value); + NfcCliRawCmdContext* ctx = output; + ctx->request.keep_field = true; + return true; +} + +const NfcCliKeyDescriptor raw_action_keys[] = { + { + .long_name = NULL, + .short_name = "t", + .features = {.parameter = true, .required = false}, + .description = "timeout in fc", + .parse = nfc_cli_raw_parse_timeout, + }, + { + .long_name = NULL, + .short_name = "k", + .description = "keep signal field ON after receive", + .parse = nfc_cli_raw_parse_keep, + }, + { + .long_name = NULL, + .short_name = "c", + .description = "calculate and append CRC", + .parse = nfc_cli_raw_parse_crc, + }, + { + .long_name = NULL, + .short_name = "s", + .description = "Select on FieldOn", + .parse = nfc_cli_raw_parse_select, + }, + { + .long_name = "protocol", + .short_name = "p", + .description = "desired protocol. Possible values: 14a, iso14a, 14b, iso14b, 15, felica", + .features = {.parameter = true, .required = true}, + .parse = nfc_cli_raw_parse_protocol, + }, + { + .long_name = "data", + .short_name = "d", + .description = "Raw bytes to send in HEX format", + .features = {.parameter = true, .required = true}, + .parse = nfc_cli_raw_parse_data, + }, +}; + +const NfcCliActionDescriptor raw_action = { + .name = "raw", + .description = "Sends raw bytes using different protocols", + .key_count = COUNT_OF(raw_action_keys), + .keys = raw_action_keys, + .execute = nfc_cli_raw_execute, + .alloc = nfc_cli_raw_alloc_ctx, + .free = nfc_cli_raw_free_ctx, + .can_reuse = nfc_cli_raw_can_reuse_ctx, +}; + +const NfcCliActionDescriptor* raw_actions_collection[] = {&raw_action}; + +ADD_NFC_CLI_COMMAND(raw, "", raw_actions_collection); + +//Command usage: raw [keys] +//Command examples: +//raw iso14a -sc 3000 +//raw iso14a 3000 +//raw iso14a 3000 -sc diff --git a/applications/main/nfc/cli/commands/raw/nfc_cli_command_raw.h b/applications/main/nfc/cli/commands/raw/nfc_cli_command_raw.h new file mode 100644 index 000000000..d1d6dadee --- /dev/null +++ b/applications/main/nfc/cli/commands/raw/nfc_cli_command_raw.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../../nfc_cli_command_base_i.h" + +extern const NfcCliCommandDescriptor raw_cmd; diff --git a/applications/main/nfc/cli/commands/raw/protocol_handlers/felica/nfc_cli_raw_felica.c b/applications/main/nfc/cli/commands/raw/protocol_handlers/felica/nfc_cli_raw_felica.c new file mode 100644 index 000000000..c613b0d84 --- /dev/null +++ b/applications/main/nfc/cli/commands/raw/protocol_handlers/felica/nfc_cli_raw_felica.c @@ -0,0 +1,101 @@ +#include "nfc_cli_raw_felica.h" +#include "../../../helpers/nfc_cli_format.h" + +#include +#include +#include + +#define TAG "FELICA" + +#define BIT_BUFFER_EMPTY(buffer) ((bit_buffer_get_size_bytes(buffer) == 0)) + +static inline void felica_format_activation_data(const FelicaData* data, FuriString* output) { + nfc_cli_format_array(data->idm.data, FELICA_IDM_SIZE, "IDm: ", output); + nfc_cli_format_array(data->pmm.data, FELICA_PMM_SIZE, " PMm: ", output); +} + +static NfcCliRawError nfc_cli_raw_felica_process_error(FelicaError error) { + switch(error) { + case FelicaErrorNone: + return NfcCliRawErrorNone; + case FelicaErrorTimeout: + return NfcCliRawErrorTimeout; + case FelicaErrorWrongCrc: + return NfcCliRawErrorWrongCrc; + case FelicaErrorNotPresent: + return NfcCliRawErrorNotPresent; + default: + return NfcCliRawErrorProtocol; + } +} + +static FelicaError nfc_cli_raw_felica_poller_process_error(NfcError error) { + switch(error) { + case NfcErrorNone: + return FelicaErrorNone; + case NfcErrorTimeout: + return FelicaErrorTimeout; + default: + return FelicaErrorNotPresent; + } +} + +static inline NfcCliRawError + nfc_cli_raw_felica_activate(NfcGenericInstance* poller, FuriString* activation_string) { + FelicaData felica_data = {}; + FelicaPoller* felica_poller = poller; + FURI_LOG_D(TAG, "Activating..."); + + FelicaError error = felica_poller_activate(felica_poller, &felica_data); + if(error == FelicaErrorNone) { + felica_format_activation_data(&felica_data, activation_string); + } + + return nfc_cli_raw_felica_process_error(error); +} + +static inline NfcCliRawError nfc_cli_raw_felica_txrx( + NfcGenericInstance* poller, + BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t timeout) { + FURI_LOG_D(TAG, "TxRx"); + FelicaPoller* felica_poller = poller; + + bit_buffer_reset(rx_buffer); + + FelicaError error = FelicaErrorNone; + + NfcError nfc_error = nfc_poller_trx(felica_poller->nfc, tx_buffer, rx_buffer, timeout); + if(nfc_error != NfcErrorNone) { + error = nfc_cli_raw_felica_poller_process_error(nfc_error); + } else if(!felica_crc_check(rx_buffer)) { + error = FelicaErrorWrongCrc; + } + + return nfc_cli_raw_felica_process_error(error); +} + +NfcCommand nfc_cli_raw_felica_handler( + NfcGenericInstance* poller, + const NfcCliRawRequest* request, + NfcCliRawResponse* const response) { + do { + if(request->select) { + response->result = nfc_cli_raw_felica_activate(poller, response->activation_string); + } + + if(response->result != NfcCliRawErrorNone) break; + if(BIT_BUFFER_EMPTY(request->tx_buffer)) break; + + if(request->append_crc) { + FURI_LOG_D(TAG, "Add CRC"); + felica_crc_append(request->tx_buffer); + } + + uint32_t timeout = request->timeout > 0 ? request->timeout : FELICA_FDT_POLL_FC; + response->result = + nfc_cli_raw_felica_txrx(poller, request->tx_buffer, response->rx_buffer, timeout); + } while(false); + return request->keep_field ? NfcCommandContinue : NfcCommandStop; +} diff --git a/applications/main/nfc/cli/commands/raw/protocol_handlers/felica/nfc_cli_raw_felica.h b/applications/main/nfc/cli/commands/raw/protocol_handlers/felica/nfc_cli_raw_felica.h new file mode 100644 index 000000000..ef3b6c13a --- /dev/null +++ b/applications/main/nfc/cli/commands/raw/protocol_handlers/felica/nfc_cli_raw_felica.h @@ -0,0 +1,8 @@ +#pragma once + +#include "../nfc_cli_raw_common_types.h" + +NfcCommand nfc_cli_raw_felica_handler( + NfcGenericInstance* poller, + const NfcCliRawRequest* request, + NfcCliRawResponse* const response); diff --git a/applications/main/nfc/cli/commands/raw/protocol_handlers/iso14443_3a/nfc_cli_raw_iso14443_3a.c b/applications/main/nfc/cli/commands/raw/protocol_handlers/iso14443_3a/nfc_cli_raw_iso14443_3a.c new file mode 100644 index 000000000..bdeaeff61 --- /dev/null +++ b/applications/main/nfc/cli/commands/raw/protocol_handlers/iso14443_3a/nfc_cli_raw_iso14443_3a.c @@ -0,0 +1,81 @@ +#include "nfc_cli_raw_iso14443_3a.h" +#include "../../../helpers/nfc_cli_format.h" + +#include +#include +#include + +#define TAG "ISO14A" + +#define BIT_BUFFER_EMPTY(buffer) ((bit_buffer_get_size_bytes(buffer) == 0)) + +static NfcCliRawError nfc_cli_raw_iso14443_3a_process_error(Iso14443_3aError error) { + switch(error) { + case Iso14443_3aErrorNone: + return NfcCliRawErrorNone; + case Iso14443_3aErrorTimeout: + return NfcCliRawErrorTimeout; + case Iso14443_3aErrorWrongCrc: + return NfcCliRawErrorWrongCrc; + case Iso14443_3aErrorNotPresent: + return NfcCliRawErrorNotPresent; + default: + return NfcCliRawErrorProtocol; + } +} + +static void iso14443_3a_format_activation_data(const Iso14443_3aData* data, FuriString* output) { + nfc_cli_format_array(data->uid, data->uid_len, "UID: ", output); + furi_string_cat_printf( + output, " ATQA: %02X%02X SAK: %02X", data->atqa[0], data->atqa[1], data->sak); +} + +static inline NfcCliRawError + nfc_cli_raw_iso14443_3a_activate(NfcGenericInstance* poller, FuriString* activation_string) { + Iso14443_3aData iso3_data = {}; + FURI_LOG_D(TAG, "Activating..."); + + Iso14443_3aError error = iso14443_3a_poller_activate(poller, &iso3_data); + if(error == Iso14443_3aErrorNone) + iso14443_3a_format_activation_data(&iso3_data, activation_string); + + return nfc_cli_raw_iso14443_3a_process_error(error); +} + +static inline NfcCliRawError nfc_cli_raw_iso14443_3a_txrx( + NfcGenericInstance* poller, + BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t timeout) { + FURI_LOG_D(TAG, "TxRx"); + bit_buffer_reset(rx_buffer); + Iso14443_3aError error = iso14443_3a_poller_txrx(poller, tx_buffer, rx_buffer, timeout); + return nfc_cli_raw_iso14443_3a_process_error(error); +} + +NfcCommand nfc_cli_raw_iso14443_3a_handler( + NfcGenericInstance* poller, + const NfcCliRawRequest* request, + NfcCliRawResponse* const response) { + do { + response->result = NfcCliRawErrorNone; + if(request->select) { + response->result = + nfc_cli_raw_iso14443_3a_activate(poller, response->activation_string); + } + + if(response->result != NfcCliRawErrorNone) break; + if(BIT_BUFFER_EMPTY(request->tx_buffer)) break; + + if(request->append_crc) { + FURI_LOG_D(TAG, "Add CRC"); + iso14443_crc_append(Iso14443CrcTypeA, request->tx_buffer); + } + + uint32_t timeout = request->timeout > 0 ? request->timeout : ISO14443_3A_FDT_LISTEN_FC; + response->result = + nfc_cli_raw_iso14443_3a_txrx(poller, request->tx_buffer, response->rx_buffer, timeout); + } while(false); + + return request->keep_field ? NfcCommandContinue : NfcCommandStop; +} diff --git a/applications/main/nfc/cli/commands/raw/protocol_handlers/iso14443_3a/nfc_cli_raw_iso14443_3a.h b/applications/main/nfc/cli/commands/raw/protocol_handlers/iso14443_3a/nfc_cli_raw_iso14443_3a.h new file mode 100644 index 000000000..5ba7fccbe --- /dev/null +++ b/applications/main/nfc/cli/commands/raw/protocol_handlers/iso14443_3a/nfc_cli_raw_iso14443_3a.h @@ -0,0 +1,8 @@ +#pragma once + +#include "../nfc_cli_raw_common_types.h" + +NfcCommand nfc_cli_raw_iso14443_3a_handler( + NfcGenericInstance* poller, + const NfcCliRawRequest* request, + NfcCliRawResponse* const response); diff --git a/applications/main/nfc/cli/commands/raw/protocol_handlers/iso14443_3b/nfc_cli_raw_iso14443_3b.c b/applications/main/nfc/cli/commands/raw/protocol_handlers/iso14443_3b/nfc_cli_raw_iso14443_3b.c new file mode 100644 index 000000000..01851eb5d --- /dev/null +++ b/applications/main/nfc/cli/commands/raw/protocol_handlers/iso14443_3b/nfc_cli_raw_iso14443_3b.c @@ -0,0 +1,121 @@ +#include "nfc_cli_raw_iso14443_3b.h" +#include "../../../helpers/nfc_cli_format.h" + +#include +#include +#include + +#define TAG "ISO14B" + +#define BIT_BUFFER_EMPTY(buffer) ((bit_buffer_get_size_bytes(buffer) == 0)) + +static NfcCliRawError nfc_cli_raw_iso14443_3b_process_error(Iso14443_3bError error) { + switch(error) { + case Iso14443_3bErrorNone: + return NfcCliRawErrorNone; + case Iso14443_3bErrorTimeout: + return NfcCliRawErrorTimeout; + case Iso14443_3bErrorWrongCrc: + return NfcCliRawErrorWrongCrc; + case Iso14443_3bErrorNotPresent: + return NfcCliRawErrorNotPresent; + default: + return NfcCliRawErrorProtocol; + } +} + +static Iso14443_3bError nfc_cli_raw_iso14443_3b_poller_process_error(NfcError error) { + switch(error) { + case NfcErrorNone: + return Iso14443_3bErrorNone; + case NfcErrorTimeout: + return Iso14443_3bErrorTimeout; + default: + return Iso14443_3bErrorNotPresent; + } +} + +static void iso14443_3b_format_activation_data(const Iso14443_3bData* data, FuriString* output) { + nfc_cli_format_array(data->uid, ISO14443_3B_UID_SIZE, "UID: ", output); + + const Iso14443_3bProtocolInfo* info = &data->protocol_info; + furi_string_cat_printf( + output, + " BitRate: %d, Protocol: %d, Max Frame Size: %d, Fo: %d, Adc: %d, Fwi: %d", + info->bit_rate_capability, + info->protocol_type, + info->max_frame_size, + info->fo, + info->adc, + info->fwi); +} + +static inline NfcCliRawError nfc_cli_raw_iso14443_3b_activate( + NfcGenericInstance* poller, + Iso14443_3bData* iso3b_data, + FuriString* activation_string) { + FURI_LOG_D(TAG, "Activating..."); + + Iso14443_3bError error = iso14443_3b_poller_activate(poller, iso3b_data); + if(error == Iso14443_3bErrorNone) + iso14443_3b_format_activation_data(iso3b_data, activation_string); + + return nfc_cli_raw_iso14443_3b_process_error(error); +} + +static inline NfcCliRawError nfc_cli_raw_iso14443_3b_txrx( + NfcGenericInstance* poller, + BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t timeout) { + FURI_LOG_D(TAG, "TxRx"); + Iso14443_3bPoller* iso14b_poller = poller; + + bit_buffer_reset(rx_buffer); + + Iso14443_3bError error = Iso14443_3bErrorNone; + + NfcError nfc_error = nfc_poller_trx(iso14b_poller->nfc, tx_buffer, rx_buffer, timeout); + if(nfc_error != NfcErrorNone) { + error = nfc_cli_raw_iso14443_3b_poller_process_error(nfc_error); + } else if(!iso14443_crc_check(Iso14443CrcTypeB, rx_buffer)) { + error = Iso14443_3bErrorWrongCrc; + } + + return nfc_cli_raw_iso14443_3b_process_error(error); +} + +NfcCommand nfc_cli_raw_iso14443_3b_handler( + NfcGenericInstance* poller, + const NfcCliRawRequest* request, + NfcCliRawResponse* const response) { + Iso14443_3bData iso3b_data = {0}; + bool activated = false; + do { + response->result = NfcCliRawErrorNone; + if(request->select) { + response->result = + nfc_cli_raw_iso14443_3b_activate(poller, &iso3b_data, response->activation_string); + activated = response->result == NfcCliRawErrorNone; + } + + if(response->result != NfcCliRawErrorNone) break; + if(BIT_BUFFER_EMPTY(request->tx_buffer)) break; + + uint32_t timeout = ISO14443_3B_FDT_POLL_FC; + if(request->timeout > 0) { + timeout = request->timeout; + } else if(activated) { + timeout = iso14443_3b_get_fwt_fc_max(&iso3b_data); + } + + if(request->append_crc) { + FURI_LOG_D(TAG, "Add CRC"); + iso14443_crc_append(Iso14443CrcTypeB, request->tx_buffer); + } + + response->result = + nfc_cli_raw_iso14443_3b_txrx(poller, request->tx_buffer, response->rx_buffer, timeout); + } while(false); + return request->keep_field ? NfcCommandContinue : NfcCommandStop; +} diff --git a/applications/main/nfc/cli/commands/raw/protocol_handlers/iso14443_3b/nfc_cli_raw_iso14443_3b.h b/applications/main/nfc/cli/commands/raw/protocol_handlers/iso14443_3b/nfc_cli_raw_iso14443_3b.h new file mode 100644 index 000000000..6fed95ef9 --- /dev/null +++ b/applications/main/nfc/cli/commands/raw/protocol_handlers/iso14443_3b/nfc_cli_raw_iso14443_3b.h @@ -0,0 +1,8 @@ +#pragma once + +#include "../nfc_cli_raw_common_types.h" + +NfcCommand nfc_cli_raw_iso14443_3b_handler( + NfcGenericInstance* poller, + const NfcCliRawRequest* request, + NfcCliRawResponse* const response); diff --git a/applications/main/nfc/cli/commands/raw/protocol_handlers/iso15693_3/nfc_cli_raw_iso15693_3.c b/applications/main/nfc/cli/commands/raw/protocol_handlers/iso15693_3/nfc_cli_raw_iso15693_3.c new file mode 100644 index 000000000..c96b7d83c --- /dev/null +++ b/applications/main/nfc/cli/commands/raw/protocol_handlers/iso15693_3/nfc_cli_raw_iso15693_3.c @@ -0,0 +1,102 @@ +#include "nfc_cli_raw_iso15693_3.h" +#include "../../../helpers/nfc_cli_format.h" + +#include +#include +#include + +#define TAG "ISO15" + +#define BIT_BUFFER_EMPTY(buffer) ((bit_buffer_get_size_bytes(buffer) == 0)) + +static NfcCliRawError nfc_cli_raw_iso15693_3_process_error(Iso15693_3Error error) { + switch(error) { + case Iso15693_3ErrorNone: + return NfcCliRawErrorNone; + case Iso15693_3ErrorTimeout: + return NfcCliRawErrorTimeout; + case Iso15693_3ErrorWrongCrc: + return NfcCliRawErrorWrongCrc; + case Iso15693_3ErrorNotPresent: + return NfcCliRawErrorNotPresent; + default: + return NfcCliRawErrorProtocol; + } +} + +static Iso15693_3Error nfc_cli_raw_iso15693_3_poller_process_nfc_error(NfcError error) { + switch(error) { + case NfcErrorNone: + return Iso15693_3ErrorNone; + case NfcErrorTimeout: + return Iso15693_3ErrorTimeout; + default: + return Iso15693_3ErrorNotPresent; + } +} + +static inline void iso15693_3_format_activation_data(const uint8_t* data, FuriString* output) { + nfc_cli_format_array(data, ISO15693_3_UID_SIZE, "UID: ", output); +} + +static inline NfcCliRawError + nfc_cli_raw_iso15693_3_activate(NfcGenericInstance* poller, FuriString* activation_string) { + FURI_LOG_D(TAG, "Activating..."); + + Iso15693_3Poller* iso15_poller = poller; + uint8_t uid[ISO15693_3_UID_SIZE] = {0}; + + Iso15693_3Error error = iso15693_3_poller_inventory(iso15_poller, uid); + if(error == Iso15693_3ErrorNone) { + iso15693_3_format_activation_data(uid, activation_string); + } + return nfc_cli_raw_iso15693_3_process_error(error); +} + +static inline NfcCliRawError nfc_cli_raw_iso15693_3_txrx( + NfcGenericInstance* poller, + BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t timeout) { + FURI_LOG_D(TAG, "TxRx"); + + Iso15693_3Poller* iso15_poller = poller; + + bit_buffer_reset(rx_buffer); + + Iso15693_3Error error = Iso15693_3ErrorNone; + + NfcError nfc_error = nfc_poller_trx(iso15_poller->nfc, tx_buffer, rx_buffer, timeout); + if(nfc_error != NfcErrorNone) { + error = nfc_cli_raw_iso15693_3_poller_process_nfc_error(nfc_error); + } else if(!iso13239_crc_check(Iso13239CrcTypeDefault, rx_buffer)) { + error = Iso15693_3ErrorWrongCrc; + } + + return nfc_cli_raw_iso15693_3_process_error(error); +} + +NfcCommand nfc_cli_raw_iso15693_3_handler( + NfcGenericInstance* poller, + const NfcCliRawRequest* request, + NfcCliRawResponse* const response) { + do { + if(request->select) { + response->result = + nfc_cli_raw_iso15693_3_activate(poller, response->activation_string); + } + + if(response->result != NfcCliRawErrorNone) break; + if(BIT_BUFFER_EMPTY(request->tx_buffer)) break; + + if(request->append_crc) { + FURI_LOG_D(TAG, "Add CRC"); + iso13239_crc_append(Iso13239CrcTypeDefault, request->tx_buffer); + } + + uint32_t timeout = request->timeout > 0 ? request->timeout : ISO15693_3_FDT_POLL_FC; + response->result = + nfc_cli_raw_iso15693_3_txrx(poller, request->tx_buffer, response->rx_buffer, timeout); + } while(false); + return request->keep_field ? NfcCommandContinue : NfcCommandStop; +} diff --git a/applications/main/nfc/cli/commands/raw/protocol_handlers/iso15693_3/nfc_cli_raw_iso15693_3.h b/applications/main/nfc/cli/commands/raw/protocol_handlers/iso15693_3/nfc_cli_raw_iso15693_3.h new file mode 100644 index 000000000..1911abe57 --- /dev/null +++ b/applications/main/nfc/cli/commands/raw/protocol_handlers/iso15693_3/nfc_cli_raw_iso15693_3.h @@ -0,0 +1,8 @@ +#pragma once + +#include "../nfc_cli_raw_common_types.h" + +NfcCommand nfc_cli_raw_iso15693_3_handler( + NfcGenericInstance* poller, + const NfcCliRawRequest* request, + NfcCliRawResponse* const response); diff --git a/applications/main/nfc/cli/commands/raw/protocol_handlers/nfc_cli_raw_common_types.h b/applications/main/nfc/cli/commands/raw/protocol_handlers/nfc_cli_raw_common_types.h new file mode 100644 index 000000000..1eb3ca242 --- /dev/null +++ b/applications/main/nfc/cli/commands/raw/protocol_handlers/nfc_cli_raw_common_types.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include + +typedef enum { + NfcCliRawErrorNone, + NfcCliRawErrorTimeout, + NfcCliRawErrorNotPresent, + NfcCliRawErrorWrongCrc, + NfcCliRawErrorProtocol, +} NfcCliRawError; + +typedef struct { + bool select; + bool keep_field; + bool append_crc; + NfcProtocol protocol; + BitBuffer* tx_buffer; + uint32_t timeout; +} NfcCliRawRequest; + +typedef struct { + NfcCliRawError result; + BitBuffer* rx_buffer; + FuriString* activation_string; +} NfcCliRawResponse; diff --git a/applications/main/nfc/cli/nfc_cli.c b/applications/main/nfc/cli/nfc_cli.c new file mode 100644 index 000000000..60250c857 --- /dev/null +++ b/applications/main/nfc/cli/nfc_cli.c @@ -0,0 +1,124 @@ +#include "nfc_cli_commands.h" +#include "nfc_cli_command_processor.h" + +#include "applications/services/loader/loader.h" +#include "applications/services/cli/cli_main_commands.h" +#include +#include + +#define NFC_DESKTOP_APP_NAME "NFC" + +#define TAG "NfcCli" + +#define NFC_PROMPT "[" ANSI_FG_GREEN "nfc" ANSI_RESET "]" + +typedef struct { + Nfc* nfc; + CliRegistry* registry; + CliShell* shell; + NfcCliProcessorContext* processor_context; +} NfcCliContext; + +static void nfc_cli_shell_motd(void* context) { + UNUSED(context); + printf(ANSI_FG_BR_BLUE "\r\n" + " 0000 \r\n" + " 0000 \r\n" + " 000 0000 \r\n" + " 0000 00000 \r\n" + " 000 00000 0000 \r\n" + " 0 0000 0000 00000 \r\n" + " 000000 0000 00000 0000 \r\n" + " 00000000 0000 0000 0000 \r\n" + " 0000000000 0000 00000 0000 \r\n" + " 0000 00000000 00000 00000 0000 \r\n" + " 0000 0000000 00000 00000 0000 \r\n" + " 0000 000000000000 0000 0000 \r\n" + " 00000 000000000 00000 0000 \r\n" + " 00 000000 0000 00000 \r\n" + " 00 00000 0000 \r\n" + " 0000 00000 \r\n" + " 000 0000 \r\n" + " 0000 \r\n" + " 0005 \r\n" + "\r\n" ANSI_FG_BR_WHITE "Welcome to NFC Command Line Interface!\r\n" + "Run `help` or `?` to list available commands\r\n" ANSI_RESET); +} + +static void nfc_cli_subscribe_commands(NfcCliContext* instance) { + size_t cnt = nfc_cli_command_get_count(); + for(size_t i = 0; i < cnt; i++) { + const NfcCliCommandDescriptor* cmd = nfc_cli_command_get_by_index(i); + CliCommandExecuteCallback callback = nfc_cli_command_get_execute(cmd); + if(callback == NULL) continue; + const char* name = nfc_cli_command_get_name(cmd); + cli_registry_add_command( + instance->registry, + name, + CliCommandFlagParallelSafe, + callback, + instance->processor_context); + } +} + +static bool nfc_cli_desktop_app_is_running() { + FuriString* app_name = furi_string_alloc(); + Loader* ldr = furi_record_open(RECORD_LOADER); + bool result = false; + + if(loader_get_application_name(ldr, app_name)) { + result = furi_string_equal_str(app_name, NFC_DESKTOP_APP_NAME); + } + + furi_record_close(RECORD_LOADER); + furi_string_free(app_name); + return result; +} + +static NfcCliContext* nfc_cli_alloc(PipeSide* pipe) { + NfcCliContext* instance = malloc(sizeof(NfcCliContext)); + instance->nfc = nfc_alloc(); + instance->processor_context = nfc_cli_command_processor_alloc(instance->nfc); + + instance->registry = cli_registry_alloc(); + + nfc_cli_subscribe_commands(instance); + + instance->shell = + cli_shell_alloc(nfc_cli_shell_motd, instance, pipe, instance->registry, NULL); + + cli_shell_set_prompt(instance->shell, NFC_PROMPT); + return instance; +} + +void nfc_cli_free(NfcCliContext* instance) { + furi_assert(instance); + nfc_cli_command_processor_free(instance->processor_context); + + cli_shell_free(instance->shell); + cli_registry_free(instance->registry); + + nfc_free(instance->nfc); + free(instance); +} + +void nfc_cli_execute(PipeSide* pipe, FuriString* args, void* context) { + furi_assert(pipe); + UNUSED(args); + UNUSED(context); + + if(nfc_cli_desktop_app_is_running()) { + printf(ANSI_FG_YELLOW + "NFC app is running, unable to run NFC CLI at the same time!\r\n" ANSI_RESET); + return; + } + + NfcCliContext* instance = nfc_cli_alloc(pipe); + + cli_shell_start(instance->shell); + cli_shell_join(instance->shell); + + nfc_cli_free(instance); +} + +CLI_COMMAND_INTERFACE(nfc, nfc_cli_execute, CliCommandFlagParallelSafe, 1024, CLI_APPID); diff --git a/applications/main/nfc/cli/nfc_cli_command_base.h b/applications/main/nfc/cli/nfc_cli_command_base.h new file mode 100644 index 000000000..8d7f0c0ac --- /dev/null +++ b/applications/main/nfc/cli/nfc_cli_command_base.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include + +/** + * @brief Type for action context to be created before action execution + * must be hanlded through callbacks in each action separately + */ +typedef void NfcCliActionContext; + +/** + * @brief Callback type for function of action context allocation + * @param nfc Instance of NFC subsystem, will be used during action execution + * @return Pointer to action context + */ +typedef NfcCliActionContext* (*NfcCliActionContextAlloc)(Nfc* nfc); + +/** + * @brief Callback for action context deleting + * @param action_ctx Action context to be freed + */ +typedef void (*NfcCliActionContextFree)(NfcCliActionContext* action_ctx); + +/** + * @brief Callback invoked by command processor to determine whether already + * existing context (from previously executed command) can be reused for the new one. + * + * @param action_ctx Action context + * + * In most cases re-creating of a new context is not needed. + * It is used in 'raw' command, where nfc field sometimes need to stay turned on between + * commands. + * + * Handling of this situation and decision about reusing action context is on developer, + * who need to decide, can this command reuse context. + * + * It can be done by comparing parameters of previously executed command and a new one, + * or by some args in command. + * + * See implementation of 'keep_field' flag in 'raw' command for example. + */ +typedef bool (*NfcCliActionContextCanReuse)(NfcCliActionContext* ctx); + +/** + * @brief Action execution callback + * @param pipe provided by cli shell, can be used for command termination + * @param ctx Action context + */ +typedef void (*NfcCliActionHandlerCallback)(PipeSide* pipe, NfcCliActionContext* ctx); + +/** + * @brief Callback used for parsing argument key. + * Each key added to command must have this, otherwise parsing result will always be + * false and command will never be executed. + * + * @param value Text value for the key to be parsed + * @param ctx Action context + * @return true when parsing was fine, otherwise false. If any argument in command input + * generates false during parsing, command will not be executed and error will be shown + */ +typedef bool (*NfcCliArgParseCallback)(FuriString* value, NfcCliActionContext* ctx); + +typedef struct NfcCliKeyDescriptor NfcCliKeyDescriptor; + +typedef struct NfcCliActionDescriptor NfcCliActionDescriptor; + +typedef struct NfcCliCommandDescriptor NfcCliCommandDescriptor; diff --git a/applications/main/nfc/cli/nfc_cli_command_base_i.h b/applications/main/nfc/cli/nfc_cli_command_base_i.h new file mode 100644 index 000000000..2ffc047f9 --- /dev/null +++ b/applications/main/nfc/cli/nfc_cli_command_base_i.h @@ -0,0 +1,130 @@ +#pragma once + +#include "nfc_cli_command_base.h" +#include +#include +#include +#include "nfc_cli_command_processor.h" + +/** + * @brief How to add command. + * + * There are 3 possible option on how to add new command to nfc_cli: + * + * @see Option 1 "Add action command directly to nfc_shell" + * + * In this case command will be invoked with argument string from nfc_shell. + * Command registration must be performed directly by user. + * + * Steps: + * 1. Add new function for command to nfc_cli.c + * 2. In nfc_cli_alloc function register command using cli_registry_add_command after nfc_cli_subscribe_commands + * + * This option is NOT RECOMENDED, because such command will not have any 'help' + * processing and parsing error checks. Argument parsing must also be done by hand. + * + * -------------------------------------------------------------------------- + * + * @see Option 2 "Add action command to collection without further processing" + * + * In this case command will be invoked with argument string from nfc_shell. + * nfc_cli_command_processor is skipped, so argument handling is up to the developer. + * + * Steps: + * 1. Add new pair of nfc_cli_command_.c/.h files to /commands folder + * 2. Define const NfcCliCommandDescriptor instance in .c file and its extern definition in .h file + * 3. Include .h file to nfc_cli_commands.c file below comment "Include new commands here" + * 4. Add new command reference to nfc_cli_commands array + * 5. Add path to nfc_cli_command_.c file into 'cli_nfc' plugin in application.fam file + * + * This option suites for simple commands with no any parameters. + * @see nfc_cli_command_field.c implementation as an example. + * + * -------------------------------------------------------------------------- + * + * @see Option 3 "Add action command to collection with full processing" + * + * In this nfc_cli_command_processor will be invoked for parsing command arguments + * and action execution. Also it will handle errors and help printing. + * + * Steps: + * 1. Add new pair of nfc_cli_command_.c/.h files to /commands folder + * 2. Use macro ADD_NFC_CLI_COMMAND to define command in .c file + * 3. Define command extern in .h file, using command name from macro in form of "_cmd" + * 4. Add all desired actions and keys to your command + * 5. Include .h file to nfc_cli_commands.c file below comment "Include new commands here" + * 6. Add new command reference to nfc_cli_commands array + * 7. Add path to nfc_cli_command_.c file into 'cli_nfc' plugin in application.fam file + * + * This option suites for "difficult" commands which has actions with lots of keys. + * @see nfc_cli_command_emulate.c implementation as an example. + * + */ + +/** + * @brief Used to decorate argument with some properties + */ +typedef struct { + bool required : 1; /**< Command always needs this argument. Missing arguments with this set to true will result execution error.*/ + bool parameter : 1; /**< Such argument requires value after its name, otherwise it is a simple on/off switch */ + bool multivalue : 1; /**< Such argument can take multiple values after its name, like this "-key value1 value2 .. valueN" */ +} FURI_PACKED NfcCliKeyFeatureSupport; + +/** + * @brief Describes key for action + */ +struct NfcCliKeyDescriptor { + NfcCliKeyFeatureSupport features; /**< Features supported defining key behaviour */ + const char* long_name; /**< Long key name starts with '--' symbol in argument string */ + const char* short_name; /**< Short key name starts with '-' symbol in argument string */ + const char* description; /**< Key description showed in help */ + NfcCliArgParseCallback parse; /**< Parsing callback */ +}; + +/** + * @brief Describes action + */ +struct NfcCliActionDescriptor { + const char* name; /**< Action name MUST be the first argument after command.*/ + const char* description; /**< Description showed in help */ + size_t key_count; /**< Amount of key entries in keys array */ + const NfcCliKeyDescriptor* keys; /**< Keys available for action */ + + NfcCliActionHandlerCallback execute; /**< Action callback, invoked if parsing is ok */ + NfcCliActionContextAlloc alloc; /**< Allocates action context during command processing */ + NfcCliActionContextFree free; /**< Frees action context */ + NfcCliActionContextCanReuse can_reuse; /**< Checks context reuse possibility */ +}; + +/** + * @brief Describes command + */ +struct NfcCliCommandDescriptor { + const char* name; /** Used to register command in cli shell */ + const char* description; /**< Description showed in help */ + size_t action_count; /** Amount of actions available in scope of this particular command */ + const NfcCliActionDescriptor** actions; /**< Actions available for command */ + CliCommandExecuteCallback callback; /** Entry point for command */ +}; + +/** + * @brief This macro simplifies command creation. It fills instance of + * NfcCliCommandDescriptor and generates a callback which invokes + * nfc_cli_command_processor inside + */ +#define ADD_NFC_CLI_COMMAND(name, description, actions) \ + static void nfc_cli_command_##name##_callback( \ + PipeSide* pipe, FuriString* args, void* context); \ + \ + const NfcCliCommandDescriptor name##_cmd = { \ + #name, \ + #description, \ + COUNT_OF(actions), \ + actions, \ + nfc_cli_command_##name##_callback, \ + }; \ + \ + static void nfc_cli_command_##name##_callback( \ + PipeSide* pipe, FuriString* args, void* context) { \ + nfc_cli_command_processor_run(&name##_cmd, pipe, args, context); \ + } diff --git a/applications/main/nfc/cli/nfc_cli_command_processor.c b/applications/main/nfc/cli/nfc_cli_command_processor.c new file mode 100644 index 000000000..839ba4267 --- /dev/null +++ b/applications/main/nfc/cli/nfc_cli_command_processor.c @@ -0,0 +1,388 @@ +#include "nfc_cli_command_processor.h" +#include "nfc_cli_commands.h" +#include "nfc_cli_command_base_i.h" + +#include +#include +#include + +#define TAG "NfcCliProcessor" + +#define NFC_CLI_KEYS_FOUND_SIZE_BYTES (10 * sizeof(NfcCliKeyDescriptor*)) + +typedef enum { + NfcCliArgumentTypeShortNameKey, + NfcCliArgumentTypeShortNameKeyGroup, + NfcCliArgumentTypeLongNameKey, + + NfcCliArgumentTypeUnknown +} NfcCliArgumentType; + +/** + * @brief Error codes for different processing states + */ +typedef enum { + NfcCliProcessorErrorNone, /**< Command was parsed successfully and execute callback will be invoked*/ + NfcCliProcessorErrorNoneButHelp, /**< There was no error, but help needs to be printed. Command wil not be executed */ + NfcCliProcessorErrorActionNotFound, /**< Wrong action was passed as first command parameter */ + NfcCliProcessorErrorKeyNotSupported, /**< Unsupported key was passed in arguments. Details will be printed in erro_message*/ + NfcCliProcessorErrorKeyParameterInGroup, /**< Parameter which requires value was passed in group. Example: -sckd */ + NfcCliProcessorErrorKeyParameterValueMissing, /**< Value is missing for the parameter which requires it */ + NfcCliProcessorErrorKeyDuplication, /**< Some argument key was duplicated in input parameters */ + NfcCliProcessorErrorKeyParseError, /**< Error happened during argument value parsing */ + NfcCliProcessorErrorKeyRequiredMissing, /**< Some keys required for command execution is missing*/ + + NfcCliProcessorErrorNum +} NfcCliProcessorError; + +struct NfcCliProcessorContext { + const NfcCliCommandDescriptor* cmd; + const NfcCliActionDescriptor* action; + const NfcCliKeyDescriptor** keys_found; + uint8_t total_keys_found; + uint8_t required_keys_expected; + uint8_t required_keys_found; + + Nfc* nfc; + void* action_context; + + FuriString* error_message; +}; + +static const NfcCliActionDescriptor* + nfc_cli_get_action_from_args(const NfcCliCommandDescriptor* cmd, FuriString* args) { + const NfcCliActionDescriptor* action = cmd->actions[0]; + + bool multiple_action_cmd = nfc_cli_command_has_multiple_actions(cmd); + if(multiple_action_cmd) { + action = NULL; + FuriString* arg_str = furi_string_alloc(); + if(args_read_string_and_trim(args, arg_str)) { + action = nfc_cli_command_get_action_by_name(cmd, arg_str); + } + furi_string_free(arg_str); + } + + return action; +} + +static bool nfc_cli_action_can_reuse_context( + NfcCliProcessorContext* instance, + const NfcCliActionDescriptor* new_action) { + bool result = false; + do { + if(instance->action != new_action) break; + if(new_action->can_reuse == NULL) break; + result = new_action->can_reuse(instance->action_context); + } while(false); + return result; +} + +static void nfc_cli_action_free(NfcCliProcessorContext* instance) { + if(instance->action && instance->action->free) { + FURI_LOG_D(TAG, "Free previous \"%s\" action context", instance->action->name); + instance->action->free(instance->action_context); + } + instance->action = NULL; +} + +static NfcCliProcessorError + nfc_cli_action_alloc(NfcCliProcessorContext* instance, FuriString* args) { + const NfcCliCommandDescriptor* cmd = instance->cmd; + + NfcCliProcessorError result = NfcCliProcessorErrorNone; + do { + const NfcCliActionDescriptor* action = nfc_cli_get_action_from_args(cmd, args); + if(action == NULL) { + result = NfcCliProcessorErrorActionNotFound; + furi_string_printf(instance->error_message, "Action not found"); + break; + } + + if(!nfc_cli_action_can_reuse_context(instance, action)) { + nfc_cli_action_free(instance); + + instance->action = action; + if(action->alloc && action->free) { + FURI_LOG_D(TAG, "Allocating context for action \"%s\"", action->name); + instance->action_context = instance->action->alloc(instance->nfc); + } else if(action->alloc && (action->free == NULL)) { + FURI_LOG_W( + TAG, + "Free callback not defined for action \"%s\". Skip allocation to avoid memory leak.", + action->name); + instance->action_context = NULL; + } else { + FURI_LOG_D(TAG, "No alloc context callback for action \"%s\"", action->name); + instance->action_context = NULL; + } + } else + FURI_LOG_D(TAG, "Reusing context from previous \"%s\" action", action->name); + + memset(instance->keys_found, 0, NFC_CLI_KEYS_FOUND_SIZE_BYTES); + instance->required_keys_expected = nfc_cli_action_get_required_keys_count(action); + instance->required_keys_found = 0; + instance->total_keys_found = 0; + } while(false); + + return result; +} + +static NfcCliArgumentType nfc_cli_get_argument_type(FuriString* argument) { + size_t arg_len = furi_string_size(argument); + NfcCliArgumentType type = NfcCliArgumentTypeUnknown; + + if(arg_len > 2) { + char ch1 = furi_string_get_char(argument, 0); + char ch2 = furi_string_get_char(argument, 1); + if(ch1 == '-') { + type = (ch2 == '-') ? NfcCliArgumentTypeLongNameKey : + NfcCliArgumentTypeShortNameKeyGroup; + } + } else if(arg_len == 2) { + char ch1 = furi_string_get_char(argument, 0); + type = (ch1 == '-') ? NfcCliArgumentTypeShortNameKey : NfcCliArgumentTypeUnknown; + } + + return type; +} + +static bool + nfc_cli_check_duplicate_keys(NfcCliProcessorContext* instance, const NfcCliKeyDescriptor* key) { + bool result = false; + for(size_t i = 0; i < instance->total_keys_found; i++) { + const NfcCliKeyDescriptor* buf = instance->keys_found[i]; + if(buf != key) continue; + result = true; + break; + } + + return result; +} + +static void nfc_cli_trim_multivalue_arg(FuriString* args, FuriString* value) { + furi_string_set(value, args); + size_t index = furi_string_search_char(value, '-', 0); + if(index != STRING_FAILURE) { + furi_string_left(value, index); + furi_string_right(args, index); + } else { + furi_string_reset(args); + } +} + +static NfcCliProcessorError nfc_cli_parse_single_key( + NfcCliProcessorContext* instance, + FuriString* argument, + FuriString* args, + bool from_group) { + FuriString* value_str = furi_string_alloc(); + + NfcCliProcessorError result = NfcCliProcessorErrorNone; + do { + const NfcCliKeyDescriptor* key = + nfc_cli_action_get_key_descriptor(instance->action, argument); + if(key == NULL) { + if(furi_string_equal_str(argument, "h")) + result = NfcCliProcessorErrorNoneButHelp; + else { + furi_string_printf( + instance->error_message, + "Key \'%s\' is not supported", + furi_string_get_cstr(argument)); + result = NfcCliProcessorErrorKeyNotSupported; + } + break; + } + + if(key->features.parameter && from_group) { + furi_string_printf( + instance->error_message, + "Parameter key \'%s\' can\'t be grouped", + furi_string_get_cstr(argument)); + result = NfcCliProcessorErrorKeyParameterInGroup; + break; + } + + if(nfc_cli_check_duplicate_keys(instance, key)) { + furi_string_printf( + instance->error_message, "Duplicated key \'%s\'", furi_string_get_cstr(argument)); + result = NfcCliProcessorErrorKeyDuplication; + break; + } + + if(key->features.multivalue && !key->features.parameter) break; + if(key->features.multivalue) { + nfc_cli_trim_multivalue_arg(args, value_str); + FURI_LOG_D(TAG, "Multivalue: %s", furi_string_get_cstr(value_str)); + } else if(key->features.parameter && !args_read_string_and_trim(args, value_str)) { + result = NfcCliProcessorErrorKeyParameterValueMissing; + furi_string_printf( + instance->error_message, + "Missing value for \'%s\'", + furi_string_get_cstr(argument)); + break; + } + + if(key->parse == NULL) { + furi_string_printf( + instance->error_message, + "Parse callback for key \'%s\' not defined", + furi_string_get_cstr(argument)); + result = NfcCliProcessorErrorKeyParseError; + break; + } + + FURI_LOG_D(TAG, "Parsing key \"%s\"", furi_string_get_cstr(argument)); + if(!key->parse(value_str, instance->action_context)) { + furi_string_printf( + instance->error_message, + "Unable to parse value \'%s\' for key \'%s\'", + furi_string_get_cstr(value_str), + furi_string_get_cstr(argument)); + result = NfcCliProcessorErrorKeyParseError; + break; + } + + instance->keys_found[instance->total_keys_found] = key; + instance->total_keys_found++; + if(key->features.required) instance->required_keys_found++; + } while(false); + furi_string_free(value_str); + + return result; +} + +static NfcCliProcessorError + nfc_cli_parse_group_key(NfcCliProcessorContext* instance, FuriString* argument) { + NfcCliProcessorError result = NfcCliProcessorErrorNone; + FURI_LOG_D(TAG, "Parsing key group\"%s\"", furi_string_get_cstr(argument)); + + FuriString* arg_buf = furi_string_alloc(); + for(size_t i = 0; i < furi_string_size(argument); i++) { + furi_string_set_n(arg_buf, argument, i, 1); + result = nfc_cli_parse_single_key(instance, arg_buf, NULL, true); + if(result != NfcCliProcessorErrorNone) break; + } + furi_string_free(arg_buf); + + return result; +} + +static NfcCliProcessorError nfc_cli_parse_argument( + NfcCliProcessorContext* instance, + FuriString* argument, + FuriString* args) { + NfcCliArgumentType type = nfc_cli_get_argument_type(argument); + + furi_string_trim(argument, "-"); + + NfcCliProcessorError result = NfcCliProcessorErrorNone; + + if(type == NfcCliArgumentTypeShortNameKeyGroup) + result = nfc_cli_parse_group_key(instance, argument); + else if((type == NfcCliArgumentTypeShortNameKey) || (type == NfcCliArgumentTypeLongNameKey)) { + result = nfc_cli_parse_single_key(instance, argument, args, false); + } else if(type == NfcCliArgumentTypeUnknown) { //-V547 + result = NfcCliProcessorErrorKeyNotSupported; + furi_string_printf( + instance->error_message, + "Key \'%s\' is not supported", + furi_string_get_cstr(argument)); + } + + return result; +} + +static NfcCliProcessorError + nfc_cli_process_arguments(NfcCliProcessorContext* instance, FuriString* args) { + NfcCliProcessorError result = NfcCliProcessorErrorNone; + + FuriString* argument = furi_string_alloc(); + while(args_read_string_and_trim(args, argument)) { + result = nfc_cli_parse_argument(instance, argument, args); + if(result != NfcCliProcessorErrorNone) break; + } + furi_string_free(argument); + + if((result == NfcCliProcessorErrorNone) && + (instance->required_keys_expected != instance->required_keys_found)) { + furi_string_printf(instance->error_message, "Some required keys missing"); + result = NfcCliProcessorErrorKeyRequiredMissing; + } + + return result; +} + +static inline void nfc_cli_command_process_error( + const NfcCliProcessorContext* instance, + NfcCliProcessorError error) { + do { + if(error == NfcCliProcessorErrorNone) break; + + if(error != NfcCliProcessorErrorNoneButHelp) + printf( + ANSI_FG_RED "Error: %s\r\n" ANSI_RESET, + furi_string_get_cstr(instance->error_message)); + + if(error == NfcCliProcessorErrorActionNotFound) + nfc_cli_command_format_info(instance->cmd, instance->error_message); + else + nfc_cli_action_format_info(instance->action, instance->error_message); + + printf("\n%s", furi_string_get_cstr(instance->error_message)); + } while(false); +} + +void nfc_cli_command_processor_run( + const NfcCliCommandDescriptor* cmd, + PipeSide* pipe, + FuriString* args, + void* context) { + furi_assert(pipe); + furi_assert(cmd); + furi_assert(args); + NfcCliProcessorContext* instance = context; + furi_string_reset(instance->error_message); + + NfcCliProcessorError error = NfcCliProcessorErrorNone; + instance->cmd = cmd; + do { + error = nfc_cli_action_alloc(instance, args); + if(error != NfcCliProcessorErrorNone) break; + + error = nfc_cli_process_arguments(instance, args); + if(error != NfcCliProcessorErrorNone) break; + + if(instance->action && instance->action->execute) { + instance->action->execute(pipe, instance->action_context); + } else { + FURI_LOG_W(TAG, "Action execute callback missing"); + } + } while(false); + + nfc_cli_command_process_error(instance, error); +} + +NfcCliProcessorContext* nfc_cli_command_processor_alloc(Nfc* nfc) { + furi_assert(nfc); + NfcCliProcessorContext* instance = malloc(sizeof(NfcCliProcessorContext)); + instance->nfc = nfc; + instance->keys_found = malloc(NFC_CLI_KEYS_FOUND_SIZE_BYTES); + instance->total_keys_found = 0; + instance->required_keys_found = 0; + instance->required_keys_expected = 0; + + instance->error_message = furi_string_alloc(); + return instance; +} + +void nfc_cli_command_processor_free(NfcCliProcessorContext* instance) { + furi_assert(instance); + nfc_cli_action_free(instance); + free(instance->keys_found); + furi_string_free(instance->error_message); + + instance->nfc = NULL; + free(instance); +} diff --git a/applications/main/nfc/cli/nfc_cli_command_processor.h b/applications/main/nfc/cli/nfc_cli_command_processor.h new file mode 100644 index 000000000..2c3849c5b --- /dev/null +++ b/applications/main/nfc/cli/nfc_cli_command_processor.h @@ -0,0 +1,15 @@ +#pragma once +#include +#include +#include "nfc_cli_command_base.h" + +typedef struct NfcCliProcessorContext NfcCliProcessorContext; + +NfcCliProcessorContext* nfc_cli_command_processor_alloc(Nfc* nfc); +void nfc_cli_command_processor_free(NfcCliProcessorContext* instance); + +void nfc_cli_command_processor_run( + const NfcCliCommandDescriptor* cmd, + PipeSide* pipe, + FuriString* args, + void* context); diff --git a/applications/main/nfc/cli/nfc_cli_commands.c b/applications/main/nfc/cli/nfc_cli_commands.c new file mode 100644 index 000000000..82321cede --- /dev/null +++ b/applications/main/nfc/cli/nfc_cli_commands.c @@ -0,0 +1,161 @@ +#include "nfc_cli_commands.h" +#include "nfc_cli_command_base_i.h" + +/** Include new commands here */ +#include "commands/raw/nfc_cli_command_raw.h" +#include "commands/apdu/nfc_cli_command_apdu.h" +#include "commands/dump/nfc_cli_command_dump.h" +#include "commands/mfu/nfc_cli_command_mfu.h" +#include "commands/nfc_cli_command_emulate.h" +#include "commands/nfc_cli_command_scanner.h" +#include "commands/nfc_cli_command_field.h" + +#define TAG "NfcCliCommands" + +/** Add new commands here */ +static const NfcCliCommandDescriptor* nfc_cli_commands[] = { + &apdu_cmd, + &raw_cmd, + &emulate_cmd, + &mfu_cmd, + &scanner_cmd, + &dump_cmd, + &field_cmd, +}; + +size_t nfc_cli_command_get_count() { + return COUNT_OF(nfc_cli_commands); +} + +const NfcCliActionDescriptor* + nfc_cli_command_get_action_by_name(const NfcCliCommandDescriptor* cmd, const FuriString* name) { + furi_assert(cmd); + furi_assert(name); + + for(size_t i = 0; i < cmd->action_count; i++) { + const NfcCliActionDescriptor* action = cmd->actions[i]; + if(furi_string_equal_str(name, action->name)) return action; + } + return NULL; +} + +const NfcCliCommandDescriptor* nfc_cli_command_get_by_index(size_t index) { + furi_assert(index < COUNT_OF(nfc_cli_commands)); + return nfc_cli_commands[index]; +} + +bool nfc_cli_command_has_multiple_actions(const NfcCliCommandDescriptor* cmd) { + furi_assert(cmd); + furi_check(cmd->action_count > 0); + return (cmd->action_count > 1); +} + +const char* nfc_cli_command_get_name(const NfcCliCommandDescriptor* cmd) { + furi_assert(cmd); + return cmd->name; +} + +CliCommandExecuteCallback nfc_cli_command_get_execute(const NfcCliCommandDescriptor* cmd) { + furi_assert(cmd); + return cmd->callback; +} + +static inline const NfcCliKeyDescriptor* nfc_cli_action_get_key_by_name( + const NfcCliActionDescriptor* action, + const FuriString* name, + bool long_name) { + for(size_t i = 0; i < action->key_count; i++) { + const NfcCliKeyDescriptor* key = &action->keys[i]; + const char* buf = long_name ? key->long_name : key->short_name; + if((buf != NULL) && furi_string_equal_str(name, buf)) return key; + } + return NULL; +} + +const NfcCliKeyDescriptor* + nfc_cli_action_get_key_descriptor(const NfcCliActionDescriptor* action, FuriString* argument) { + furi_assert(action); + furi_assert(argument); + + return nfc_cli_action_get_key_by_name(action, argument, furi_string_size(argument) > 1); +} + +size_t nfc_cli_action_get_required_keys_count(const NfcCliActionDescriptor* action) { + furi_assert(action); + + size_t required_key_count = 0; + for(size_t i = 0; i < action->key_count; i++) { + const NfcCliKeyDescriptor* key = &action->keys[i]; + if(!key->features.required) continue; + required_key_count++; + } + return required_key_count; +} + +static int nfc_cli_action_format_key_name(const NfcCliKeyDescriptor* key, FuriString* output) { + int len = 0; + FuriString* name = furi_string_alloc(); + if(key->short_name && key->long_name) { + len = furi_string_printf(name, "-%s, --%s", key->short_name, key->long_name); + } else if(key->short_name && (key->long_name == NULL)) { + len = furi_string_printf(name, "-%s", key->short_name); + } else if((key->short_name == NULL) && key->long_name) { + len = furi_string_printf(name, "--%s", key->long_name); + } + + const char* color = key->features.required ? ANSI_FLIPPER_BRAND_ORANGE : ANSI_RESET; + furi_string_printf(output, "%s%s%s", color, furi_string_get_cstr(name), ANSI_RESET); + furi_string_free(name); + return len; +} + +void nfc_cli_action_format_info(const NfcCliActionDescriptor* action, FuriString* output) { + furi_assert(action); + furi_assert(output); + furi_string_printf( + output, + action->description ? "%s - %s\r\n\n" : "%s\r\n\n", + action->name, + action->description); + + if(action->key_count == 0) return; + + FuriString* buf = furi_string_alloc(); + furi_string_cat_printf( + output, + ANSI_FG_BR_GREEN "Keys " ANSI_FLIPPER_BRAND_ORANGE "(required) " ANSI_RESET + "(optional):\r\n"); + + for(size_t i = 0; i < action->key_count; i++) { + const NfcCliKeyDescriptor* key = &action->keys[i]; + + int len = nfc_cli_action_format_key_name(key, buf); + furi_string_cat_printf(output, "%s", furi_string_get_cstr(buf)); + + if(key->description) { + const int offset = 20; + furi_string_cat_printf( + output, ANSI_CURSOR_RIGHT_BY("%d") "%s", offset - len, key->description); + } + furi_string_cat_printf(output, "\r\n"); + } + furi_string_free(buf); +} + +void nfc_cli_command_format_info(const NfcCliCommandDescriptor* cmd, FuriString* output) { + furi_assert(cmd); + furi_assert(output); + furi_string_printf(output, "%s - %s\r\n", cmd->name, cmd->description); + if(cmd->action_count > 1) { + furi_string_cat_printf(output, "Possible actions: \r\n"); + + for(size_t i = 0; i < cmd->action_count; i++) { + const NfcCliActionDescriptor* action = cmd->actions[i]; + furi_string_cat_printf( + output, + action->description ? "\t%s\t-\t%s\r\n" : "%s\r\n", + action->name, + action->description); + } + } +} diff --git a/applications/main/nfc/cli/nfc_cli_commands.h b/applications/main/nfc/cli/nfc_cli_commands.h new file mode 100644 index 000000000..eb2b30f5a --- /dev/null +++ b/applications/main/nfc/cli/nfc_cli_commands.h @@ -0,0 +1,27 @@ +#pragma once + +#include "nfc_cli_command_base.h" +#include +#include + +size_t nfc_cli_command_get_count(); + +const NfcCliCommandDescriptor* nfc_cli_command_get_by_index(size_t index); + +const char* nfc_cli_command_get_name(const NfcCliCommandDescriptor* cmd); + +CliCommandExecuteCallback nfc_cli_command_get_execute(const NfcCliCommandDescriptor* cmd); + +bool nfc_cli_command_has_multiple_actions(const NfcCliCommandDescriptor* cmd); + +const NfcCliActionDescriptor* + nfc_cli_command_get_action_by_name(const NfcCliCommandDescriptor* cmd, const FuriString* name); + +size_t nfc_cli_action_get_required_keys_count(const NfcCliActionDescriptor* action); + +const NfcCliKeyDescriptor* + nfc_cli_action_get_key_descriptor(const NfcCliActionDescriptor* action, FuriString* argument); + +void nfc_cli_command_format_info(const NfcCliCommandDescriptor* cmd, FuriString* output); + +void nfc_cli_action_format_info(const NfcCliActionDescriptor* action, FuriString* output); diff --git a/applications/main/nfc/nfc_cli.c b/applications/main/nfc/nfc_cli.c deleted file mode 100644 index af3fd62eb..000000000 --- a/applications/main/nfc/nfc_cli.c +++ /dev/null @@ -1,67 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include - -#define FLAG_EVENT (1 << 10) - -static void nfc_cli_print_usage(void) { - printf("Usage:\r\n"); - printf("nfc \r\n"); - printf("Cmd list:\r\n"); - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - printf("\tfield\t - turn field on\r\n"); - } -} - -static void nfc_cli_field(PipeSide* pipe, FuriString* args) { - UNUSED(args); - // Check if nfc worker is not busy - if(furi_hal_nfc_is_hal_ready() != FuriHalNfcErrorNone) { - printf("NFC chip failed to start\r\n"); - return; - } - - furi_hal_nfc_acquire(); - furi_hal_nfc_low_power_mode_stop(); - furi_hal_nfc_poller_field_on(); - - printf("Field is on. Don't leave device in this mode for too long.\r\n"); - printf("Press Ctrl+C to abort\r\n"); - - while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { - furi_delay_ms(50); - } - - furi_hal_nfc_low_power_mode_start(); - furi_hal_nfc_release(); -} - -static void execute(PipeSide* pipe, FuriString* args, void* context) { - UNUSED(context); - FuriString* cmd; - cmd = furi_string_alloc(); - - do { - if(!args_read_string_and_trim(args, cmd)) { - nfc_cli_print_usage(); - break; - } - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - if(furi_string_cmp_str(cmd, "field") == 0) { - nfc_cli_field(pipe, args); - break; - } - } - - nfc_cli_print_usage(); - } while(false); - - furi_string_free(cmd); -} - -CLI_COMMAND_INTERFACE(nfc, execute, CliCommandFlagDefault, 1024, CLI_APPID); diff --git a/applications/main/nfc/resources/nfc/assets/vendors.nfc b/applications/main/nfc/resources/nfc/assets/vendors.nfc new file mode 100644 index 000000000..7750e99b7 --- /dev/null +++ b/applications/main/nfc/resources/nfc/assets/vendors.nfc @@ -0,0 +1,115 @@ +Filetype: NFC Vendors +Version: 1 +# Please do not change IDs in this list. Add new to the end if necessary. +# ID: "Vendor Country" +1: Motorola UK +2: ST Microelectronics SA France +3: Hitachi, Ltd Japan +4: NXP Semiconductors Germany +5: Infineon Technologies AG Germany +6: Cylink USA +7: Texas Instrument France +8: Fujitsu Limited Japan +9: Matsushita Electronics Corporation, Semiconductor Company Japan +10: NEC Japan +11: Oki Electric Industry Co. Ltd Japan +12: Toshiba Corp. Japan +13: Mitsubishi Electric Corp. Japan +14: Samsung Electronics Co. Ltd Korea +15: Hynix / Hyundai, Korea +16: LG-Semiconductors Co. Ltd Korea +17: Emosyn-EM Microelectronics USA +18: INSIDE Technology France +19: ORGA Kartensysteme GmbH Germany +20: SHARP Corporation Japan +21: ATMEL France +22: EM Microelectronic-Marin SA Switzerland +23: KSW Microtec GmbH Germany +24: ZMD AG Germany +25: XICOR, Inc. USA +26: Sony Corporation Japan +27: Malaysia Microelectronic Solutions Sdn. Bhd Malaysia +28: Emosyn USA +29: Shanghai Fudan Microelectronics Co. Ltd. P.R. China +30: Magellan Technology Pty Limited Australia +31: Melexis NV BO Switzerland +32: Renesas Technology Corp. Japan +33: TAGSYS France +34: Transcore USA +35: Shanghai belling corp., ltd. China +36: Masktech Germany Gmbh Germany +37: Innovision Research and Technology Plc UK +38: Hitachi ULSI Systems Co., Ltd. Japan +39: Cypak AB Sweden +40: Ricoh Japan +41: ASK France +42: Unicore Microsystems, LLC Russian Federation +43: Dallas Semiconductor/Maxim USA +44: Impinj, Inc. USA +45: RightPlug Alliance USA +46: Broadcom Corporation USA +47: MStar Semiconductor, Inc Taiwan, ROC +48: BeeDar Technology Inc. USA +49: RFIDsec Denmark +50: Schweizer Electronic AG Germany +51: AMIC Technology Corp Taiwan +52: Mikron JSC Russia +53: Fraunhofer Institute for Photonic Microsystems Germany +54: IDS Microchip AG Switzerland +55: Thinfilm - Kovio USA +56: HMT Microelectronic Ltd Switzerland +57: Silicon Craft Technology Thailand +58: Advanced Film Device Inc. Japan +59: Nitecrest Ltd UK +60: Verayo Inc. USA +61: HID Global USA +62: Productivity Engineering Gmbh Germany +63: Austriamicrosystems AG (reserved) Austria +64: Gemalto SA France +65: Renesas Electronics Corporation Japan +66: 3Alogics Inc Korea +67: Top TroniQ Asia Limited Hong Kong +68: Gentag Inc. USA +69: Invengo Information Technology Co.Ltd China +70: Guangzhou Sysur Microelectronics, Inc China +71: CEITEC S.A. Brazil +72: Shanghai Quanray Electronics Co. Ltd. China +73: MediaTek Inc Taiwan +74: Angstrem PJSC Russia +75: Celisic Semiconductor (Hong Kong) Limited China +76: LEGIC Identsystems AG Switzerland +77: Balluff GmbH Germany +78: Oberthur Technologies France +79: Silterra Malaysia Sdn. Bhd. Malaysia +80: DELTA Danish Electronics, Light & Acoustics Denmark +81: Giesecke & Devrient GmbH Germany +82: Shenzhen China Vision Microelectronics Co., Ltd. China +83: Shanghai Feiju Microelectronics Co. Ltd. China +84: Intel Corporation USA +85: Microsensys GmbH Germany +86: Sonix Technology Co., Ltd. Taiwan +87: Qualcomm Technologies Inc USA +88: Realtek Semiconductor Corp Taiwan +89: Freevision Technologies Co. Ltd China +90: Giantec Semiconductor Inc. China +91: JSC Angstrem-T Russia +92: STARCHIP France +93: SPIRTECH France +94: GANTNER Electronic GmbH Austria +95: Nordic Semiconductor Norway +96: Verisiti Inc USA +97: Wearlinks Technology Inc. China +98: Userstar Information Systems Co., Ltd Taiwan +99: Pragmatic Printing Ltd. UK +100: Associacao do Laboratorio de Sistemas Integraveis Tecnologico - LSI-TEC Brazil +101: Tendyron Corporation China +102: MUTO Smart Co., Ltd. Korea +103: ON Semiconductor USA +104: TUBITAK BILGEM Turkey +105: Huada Semiconductor Co., Ltd China +106: SEVENEY France +107: ISSM France +108: Wisesec Ltd Israel +124: DB HiTek Co Ltd Korea +125: SATO Vicinity Australia +126: Holtek Taiwan diff --git a/applications/services/cli/cli_vcp.h b/applications/services/cli/cli_vcp.h index 10e286183..e9a0671a8 100644 --- a/applications/services/cli/cli_vcp.h +++ b/applications/services/cli/cli_vcp.h @@ -15,7 +15,6 @@ typedef struct CliVcp CliVcp; void cli_vcp_enable(CliVcp* cli_vcp); void cli_vcp_disable(CliVcp* cli_vcp); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index 82b87ea2a..585e98197 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -31,6 +31,7 @@ env.Append( File("protocols/iso14443_3b/iso14443_3b_poller.h"), File("protocols/iso14443_4a/iso14443_4a_poller.h"), File("protocols/iso14443_4b/iso14443_4b_poller.h"), + File("protocols/iso15693_3/iso15693_3_poller.h"), File("protocols/mf_ultralight/mf_ultralight_poller.h"), File("protocols/mf_classic/mf_classic_poller.h"), File("protocols/mf_plus/mf_plus_poller.h"), @@ -52,6 +53,7 @@ env.Append( File("protocols/felica/felica_poller_sync.h"), # Misc File("helpers/nfc_util.h"), + File("helpers/felica_crc.h"), File("helpers/iso14443_crc.h"), File("helpers/iso13239_crc.h"), File("helpers/nfc_data_generator.h"), diff --git a/lib/nfc/protocols/felica/felica.c b/lib/nfc/protocols/felica/felica.c index e9ffb771a..a298f37a9 100644 --- a/lib/nfc/protocols/felica/felica.c +++ b/lib/nfc/protocols/felica/felica.c @@ -104,6 +104,7 @@ bool felica_load(FelicaData* data, FlipperFormat* ff, uint32_t version) { break; } } + furi_string_free(temp_str); } while(false); return parsed; diff --git a/lib/nfc/protocols/felica/felica.h b/lib/nfc/protocols/felica/felica.h index b4e7af9d1..1fcaffc53 100644 --- a/lib/nfc/protocols/felica/felica.h +++ b/lib/nfc/protocols/felica/felica.h @@ -155,15 +155,13 @@ typedef struct { FelicaFSUnion data; } FelicaData; -#pragma pack(push, 1) -typedef struct { +typedef struct FURI_PACKED { uint8_t code; FelicaIDm idm; uint8_t service_num; uint16_t service_code; uint8_t block_count; } FelicaCommandHeader; -#pragma pack(pop) typedef struct { uint8_t length; diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.c index 252c46399..be6bca970 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.c @@ -248,7 +248,8 @@ static NfcCommand mf_ultralight_poller_read_callback(NfcGenericEvent event, void poller_context->error = MfUltralightErrorNone; command = NfcCommandStop; } else if(mfu_event->type == MfUltralightPollerEventTypeReadFailed) { - poller_context->error = mfu_event->data->error; + poller_context->error = mf_ultralight_process_error( + mfu_poller->iso14443_3a_poller->iso14443_3a_event_data.error); command = NfcCommandStop; } else if(mfu_event->type == MfUltralightPollerEventTypeAuthRequest) { if(poller_context->auth_context != NULL) { diff --git a/lib/toolbox/cli/shell/cli_shell.c b/lib/toolbox/cli/shell/cli_shell.c index b2648d127..3516b51ce 100644 --- a/lib/toolbox/cli/shell/cli_shell.c +++ b/lib/toolbox/cli/shell/cli_shell.c @@ -442,7 +442,6 @@ static int32_t cli_shell_thread(void* context) { // ========== // Public API // ========== - CliShell* cli_shell_alloc( CliShellMotd motd, void* context, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 1169749cd..62f0245df 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -129,6 +129,7 @@ Header,+,lib/nanopb/pb.h,, Header,+,lib/nanopb/pb_decode.h,, Header,+,lib/nanopb/pb_encode.h,, Header,+,lib/nfc/helpers/crypto1.h,, +Header,+,lib/nfc/helpers/felica_crc.h,, Header,+,lib/nfc/helpers/iso13239_crc.h,, Header,+,lib/nfc/helpers/iso14443_crc.h,, Header,+,lib/nfc/helpers/nfc_data_generator.h,, @@ -153,6 +154,7 @@ Header,+,lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h,, Header,+,lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h,, Header,+,lib/nfc/protocols/iso14443_4b/iso14443_4b.h,, Header,+,lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h,, +Header,+,lib/nfc/protocols/iso15693_3/iso15693_3_poller.h,, Header,+,lib/nfc/protocols/mf_classic/mf_classic.h,, Header,+,lib/nfc/protocols/mf_classic/mf_classic_listener.h,, Header,+,lib/nfc/protocols/mf_classic/mf_classic_poller.h,, @@ -1059,6 +1061,9 @@ Function,+,felica_calculate_mac_write,void,"mbedtls_des3_context*, const uint8_t Function,+,felica_calculate_session_key,void,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, uint8_t*" Function,+,felica_check_mac,_Bool,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t, uint8_t*" Function,+,felica_copy,void,"FelicaData*, const FelicaData*" +Function,+,felica_crc_append,void,BitBuffer* +Function,+,felica_crc_check,_Bool,const BitBuffer* +Function,+,felica_crc_trim,void,BitBuffer* Function,+,felica_free,void,FelicaData* Function,+,felica_get_base_data,FelicaData*,const FelicaData* Function,+,felica_get_device_name,const char*,"const FelicaData*, NfcDeviceNameType" @@ -2215,6 +2220,13 @@ Function,+,iso15693_3_get_uid,const uint8_t*,"const Iso15693_3Data*, size_t*" Function,+,iso15693_3_is_block_locked,_Bool,"const Iso15693_3Data*, uint8_t" Function,+,iso15693_3_is_equal,_Bool,"const Iso15693_3Data*, const Iso15693_3Data*" Function,+,iso15693_3_load,_Bool,"Iso15693_3Data*, FlipperFormat*, uint32_t" +Function,+,iso15693_3_poller_activate,Iso15693_3Error,"Iso15693_3Poller*, Iso15693_3Data*" +Function,+,iso15693_3_poller_get_blocks_security,Iso15693_3Error,"Iso15693_3Poller*, uint8_t*, uint16_t" +Function,+,iso15693_3_poller_get_system_info,Iso15693_3Error,"Iso15693_3Poller*, Iso15693_3SystemInfo*" +Function,+,iso15693_3_poller_inventory,Iso15693_3Error,"Iso15693_3Poller*, uint8_t*" +Function,+,iso15693_3_poller_read_block,Iso15693_3Error,"Iso15693_3Poller*, uint8_t*, uint8_t, uint8_t" +Function,+,iso15693_3_poller_read_blocks,Iso15693_3Error,"Iso15693_3Poller*, uint8_t*, uint16_t, uint8_t" +Function,+,iso15693_3_poller_send_frame,Iso15693_3Error,"Iso15693_3Poller*, const BitBuffer*, BitBuffer*, uint32_t" Function,+,iso15693_3_reset,void,Iso15693_3Data* Function,+,iso15693_3_save,_Bool,"const Iso15693_3Data*, FlipperFormat*" Function,+,iso15693_3_set_uid,_Bool,"Iso15693_3Data*, const uint8_t*, size_t" From e7634d7563cd807caf5f522806e83c1d7393340b Mon Sep 17 00:00:00 2001 From: Nathan N Date: Mon, 29 Sep 2025 13:05:06 -0400 Subject: [PATCH 088/192] NFC: Ultralight C App Key Management, Dictionary Attack (#4271) * Upstream Ultralight C dictionary attack (squashed) * linter: formatting * unit_tests: nfc: split nfc data to named var * Fix mf_ultralight_poller_sync_read_card * linter: suppressed warnings on TODOs --------- Co-authored-by: hedger Co-authored-by: hedger --- .../debug/unit_tests/tests/nfc/nfc_test.c | 6 +- .../mf_ultralight/mf_ultralight.c | 51 ++- applications/main/nfc/nfc_app_i.h | 16 +- .../nfc/assets/mf_ultralight_c_dict.nfc | 55 ++++ .../main/nfc/scenes/nfc_scene_config.h | 7 + .../nfc/scenes/nfc_scene_delete_success.c | 4 + .../main/nfc/scenes/nfc_scene_extra_actions.c | 10 + .../nfc/scenes/nfc_scene_mf_classic_keys.c | 2 - .../scenes/nfc_scene_mf_classic_keys_add.c | 2 +- .../nfc_scene_mf_ultralight_c_dict_attack.c | 238 ++++++++++++++ .../scenes/nfc_scene_mf_ultralight_c_keys.c | 96 ++++++ .../nfc_scene_mf_ultralight_c_keys_add.c | 63 ++++ .../nfc_scene_mf_ultralight_c_keys_delete.c | 108 +++++++ .../nfc_scene_mf_ultralight_c_keys_list.c | 66 ++++ ...cene_mf_ultralight_c_keys_warn_duplicate.c | 49 +++ .../main/nfc/scenes/nfc_scene_save_success.c | 4 + applications/main/nfc/views/dict_attack.c | 299 +++++++++++------- applications/main/nfc/views/dict_attack.h | 13 + .../services/dolphin/helpers/dolphin_deed.c | 2 +- .../services/dolphin/helpers/dolphin_deed.h | 2 +- documentation/file_formats/NfcFileFormats.md | 26 +- .../mf_ultralight/mf_ultralight_poller.c | 94 +++++- .../mf_ultralight/mf_ultralight_poller.h | 21 +- .../mf_ultralight/mf_ultralight_poller_i.c | 6 +- lib/toolbox/keys_dict.c | 19 +- 25 files changed, 1094 insertions(+), 165 deletions(-) create mode 100644 applications/main/nfc/resources/nfc/assets/mf_ultralight_c_dict.nfc create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_delete.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_list.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_warn_duplicate.c diff --git a/applications/debug/unit_tests/tests/nfc/nfc_test.c b/applications/debug/unit_tests/tests/nfc/nfc_test.c index e028b8041..caeb3b9d7 100644 --- a/applications/debug/unit_tests/tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/tests/nfc/nfc_test.c @@ -268,9 +268,9 @@ static void mf_ultralight_reader_test(const char* path) { nfc_listener_stop(mfu_listener); nfc_listener_free(mfu_listener); - mu_assert( - mf_ultralight_is_equal(mfu_data, nfc_device_get_data(nfc_device, NfcProtocolMfUltralight)), - "Data not matches"); + MfUltralightData* mfu_other_data = + (MfUltralightData*)nfc_device_get_data(nfc_device, NfcProtocolMfUltralight); + mu_assert(mf_ultralight_is_equal(mfu_data, mfu_other_data), "Data mismatch"); mf_ultralight_free(mfu_data); nfc_device_free(nfc_device); diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index 3adf2a1f5..5ed564897 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -15,6 +15,7 @@ enum { SubmenuIndexUnlockByReader, SubmenuIndexUnlockByPassword, SubmenuIndexWrite, + SubmenuIndexDictAttack }; enum { @@ -150,7 +151,15 @@ static NfcCommand } if(!mf_ultralight_event->data->auth_context.skip_auth) { mf_ultralight_event->data->auth_context.password = instance->mf_ul_auth->password; - mf_ultralight_event->data->auth_context.tdes_key = instance->mf_ul_auth->tdes_key; + + // Only set tdes_key for Manual/Reader auth types, not for dictionary attacks + if(instance->mf_ul_auth->type == MfUltralightAuthTypeManual || + instance->mf_ul_auth->type == MfUltralightAuthTypeReader) { + mf_ultralight_event->data->key_request_data.key = instance->mf_ul_auth->tdes_key; + mf_ultralight_event->data->key_request_data.key_provided = true; + } else { + mf_ultralight_event->data->key_request_data.key_provided = false; + } } } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthSuccess) { instance->mf_ul_auth->pack = mf_ultralight_event->data->auth_context.pack; @@ -166,15 +175,31 @@ static void nfc_scene_read_on_enter_mf_ultralight(NfcApp* instance) { bool nfc_scene_read_on_event_mf_ultralight(NfcApp* instance, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventCardDetected) { - nfc_unlock_helper_card_detected_handler(instance); - } else if(event.event == NfcCustomEventPollerIncomplete) { - notification_message(instance->notifications, &sequence_semi_success); + if(event.event == NfcCustomEventPollerSuccess) { + notification_message(instance->notifications, &sequence_success); scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); dolphin_deed(DolphinDeedNfcReadSuccess); + return true; + } else if(event.event == NfcCustomEventPollerIncomplete) { + const MfUltralightData* data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight); + if(data->type == MfUltralightTypeMfulC && + instance->mf_ul_auth->type == MfUltralightAuthTypeNone) { + // Start dict attack for MFUL C cards only if no specific auth was attempted + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCDictAttack); + } else { + if(data->pages_read == data->pages_total) { + notification_message(instance->notifications, &sequence_success); + } else { + notification_message(instance->notifications, &sequence_semi_success); + } + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + } + return true; } } - return true; + return false; } static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instance) { @@ -190,6 +215,14 @@ static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instanc SubmenuIndexUnlock, nfc_protocol_support_common_submenu_callback, instance); + if(data->type == MfUltralightTypeMfulC) { + submenu_add_item( + submenu, + "Unlock with Dictionary", + SubmenuIndexDictAttack, + nfc_protocol_support_common_submenu_callback, + instance); + } } else if( data->type == MfUltralightTypeNTAG213 || data->type == MfUltralightTypeNTAG215 || data->type == MfUltralightTypeNTAG216 || data->type == MfUltralightTypeUL11 || @@ -258,6 +291,12 @@ static bool nfc_scene_read_and_saved_menu_on_event_mf_ultralight( } else if(event.event == SubmenuIndexCommonEdit) { scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); consumed = true; + } else if(event.event == SubmenuIndexDictAttack) { + if(!scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneMfUltralightCDictAttack)) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCDictAttack); + } + consumed = true; } } return consumed; diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 920127fef..f7af85ea8 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -47,6 +47,7 @@ #include #include #include +#include #include #include @@ -64,7 +65,7 @@ #define NFC_NAME_SIZE 22 #define NFC_TEXT_STORE_SIZE 128 -#define NFC_BYTE_INPUT_STORE_SIZE 10 +#define NFC_BYTE_INPUT_STORE_SIZE 16 #define NFC_LOG_SIZE_MAX (1024) #define NFC_APP_FOLDER EXT_PATH("nfc") #define NFC_APP_EXTENSION ".nfc" @@ -80,6 +81,10 @@ #define NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict.nfc") #define NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH \ (NFC_APP_FOLDER "/assets/mf_classic_dict_nested.nfc") +#define NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH \ + (NFC_APP_FOLDER "/assets/mf_ultralight_c_dict_user.nfc") +#define NFC_APP_MF_ULTRALIGHT_C_DICT_SYSTEM_PATH \ + (NFC_APP_FOLDER "/assets/mf_ultralight_c_dict.nfc") #define NFC_MFKEY32_APP_PATH (EXT_PATH("apps/NFC/mfkey.fap")) @@ -107,6 +112,14 @@ typedef struct { bool enhanced_dict; } NfcMfClassicDictAttackContext; +typedef struct { + KeysDict* dict; + bool auth_success; + bool is_card_present; + size_t dict_keys_total; + size_t dict_keys_current; +} NfcMfUltralightCDictContext; + struct NfcApp { DialogsApp* dialogs; Storage* storage; @@ -145,6 +158,7 @@ struct NfcApp { MfUltralightAuth* mf_ul_auth; SlixUnlock* slix_unlock; NfcMfClassicDictAttackContext nfc_dict_context; + NfcMfUltralightCDictContext mf_ultralight_c_dict_context; Mfkey32Logger* mfkey32_logger; MfUserDict* mf_user_dict; MfClassicKeyCache* mfc_key_cache; diff --git a/applications/main/nfc/resources/nfc/assets/mf_ultralight_c_dict.nfc b/applications/main/nfc/resources/nfc/assets/mf_ultralight_c_dict.nfc new file mode 100644 index 000000000..fa5dbb1fb --- /dev/null +++ b/applications/main/nfc/resources/nfc/assets/mf_ultralight_c_dict.nfc @@ -0,0 +1,55 @@ +# Sample Key (BREAKMEIFYOUCAN!) +425245414B4D454946594F5543414E21 +# Hexadecimal-Reversed Sample Key +12E4143455F495649454D4B414542524 +# Byte-Reversed Sample Key (!NACUOYFIEMKAERB) +214E4143554F594649454D4B41455242 +# Semnox Key (IEMKAERB!NACUOY ) +49454D4B41455242214E4143554F5900 +# Modified Semnox Key (IEMKAERB!NACUOYF) +49454D4B41455242214E4143554F5946 + +# Mix of Proxmark and ChameleonMiniLiveDebugger +00000000000000000000000000000000 +000102030405060708090A0B0C0D0E0F +01010101010101010101010101010101 +FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +00112233445566778899AABBCCDDEEFF +47454D5850524553534F53414D504C45 +79702553797025537970255379702553 +4E617468616E2E4C6920546564647920 +43464F494D48504E4C4359454E528841 +6AC292FAA1315B4D858AB3A3D7D5933A +404142434445464748494A4B4C4D4E4F +2B7E151628AED2A6ABF7158809CF4F3C +FBEED618357133667C85E08F7236A8DE +F7DDAC306AE266CCF90BC11EE46D513B +54686973206973206D79206B65792020 +A0A1A2A3A4A5A6A7A0A1A2A3A4A5A6A7 +B0B1B2B3B4B5B6B7B0B1B2B3B4B5B6B7 +B0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF +D3F7D3F7D3F7D3F7D3F7D3F7D3F7D3F7 +11111111111111111111111111111111 +22222222222222222222222222222222 +33333333333333333333333333333333 +44444444444444444444444444444444 +55555555555555555555555555555555 +66666666666666666666666666666666 +77777777777777777777777777777777 +88888888888888888888888888888888 +99999999999999999999999999999999 +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC +DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +0102030405060708090A0B0C0D0E0F10 +00010203040506070809101112131415 +01020304050607080910111213141516 +16151413121110090807060504030201 +15141312111009080706050403020100 +0F0E0D0C0B0A09080706050403020100 +100F0E0D0C0B0A090807060504030201 +303132333435363738393A3B3C3D3E3F +9CABF398358405AE2F0E2B3D31C99A8A +605F5E5D5C5B5A59605F5E5D5C5B5A59 diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index 83c8ffeed..399d59b92 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -25,6 +25,7 @@ ADD_SCENE(nfc, retry_confirm, RetryConfirm) ADD_SCENE(nfc, exit_confirm, ExitConfirm) ADD_SCENE(nfc, save_confirm, SaveConfirm) +ADD_SCENE(nfc, mf_ultralight_c_dict_attack, MfUltralightCDictAttack) ADD_SCENE(nfc, mf_ultralight_write, MfUltralightWrite) ADD_SCENE(nfc, mf_ultralight_write_success, MfUltralightWriteSuccess) ADD_SCENE(nfc, mf_ultralight_write_fail, MfUltralightWriteFail) @@ -57,6 +58,12 @@ ADD_SCENE(nfc, mf_classic_keys_delete, MfClassicKeysDelete) ADD_SCENE(nfc, mf_classic_keys_add, MfClassicKeysAdd) ADD_SCENE(nfc, mf_classic_keys_warn_duplicate, MfClassicKeysWarnDuplicate) +ADD_SCENE(nfc, mf_ultralight_c_keys, MfUltralightCKeys) +ADD_SCENE(nfc, mf_ultralight_c_keys_list, MfUltralightCKeysList) +ADD_SCENE(nfc, mf_ultralight_c_keys_delete, MfUltralightCKeysDelete) +ADD_SCENE(nfc, mf_ultralight_c_keys_add, MfUltralightCKeysAdd) +ADD_SCENE(nfc, mf_ultralight_c_keys_warn_duplicate, MfUltralightCKeysWarnDuplicate) + ADD_SCENE(nfc, set_type, SetType) ADD_SCENE(nfc, set_sak, SetSak) ADD_SCENE(nfc, set_atqa, SetAtqa) diff --git a/applications/main/nfc/scenes/nfc_scene_delete_success.c b/applications/main/nfc/scenes/nfc_scene_delete_success.c index d41e52549..d8308addd 100644 --- a/applications/main/nfc/scenes/nfc_scene_delete_success.c +++ b/applications/main/nfc/scenes/nfc_scene_delete_success.c @@ -28,6 +28,10 @@ bool nfc_scene_delete_success_on_event(void* context, SceneManagerEvent event) { if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicKeys)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneMfClassicKeys); + } else if(scene_manager_has_previous_scene( + nfc->scene_manager, NfcSceneMfUltralightCKeys)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneMfUltralightCKeys); } else { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneFileSelect); diff --git a/applications/main/nfc/scenes/nfc_scene_extra_actions.c b/applications/main/nfc/scenes/nfc_scene_extra_actions.c index 2943c0c55..6720b2d7b 100644 --- a/applications/main/nfc/scenes/nfc_scene_extra_actions.c +++ b/applications/main/nfc/scenes/nfc_scene_extra_actions.c @@ -3,6 +3,7 @@ enum SubmenuIndex { SubmenuIndexReadCardType, SubmenuIndexMfClassicKeys, + SubmenuIndexMfUltralightCKeys, SubmenuIndexMfUltralightUnlock, SubmenuIndexSlixUnlock, }; @@ -29,6 +30,12 @@ void nfc_scene_extra_actions_on_enter(void* context) { SubmenuIndexMfClassicKeys, nfc_scene_extra_actions_submenu_callback, instance); + submenu_add_item( + submenu, + "MIFARE Ultralight C Keys", + SubmenuIndexMfUltralightCKeys, + nfc_scene_extra_actions_submenu_callback, + instance); submenu_add_item( submenu, "Unlock NTAG/Ultralight", @@ -54,6 +61,9 @@ bool nfc_scene_extra_actions_on_event(void* context, SceneManagerEvent event) { if(event.event == SubmenuIndexMfClassicKeys) { scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicKeys); consumed = true; + } else if(event.event == SubmenuIndexMfUltralightCKeys) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCKeys); + consumed = true; } else if(event.event == SubmenuIndexMfUltralightUnlock) { mf_ultralight_auth_reset(instance->mf_ul_auth); scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c index eaa054149..7ee203285 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c @@ -1,7 +1,5 @@ #include "../nfc_app_i.h" -#define NFC_SCENE_MF_CLASSIC_KEYS_MAX (100) - void nfc_scene_mf_classic_keys_widget_callback(GuiButtonType result, InputType type, void* context) { NfcApp* instance = context; if(type == InputTypeShort) { diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c index a963f44ac..131b5e230 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_add.c @@ -39,7 +39,7 @@ bool nfc_scene_mf_classic_keys_add_on_event(void* context, SceneManagerEvent eve instance->scene_manager, NfcSceneMfClassicKeysWarnDuplicate); } else if(keys_dict_add_key(dict, key.data, sizeof(MfClassicKey))) { scene_manager_next_scene(instance->scene_manager, NfcSceneSaveSuccess); - dolphin_deed(DolphinDeedNfcMfcAdd); + dolphin_deed(DolphinDeedNfcKeyAdd); } else { scene_manager_previous_scene(instance->scene_manager); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c new file mode 100644 index 000000000..843261142 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c @@ -0,0 +1,238 @@ +#include "../nfc_app_i.h" +#include + +#define TAG "NfcMfUlCDictAttack" + +// TODO: Support card_detected properly -nofl + +enum { + DictAttackStateUserDictInProgress, + DictAttackStateSystemDictInProgress, +}; + +NfcCommand nfc_mf_ultralight_c_dict_attack_worker_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.protocol == NfcProtocolMfUltralight); + NfcCommand command = NfcCommandContinue; + NfcApp* instance = context; + MfUltralightPollerEvent* poller_event = event.event_data; + + if(poller_event->type == MfUltralightPollerEventTypeRequestMode) { + poller_event->data->poller_mode = MfUltralightPollerModeDictAttack; + command = NfcCommandContinue; + } else if(poller_event->type == MfUltralightPollerEventTypeRequestKey) { + MfUltralightC3DesAuthKey key = {}; + if(keys_dict_get_next_key( + instance->mf_ultralight_c_dict_context.dict, + key.data, + sizeof(MfUltralightC3DesAuthKey))) { + poller_event->data->key_request_data.key = key; + poller_event->data->key_request_data.key_provided = true; + instance->mf_ultralight_c_dict_context.dict_keys_current++; + + if(instance->mf_ultralight_c_dict_context.dict_keys_current % 10 == 0) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); + } + } else { + poller_event->data->key_request_data.key_provided = false; + } + } else if(poller_event->type == MfUltralightPollerEventTypeReadSuccess) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(instance->poller)); + // Check if this is a successful authentication by looking at the poller's auth context + const MfUltralightData* data = nfc_poller_get_data(instance->poller); + + // Update page information + dict_attack_set_pages_read(instance->dict_attack, data->pages_read); + dict_attack_set_pages_total(instance->dict_attack, data->pages_total); + + if(data->pages_read == data->pages_total) { + // Full read indicates successful authentication in dict attack mode + instance->mf_ultralight_c_dict_context.auth_success = true; + dict_attack_set_key_found(instance->dict_attack, true); + } + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventDictAttackComplete); + command = NfcCommandStop; + } + return command; +} + +void nfc_scene_mf_ultralight_c_dict_attack_dict_attack_result_callback( + DictAttackEvent event, + void* context) { + furi_assert(context); + NfcApp* instance = context; + if(event == DictAttackEventSkipPressed) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventDictAttackSkip); + } +} + +void nfc_scene_mf_ultralight_c_dict_attack_prepare_view(NfcApp* instance) { + uint32_t state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightCDictAttack); + + // Set attack type to Ultralight C + dict_attack_set_type(instance->dict_attack, DictAttackTypeMfUltralightC); + + if(state == DictAttackStateUserDictInProgress) { + do { + if(!keys_dict_check_presence(NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH)) { + state = DictAttackStateSystemDictInProgress; + break; + } + instance->mf_ultralight_c_dict_context.dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + if(keys_dict_get_total_keys(instance->mf_ultralight_c_dict_context.dict) == 0) { + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + state = DictAttackStateSystemDictInProgress; + break; + } + dict_attack_set_header(instance->dict_attack, "MFUL C User Dictionary"); + } while(false); + } + if(state == DictAttackStateSystemDictInProgress) { + instance->mf_ultralight_c_dict_context.dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_SYSTEM_PATH, + KeysDictModeOpenExisting, + sizeof(MfUltralightC3DesAuthKey)); + dict_attack_set_header(instance->dict_attack, "MFUL C System Dictionary"); + } + + instance->mf_ultralight_c_dict_context.dict_keys_total = + keys_dict_get_total_keys(instance->mf_ultralight_c_dict_context.dict); + dict_attack_set_total_dict_keys( + instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_total); + instance->mf_ultralight_c_dict_context.dict_keys_current = 0; + dict_attack_set_current_dict_key( + instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_current); + + // Set initial Ultralight C specific values + dict_attack_set_key_found(instance->dict_attack, false); + dict_attack_set_pages_total(instance->dict_attack, 48); // Ultralight C page count + dict_attack_set_pages_read(instance->dict_attack, 0); + + dict_attack_set_callback( + instance->dict_attack, + nfc_scene_mf_ultralight_c_dict_attack_dict_attack_result_callback, + instance); + scene_manager_set_scene_state(instance->scene_manager, NfcSceneMfUltralightCDictAttack, state); +} + +void nfc_scene_mf_ultralight_c_dict_attack_on_enter(void* context) { + NfcApp* instance = context; + + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfUltralightCDictAttack, + DictAttackStateUserDictInProgress); + nfc_scene_mf_ultralight_c_dict_attack_prepare_view(instance); + + // Setup and start worker + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); + nfc_poller_start(instance->poller, nfc_mf_ultralight_c_dict_attack_worker_callback, instance); + + dict_attack_set_card_state(instance->dict_attack, true); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewDictAttack); + nfc_blink_read_start(instance); +} + +void nfc_scene_mf_ul_c_dict_attack_update_view(NfcApp* instance) { + dict_attack_set_card_state( + instance->dict_attack, instance->mf_ultralight_c_dict_context.is_card_present); + dict_attack_set_current_dict_key( + instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_current); +} + +bool nfc_scene_mf_ultralight_c_dict_attack_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + uint32_t state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightCDictAttack); + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventDictAttackComplete) { + if(state == DictAttackStateUserDictInProgress) { + if(instance->mf_ultralight_c_dict_context.auth_success) { + notification_message(instance->notifications, &sequence_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + consumed = true; + } else { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfUltralightCDictAttack, + DictAttackStateSystemDictInProgress); + nfc_scene_mf_ultralight_c_dict_attack_prepare_view(instance); + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); + nfc_poller_start( + instance->poller, + nfc_mf_ultralight_c_dict_attack_worker_callback, + instance); + consumed = true; + } + } else { + // Could check if card is fully read here like MFC dict attack, but found key means fully read + if(instance->mf_ultralight_c_dict_context.auth_success) { + notification_message(instance->notifications, &sequence_success); + } else { + notification_message(instance->notifications, &sequence_semi_success); + } + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + consumed = true; + } + } else if(event.event == NfcCustomEventDictAttackDataUpdate) { + dict_attack_set_current_dict_key( + instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_current); + consumed = true; + } else if(event.event == NfcCustomEventDictAttackSkip) { + if(state == DictAttackStateUserDictInProgress) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfUltralightCDictAttack, + DictAttackStateSystemDictInProgress); + nfc_scene_mf_ultralight_c_dict_attack_prepare_view(instance); + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); + nfc_poller_start( + instance->poller, nfc_mf_ultralight_c_dict_attack_worker_callback, instance); + } else { + notification_message(instance->notifications, &sequence_semi_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + } + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + scene_manager_next_scene(instance->scene_manager, NfcSceneExitConfirm); + consumed = true; + } + return consumed; +} + +void nfc_scene_mf_ultralight_c_dict_attack_on_exit(void* context) { + NfcApp* instance = context; + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfUltralightCDictAttack, + DictAttackStateUserDictInProgress); + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + instance->mf_ultralight_c_dict_context.dict_keys_total = 0; + instance->mf_ultralight_c_dict_context.dict_keys_current = 0; + instance->mf_ultralight_c_dict_context.auth_success = false; + instance->mf_ultralight_c_dict_context.is_card_present = false; + nfc_blink_stop(instance); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys.c new file mode 100644 index 000000000..9bf96f0b4 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys.c @@ -0,0 +1,96 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_ultralight_c_keys_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + NfcApp* instance = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(instance->view_dispatcher, result); + } +} + +void nfc_scene_mf_ultralight_c_keys_on_enter(void* context) { + NfcApp* instance = context; + + // Load flipper dict keys total + uint32_t flipper_dict_keys_total = 0; + KeysDict* dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_SYSTEM_PATH, + KeysDictModeOpenExisting, + sizeof(MfUltralightC3DesAuthKey)); + flipper_dict_keys_total = keys_dict_get_total_keys(dict); + keys_dict_free(dict); + + // Load user dict keys total + uint32_t user_dict_keys_total = 0; + dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + user_dict_keys_total = keys_dict_get_total_keys(dict); + keys_dict_free(dict); + + FuriString* temp_str = furi_string_alloc(); + widget_add_string_element( + instance->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "MIFARE Ultralight C Keys"); + furi_string_printf(temp_str, "System dict: %lu", flipper_dict_keys_total); + widget_add_string_element( + instance->widget, + 0, + 20, + AlignLeft, + AlignTop, + FontSecondary, + furi_string_get_cstr(temp_str)); + furi_string_printf(temp_str, "User dict: %lu", user_dict_keys_total); + widget_add_string_element( + instance->widget, + 0, + 32, + AlignLeft, + AlignTop, + FontSecondary, + furi_string_get_cstr(temp_str)); + widget_add_icon_element(instance->widget, 87, 13, &I_Keychain_39x36); + widget_add_button_element( + instance->widget, + GuiButtonTypeCenter, + "Add", + nfc_scene_mf_ultralight_c_keys_widget_callback, + instance); + if(user_dict_keys_total > 0) { + widget_add_button_element( + instance->widget, + GuiButtonTypeRight, + "List", + nfc_scene_mf_ultralight_c_keys_widget_callback, + instance); + } + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_mf_ultralight_c_keys_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeCenter) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCKeysAdd); + consumed = true; + } else if(event.event == GuiButtonTypeRight) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCKeysList); + consumed = true; + } + } + + return consumed; +} + +void nfc_scene_mf_ultralight_c_keys_on_exit(void* context) { + NfcApp* instance = context; + + widget_reset(instance->widget); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c new file mode 100644 index 000000000..63fdaed49 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c @@ -0,0 +1,63 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_ultralight_c_keys_add_byte_input_callback(void* context) { + NfcApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventByteInputDone); +} + +void nfc_scene_mf_ultralight_c_keys_add_on_enter(void* context) { + NfcApp* instance = context; + + // Setup view + ByteInput* byte_input = instance->byte_input; + byte_input_set_header_text(byte_input, "Enter the key in hex"); + byte_input_set_result_callback( + byte_input, + nfc_scene_mf_ultralight_c_keys_add_byte_input_callback, + NULL, + instance, + instance->byte_input_store, + sizeof(MfUltralightC3DesAuthKey)); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewByteInput); +} + +bool nfc_scene_mf_ultralight_c_keys_add_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventByteInputDone) { + // Add key to dict + KeysDict* dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + + MfUltralightC3DesAuthKey key = {}; + memcpy(key.data, instance->byte_input_store, sizeof(MfUltralightC3DesAuthKey)); + if(keys_dict_is_key_present(dict, key.data, sizeof(MfUltralightC3DesAuthKey))) { + scene_manager_next_scene( + instance->scene_manager, NfcSceneMfUltralightCKeysWarnDuplicate); + } else if(keys_dict_add_key(dict, key.data, sizeof(MfUltralightC3DesAuthKey))) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSaveSuccess); + dolphin_deed(DolphinDeedNfcKeyAdd); + } else { + scene_manager_previous_scene(instance->scene_manager); + } + + keys_dict_free(dict); + consumed = true; + } + } + + return consumed; +} + +void nfc_scene_mf_ultralight_c_keys_add_on_exit(void* context) { + NfcApp* instance = context; + + // Clear view + byte_input_set_result_callback(instance->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(instance->byte_input, ""); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_delete.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_delete.c new file mode 100644 index 000000000..db2903939 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_delete.c @@ -0,0 +1,108 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_ultralight_c_keys_delete_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + NfcApp* instance = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(instance->view_dispatcher, result); + } +} + +void nfc_scene_mf_ultralight_c_keys_delete_on_enter(void* context) { + NfcApp* instance = context; + + uint32_t key_index = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightCKeysDelete); + FuriString* key_str = furi_string_alloc(); + + widget_add_string_element( + instance->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Delete this key?"); + widget_add_button_element( + instance->widget, + GuiButtonTypeLeft, + "Cancel", + nfc_scene_mf_ultralight_c_keys_delete_widget_callback, + instance); + widget_add_button_element( + instance->widget, + GuiButtonTypeRight, + "Delete", + nfc_scene_mf_ultralight_c_keys_delete_widget_callback, + instance); + + KeysDict* mf_ultralight_c_user_dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + size_t dict_keys_num = keys_dict_get_total_keys(mf_ultralight_c_user_dict); + furi_assert(key_index < dict_keys_num); + MfUltralightC3DesAuthKey stack_key; + for(size_t i = 0; i < (key_index + 1); i++) { + bool key_loaded = keys_dict_get_next_key( + mf_ultralight_c_user_dict, stack_key.data, sizeof(MfUltralightC3DesAuthKey)); + furi_assert(key_loaded); + } + furi_string_reset(key_str); + for(size_t i = 0; i < sizeof(MfUltralightC3DesAuthKey); i++) { + furi_string_cat_printf(key_str, "%02X", stack_key.data[i]); + } + + widget_add_string_element( + instance->widget, + 64, + 32, + AlignCenter, + AlignCenter, + FontSecondary, + furi_string_get_cstr(key_str)); + + keys_dict_free(mf_ultralight_c_user_dict); + furi_string_free(key_str); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_mf_ultralight_c_keys_delete_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeRight) { + uint32_t key_index = scene_manager_get_scene_state( + instance->scene_manager, NfcSceneMfUltralightCKeysDelete); + KeysDict* mf_ultralight_c_user_dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + size_t dict_keys_num = keys_dict_get_total_keys(mf_ultralight_c_user_dict); + furi_assert(key_index < dict_keys_num); + MfUltralightC3DesAuthKey stack_key; + for(size_t i = 0; i < (key_index + 1); i++) { + bool key_loaded = keys_dict_get_next_key( + mf_ultralight_c_user_dict, stack_key.data, sizeof(MfUltralightC3DesAuthKey)); + furi_assert(key_loaded); + } + bool key_delete_success = keys_dict_delete_key( + mf_ultralight_c_user_dict, stack_key.data, sizeof(MfUltralightC3DesAuthKey)); + keys_dict_free(mf_ultralight_c_user_dict); + if(key_delete_success) { + scene_manager_next_scene(instance->scene_manager, NfcSceneDeleteSuccess); + } else { + scene_manager_previous_scene(instance->scene_manager); + } + } else if(event.event == GuiButtonTypeLeft) { + scene_manager_previous_scene(instance->scene_manager); + } + consumed = true; + } + + return consumed; +} + +void nfc_scene_mf_ultralight_c_keys_delete_on_exit(void* context) { + NfcApp* instance = context; + + widget_reset(instance->widget); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_list.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_list.c new file mode 100644 index 000000000..e2fda3aea --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_list.c @@ -0,0 +1,66 @@ +#include "../nfc_app_i.h" + +#define NFC_SCENE_MF_ULTRALIGHT_C_KEYS_LIST_MAX (100) + +void nfc_scene_mf_ultralight_c_keys_list_submenu_callback(void* context, uint32_t index) { + NfcApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, index); +} + +void nfc_scene_mf_ultralight_c_keys_list_on_enter(void* context) { + NfcApp* instance = context; + + KeysDict* mf_ultralight_c_user_dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + + submenu_set_header(instance->submenu, "Select key to delete:"); + FuriString* temp_str = furi_string_alloc(); + + size_t dict_keys_num = keys_dict_get_total_keys(mf_ultralight_c_user_dict); + size_t keys_num = MIN((size_t)NFC_SCENE_MF_ULTRALIGHT_C_KEYS_LIST_MAX, dict_keys_num); + MfUltralightC3DesAuthKey stack_key; + + if(keys_num > 0) { + for(size_t i = 0; i < keys_num; i++) { + bool key_loaded = keys_dict_get_next_key( + mf_ultralight_c_user_dict, stack_key.data, sizeof(MfUltralightC3DesAuthKey)); + furi_assert(key_loaded); + furi_string_reset(temp_str); + for(size_t i = 0; i < sizeof(MfUltralightC3DesAuthKey); i++) { + furi_string_cat_printf(temp_str, "%02X", stack_key.data[i]); + } + submenu_add_item( + instance->submenu, + furi_string_get_cstr(temp_str), + i, + nfc_scene_mf_ultralight_c_keys_list_submenu_callback, + instance); + } + } + keys_dict_free(mf_ultralight_c_user_dict); + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_mf_ultralight_c_keys_list_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + + bool consumed = false; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneMfUltralightCKeysDelete, event.event); + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCKeysDelete); + } + + return consumed; +} + +void nfc_scene_mf_ultralight_c_keys_list_on_exit(void* context) { + NfcApp* instance = context; + + submenu_reset(instance->submenu); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_warn_duplicate.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_warn_duplicate.c new file mode 100644 index 000000000..c8881e5d4 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_warn_duplicate.c @@ -0,0 +1,49 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_ultralight_c_keys_warn_duplicate_popup_callback(void* context) { + NfcApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventViewExit); +} + +void nfc_scene_mf_ultralight_c_keys_warn_duplicate_on_enter(void* context) { + NfcApp* instance = context; + + // Setup view + Popup* popup = instance->popup; + popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42); + popup_set_header(popup, "Key Already Exists!", 64, 3, AlignCenter, AlignTop); + popup_set_text( + popup, + "Please enter a\n" + "different key.", + 4, + 24, + AlignLeft, + AlignTop); + popup_set_timeout(popup, 1500); + popup_set_context(popup, instance); + popup_set_callback(popup, nfc_scene_mf_ultralight_c_keys_warn_duplicate_popup_callback); + popup_enable_timeout(popup); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); +} + +bool nfc_scene_mf_ultralight_c_keys_warn_duplicate_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventViewExit) { + consumed = scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneMfUltralightCKeysAdd); + } + } + + return consumed; +} + +void nfc_scene_mf_ultralight_c_keys_warn_duplicate_on_exit(void* context) { + NfcApp* instance = context; + + popup_reset(instance->popup); +} diff --git a/applications/main/nfc/scenes/nfc_scene_save_success.c b/applications/main/nfc/scenes/nfc_scene_save_success.c index 5f812ba9c..06999fe9f 100644 --- a/applications/main/nfc/scenes/nfc_scene_save_success.c +++ b/applications/main/nfc/scenes/nfc_scene_save_success.c @@ -28,6 +28,10 @@ bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) { if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicKeys)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneMfClassicKeys); + } else if(scene_manager_has_previous_scene( + nfc->scene_manager, NfcSceneMfUltralightCKeys)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneMfUltralightCKeys); } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSaveConfirm)) { NfcSceneSaveConfirmState scene_state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneSaveConfirm); diff --git a/applications/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index 726076972..a71e466f8 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -13,12 +13,13 @@ struct DictAttack { typedef struct { FuriString* header; bool card_detected; + DictAttackType attack_type; + + // MIFARE Classic specific uint8_t sectors_total; uint8_t sectors_read; uint8_t current_sector; uint8_t keys_found; - size_t dict_keys_total; - size_t dict_keys_current; bool is_key_attack; uint8_t key_attack_current_sector; MfClassicNestedPhase nested_phase; @@ -26,8 +27,150 @@ typedef struct { MfClassicBackdoor backdoor; uint16_t nested_target_key; uint16_t msb_count; + + // Ultralight C specific + uint8_t pages_total; + uint8_t pages_read; + bool key_found; + + // Common + size_t dict_keys_total; + size_t dict_keys_current; } DictAttackViewModel; +static void dict_attack_draw_mf_classic(Canvas* canvas, DictAttackViewModel* m) { + char draw_str[32] = {}; + canvas_set_font(canvas, FontSecondary); + + switch(m->nested_phase) { + case MfClassicNestedPhaseAnalyzePRNG: + furi_string_set(m->header, "PRNG Analysis"); + break; + case MfClassicNestedPhaseDictAttack: + case MfClassicNestedPhaseDictAttackVerify: + case MfClassicNestedPhaseDictAttackResume: + furi_string_set(m->header, "Nested Dictionary"); + break; + case MfClassicNestedPhaseCalibrate: + case MfClassicNestedPhaseRecalibrate: + furi_string_set(m->header, "Calibration"); + break; + case MfClassicNestedPhaseCollectNtEnc: + furi_string_set(m->header, "Nonce Collection"); + break; + default: + break; + } + + if(m->prng_type == MfClassicPrngTypeHard) { + furi_string_cat(m->header, " (Hard)"); + } + + if(m->backdoor != MfClassicBackdoorNone && m->backdoor != MfClassicBackdoorUnknown) { + if(m->nested_phase != MfClassicNestedPhaseNone) { + furi_string_cat(m->header, " (Backdoor)"); + } else { + furi_string_set(m->header, "Backdoor Read"); + } + } + + canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(m->header)); + if(m->nested_phase == MfClassicNestedPhaseCollectNtEnc) { + uint8_t nonce_sector = + m->nested_target_key / (m->prng_type == MfClassicPrngTypeWeak ? 4 : 2); + snprintf(draw_str, sizeof(draw_str), "Collecting from sector: %d", nonce_sector); + canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); + } else if(m->is_key_attack) { + snprintf( + draw_str, + sizeof(draw_str), + "Reuse key check for sector: %d", + m->key_attack_current_sector); + } else { + snprintf(draw_str, sizeof(draw_str), "Unlocking sector: %d", m->current_sector); + } + canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); + float dict_progress = 0; + if(m->nested_phase == MfClassicNestedPhaseAnalyzePRNG || + m->nested_phase == MfClassicNestedPhaseDictAttack || + m->nested_phase == MfClassicNestedPhaseDictAttackVerify || + m->nested_phase == MfClassicNestedPhaseDictAttackResume) { + // Phase: Nested dictionary attack + uint8_t target_sector = + m->nested_target_key / (m->prng_type == MfClassicPrngTypeWeak ? 2 : 16); + dict_progress = (float)(target_sector) / (float)(m->sectors_total); + snprintf(draw_str, sizeof(draw_str), "%d/%d", target_sector, m->sectors_total); + } else if( + m->nested_phase == MfClassicNestedPhaseCalibrate || + m->nested_phase == MfClassicNestedPhaseRecalibrate || + m->nested_phase == MfClassicNestedPhaseCollectNtEnc) { + // Phase: Nonce collection + if(m->prng_type == MfClassicPrngTypeWeak) { + uint8_t target_sector = m->nested_target_key / 4; + dict_progress = (float)(target_sector) / (float)(m->sectors_total); + snprintf(draw_str, sizeof(draw_str), "%d/%d", target_sector, m->sectors_total); + } else { + uint16_t max_msb = UINT8_MAX + 1; + dict_progress = (float)(m->msb_count) / (float)(max_msb); + snprintf(draw_str, sizeof(draw_str), "%d/%d", m->msb_count, max_msb); + } + } else { + dict_progress = m->dict_keys_total == 0 ? + 0 : + (float)(m->dict_keys_current) / (float)(m->dict_keys_total); + if(m->dict_keys_current == 0) { + // Cause when people see 0 they think it's broken + snprintf(draw_str, sizeof(draw_str), "%d/%zu", 1, m->dict_keys_total); + } else { + snprintf( + draw_str, sizeof(draw_str), "%zu/%zu", m->dict_keys_current, m->dict_keys_total); + } + } + if(dict_progress > 1.0f) { + dict_progress = 1.0f; + } + elements_progress_bar_with_text(canvas, 0, 20, 128, dict_progress, draw_str); + canvas_set_font(canvas, FontSecondary); + snprintf( + draw_str, + sizeof(draw_str), + "Keys found: %d/%d", + m->keys_found, + m->sectors_total * NFC_CLASSIC_KEYS_PER_SECTOR); + canvas_draw_str_aligned(canvas, 0, 33, AlignLeft, AlignTop, draw_str); + snprintf(draw_str, sizeof(draw_str), "Sectors Read: %d/%d", m->sectors_read, m->sectors_total); + canvas_draw_str_aligned(canvas, 0, 43, AlignLeft, AlignTop, draw_str); +} + +static void dict_attack_draw_mf_ultralight_c(Canvas* canvas, DictAttackViewModel* m) { + char draw_str[32] = {}; + canvas_set_font(canvas, FontSecondary); + + canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(m->header)); + + snprintf(draw_str, sizeof(draw_str), "Trying keys"); + canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); + + float dict_progress = + m->dict_keys_total == 0 ? 0 : (float)(m->dict_keys_current) / (float)(m->dict_keys_total); + if(m->dict_keys_current == 0) { + snprintf(draw_str, sizeof(draw_str), "%d/%zu", 1, m->dict_keys_total); + } else { + snprintf(draw_str, sizeof(draw_str), "%zu/%zu", m->dict_keys_current, m->dict_keys_total); + } + if(dict_progress > 1.0f) { + dict_progress = 1.0f; + } + elements_progress_bar_with_text(canvas, 0, 20, 128, dict_progress, draw_str); + + canvas_set_font(canvas, FontSecondary); + snprintf(draw_str, sizeof(draw_str), "Key found: %s", m->key_found ? "Yes" : "No"); + canvas_draw_str_aligned(canvas, 0, 33, AlignLeft, AlignTop, draw_str); + + snprintf(draw_str, sizeof(draw_str), "Pages read: %d/%d", m->pages_read, m->pages_total); + canvas_draw_str_aligned(canvas, 0, 43, AlignLeft, AlignTop, draw_str); +} + static void dict_attack_draw_callback(Canvas* canvas, void* model) { DictAttackViewModel* m = model; if(!m->card_detected) { @@ -37,113 +180,11 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { elements_multiline_text_aligned( canvas, 64, 23, AlignCenter, AlignTop, "Make sure the tag is\npositioned correctly."); } else { - char draw_str[32] = {}; - canvas_set_font(canvas, FontSecondary); - - switch(m->nested_phase) { - case MfClassicNestedPhaseAnalyzePRNG: - furi_string_set(m->header, "PRNG Analysis"); - break; - case MfClassicNestedPhaseDictAttack: - case MfClassicNestedPhaseDictAttackVerify: - case MfClassicNestedPhaseDictAttackResume: - furi_string_set(m->header, "Nested Dictionary"); - break; - case MfClassicNestedPhaseCalibrate: - case MfClassicNestedPhaseRecalibrate: - furi_string_set(m->header, "Calibration"); - break; - case MfClassicNestedPhaseCollectNtEnc: - furi_string_set(m->header, "Nonce Collection"); - break; - default: - break; + if(m->attack_type == DictAttackTypeMfClassic) { + dict_attack_draw_mf_classic(canvas, m); + } else if(m->attack_type == DictAttackTypeMfUltralightC) { + dict_attack_draw_mf_ultralight_c(canvas, m); } - - if(m->prng_type == MfClassicPrngTypeHard) { - furi_string_cat(m->header, " (Hard)"); - } - - if(m->backdoor != MfClassicBackdoorNone && m->backdoor != MfClassicBackdoorUnknown) { - if(m->nested_phase != MfClassicNestedPhaseNone) { - furi_string_cat(m->header, " (Backdoor)"); - } else { - furi_string_set(m->header, "Backdoor Read"); - } - } - - canvas_draw_str_aligned( - canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(m->header)); - if(m->nested_phase == MfClassicNestedPhaseCollectNtEnc) { - uint8_t nonce_sector = - m->nested_target_key / (m->prng_type == MfClassicPrngTypeWeak ? 4 : 2); - snprintf(draw_str, sizeof(draw_str), "Collecting from sector: %d", nonce_sector); - canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); - } else if(m->is_key_attack) { - snprintf( - draw_str, - sizeof(draw_str), - "Reuse key check for sector: %d", - m->key_attack_current_sector); - } else { - snprintf(draw_str, sizeof(draw_str), "Unlocking sector: %d", m->current_sector); - } - canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); - float dict_progress = 0; - if(m->nested_phase == MfClassicNestedPhaseAnalyzePRNG || - m->nested_phase == MfClassicNestedPhaseDictAttack || - m->nested_phase == MfClassicNestedPhaseDictAttackVerify || - m->nested_phase == MfClassicNestedPhaseDictAttackResume) { - // Phase: Nested dictionary attack - uint8_t target_sector = - m->nested_target_key / (m->prng_type == MfClassicPrngTypeWeak ? 2 : 16); - dict_progress = (float)(target_sector) / (float)(m->sectors_total); - snprintf(draw_str, sizeof(draw_str), "%d/%d", target_sector, m->sectors_total); - } else if( - m->nested_phase == MfClassicNestedPhaseCalibrate || - m->nested_phase == MfClassicNestedPhaseRecalibrate || - m->nested_phase == MfClassicNestedPhaseCollectNtEnc) { - // Phase: Nonce collection - if(m->prng_type == MfClassicPrngTypeWeak) { - uint8_t target_sector = m->nested_target_key / 4; - dict_progress = (float)(target_sector) / (float)(m->sectors_total); - snprintf(draw_str, sizeof(draw_str), "%d/%d", target_sector, m->sectors_total); - } else { - uint16_t max_msb = UINT8_MAX + 1; - dict_progress = (float)(m->msb_count) / (float)(max_msb); - snprintf(draw_str, sizeof(draw_str), "%d/%d", m->msb_count, max_msb); - } - } else { - dict_progress = m->dict_keys_total == 0 ? - 0 : - (float)(m->dict_keys_current) / (float)(m->dict_keys_total); - if(m->dict_keys_current == 0) { - // Cause when people see 0 they think it's broken - snprintf(draw_str, sizeof(draw_str), "%d/%zu", 1, m->dict_keys_total); - } else { - snprintf( - draw_str, - sizeof(draw_str), - "%zu/%zu", - m->dict_keys_current, - m->dict_keys_total); - } - } - if(dict_progress > 1.0f) { - dict_progress = 1.0f; - } - elements_progress_bar_with_text(canvas, 0, 20, 128, dict_progress, draw_str); - canvas_set_font(canvas, FontSecondary); - snprintf( - draw_str, - sizeof(draw_str), - "Keys found: %d/%d", - m->keys_found, - m->sectors_total * NFC_CLASSIC_KEYS_PER_SECTOR); - canvas_draw_str_aligned(canvas, 0, 33, AlignLeft, AlignTop, draw_str); - snprintf( - draw_str, sizeof(draw_str), "Sectors Read: %d/%d", m->sectors_read, m->sectors_total); - canvas_draw_str_aligned(canvas, 0, 43, AlignLeft, AlignTop, draw_str); } elements_button_center(canvas, "Skip"); } @@ -195,18 +236,28 @@ void dict_attack_reset(DictAttack* instance) { instance->view, DictAttackViewModel * model, { + model->attack_type = DictAttackTypeMfClassic; + + // MIFARE Classic fields model->sectors_total = 0; model->sectors_read = 0; model->current_sector = 0; model->keys_found = 0; - model->dict_keys_total = 0; - model->dict_keys_current = 0; model->is_key_attack = false; model->nested_phase = MfClassicNestedPhaseNone; model->prng_type = MfClassicPrngTypeUnknown; model->backdoor = MfClassicBackdoorUnknown; model->nested_target_key = 0; model->msb_count = 0; + + // Ultralight C fields + model->pages_total = 0; + model->pages_read = 0; + model->key_found = false; + + // Common fields + model->dict_keys_total = 0; + model->dict_keys_current = 0; furi_string_reset(model->header); }, false); @@ -355,3 +406,31 @@ void dict_attack_set_msb_count(DictAttack* instance, uint16_t msb_count) { with_view_model( instance->view, DictAttackViewModel * model, { model->msb_count = msb_count; }, true); } + +void dict_attack_set_type(DictAttack* instance, DictAttackType type) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->attack_type = type; }, true); +} + +void dict_attack_set_pages_total(DictAttack* instance, uint8_t pages_total) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->pages_total = pages_total; }, true); +} + +void dict_attack_set_pages_read(DictAttack* instance, uint8_t pages_read) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->pages_read = pages_read; }, true); +} + +void dict_attack_set_key_found(DictAttack* instance, bool key_found) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->key_found = key_found; }, true); +} diff --git a/applications/main/nfc/views/dict_attack.h b/applications/main/nfc/views/dict_attack.h index b6c6fdbdc..70709f86e 100644 --- a/applications/main/nfc/views/dict_attack.h +++ b/applications/main/nfc/views/dict_attack.h @@ -8,6 +8,11 @@ extern "C" { #endif +typedef enum { + DictAttackTypeMfClassic, + DictAttackTypeMfUltralightC, +} DictAttackType; + typedef struct DictAttack DictAttack; typedef enum { @@ -56,6 +61,14 @@ void dict_attack_set_nested_target_key(DictAttack* instance, uint16_t target_key void dict_attack_set_msb_count(DictAttack* instance, uint16_t msb_count); +void dict_attack_set_type(DictAttack* instance, DictAttackType type); + +void dict_attack_set_pages_total(DictAttack* instance, uint8_t pages_total); + +void dict_attack_set_pages_read(DictAttack* instance, uint8_t pages_read); + +void dict_attack_set_key_found(DictAttack* instance, bool key_found); + #ifdef __cplusplus } #endif diff --git a/applications/services/dolphin/helpers/dolphin_deed.c b/applications/services/dolphin/helpers/dolphin_deed.c index f1f42b770..43f30dced 100644 --- a/applications/services/dolphin/helpers/dolphin_deed.c +++ b/applications/services/dolphin/helpers/dolphin_deed.c @@ -20,7 +20,7 @@ static const DolphinDeedWeight dolphin_deed_weights[] = { {3, DolphinAppNfc}, // DolphinDeedNfcSave {1, DolphinAppNfc}, // DolphinDeedNfcDetectReader {2, DolphinAppNfc}, // DolphinDeedNfcEmulate - {2, DolphinAppNfc}, // DolphinDeedNfcMfcAdd + {2, DolphinAppNfc}, // DolphinDeedNfcKeyAdd {1, DolphinAppNfc}, // DolphinDeedNfcAddSave {1, DolphinAppNfc}, // DolphinDeedNfcAddEmulate diff --git a/applications/services/dolphin/helpers/dolphin_deed.h b/applications/services/dolphin/helpers/dolphin_deed.h index c9cd18f31..7202dcf07 100644 --- a/applications/services/dolphin/helpers/dolphin_deed.h +++ b/applications/services/dolphin/helpers/dolphin_deed.h @@ -36,7 +36,7 @@ typedef enum { DolphinDeedNfcSave, DolphinDeedNfcDetectReader, DolphinDeedNfcEmulate, - DolphinDeedNfcMfcAdd, + DolphinDeedNfcKeyAdd, DolphinDeedNfcAddSave, DolphinDeedNfcAddEmulate, diff --git a/documentation/file_formats/NfcFileFormats.md b/documentation/file_formats/NfcFileFormats.md index da0b0a19d..d89483390 100644 --- a/documentation/file_formats/NfcFileFormats.md +++ b/documentation/file_formats/NfcFileFormats.md @@ -36,7 +36,7 @@ Version differences: ATQA: 00 44 SAK: 00 -### Description +### Description This file format is used to store the UID, SAK and ATQA of an ISO14443-3A device. UID must be either 4 or 7 bytes long. ATQA is 2 bytes long. SAK is 1 byte long. @@ -56,7 +56,7 @@ None, there are no versions yet. Application data: 00 12 34 FF Protocol info: 11 81 E1 -### Description +### Description This file format is used to store the UID, Application data and Protocol info of a ISO14443-3B device. UID must be 4 bytes long. Application data is 4 bytes long. Protocol info is 3 bytes long. @@ -80,7 +80,7 @@ None, there are no versions yet. # ISO14443-4A specific data ATS: 06 75 77 81 02 80 -### Description +### Description This file format is used to store the UID, SAK and ATQA of a ISO14443-4A device. It also stores the Answer to Select (ATS) data of the card. ATS must be no less than 5 bytes long. @@ -303,6 +303,26 @@ None, there are no versions yet. This file contains a list of Mifare Classic keys. Each key is represented as a hex string. Lines starting with '#' are ignored as comments. Blank lines are ignored as well. +## Mifare Ultralight C Dictionary + +### Example + + # Hexadecimal-Reversed Sample Key + 12E4143455F495649454D4B414542524 + # Byte-Reversed Sample Key (!NACUOYFIEMKAERB) + 214E4143554F594649454D4B41455242 + # Sample Key (BREAKMEIFYOUCAN!) + 425245414B4D454946594F5543414E21 + # Semnox Key (IEMKAERB!NACUOY ) + 49454D4B41455242214E4143554F5900 + # Modified Semnox Key (IEMKAERB!NACUOYF) + 49454D4B41455242214E4143554F5946 + ... + +### Description + +This file contains a list of Mifare Ultralight C keys. Each key is represented as a hex string. Lines starting with '#' are ignored as comments. Blank lines are ignored as well. + ## EMV resources ### Example diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index 7d51f6c6e..5f872952e 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -251,7 +251,7 @@ static NfcCommand mf_ultralight_poller_handler_read_version(MfUltralightPoller* instance->data->type = mf_ultralight_get_type_by_version(&instance->data->version); instance->state = MfUltralightPollerStateGetFeatureSet; } else { - FURI_LOG_D(TAG, "Didn't response. Check Ultralight C"); + FURI_LOG_D(TAG, "Didn't respond. Check Ultralight C"); iso14443_3a_poller_halt(instance->iso14443_3a_poller); instance->state = MfUltralightPollerStateDetectMfulC; } @@ -266,7 +266,7 @@ static NfcCommand mf_ultralight_poller_handler_check_ultralight_c(MfUltralightPo instance->data->type = MfUltralightTypeMfulC; instance->state = MfUltralightPollerStateGetFeatureSet; } else { - FURI_LOG_D(TAG, "Didn't response. Check NTAG 203"); + FURI_LOG_D(TAG, "Didn't respond. Check NTAG 203"); instance->state = MfUltralightPollerStateDetectNtag203; } iso14443_3a_poller_halt(instance->iso14443_3a_poller); @@ -452,7 +452,48 @@ static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPol command = instance->callback(instance->general_event, instance->context); if(!instance->mfu_event.data->auth_context.skip_auth) { FURI_LOG_D(TAG, "Trying to authenticate with 3des key"); - instance->auth_context.tdes_key = instance->mfu_event.data->auth_context.tdes_key; + // Only use the key if it was actually provided + if(instance->mfu_event.data->key_request_data.key_provided) { + instance->auth_context.tdes_key = instance->mfu_event.data->key_request_data.key; + } else if(instance->mode == MfUltralightPollerModeDictAttack) { + // TODO: -nofl Can logic be rearranged to request this key + // before reaching mf_ultralight_poller_handler_auth_ultralight_c in poller? + FURI_LOG_D(TAG, "No initial key provided, requesting key from dictionary"); + // Trigger dictionary key request + instance->mfu_event.type = MfUltralightPollerEventTypeRequestKey; + command = instance->callback(instance->general_event, instance->context); + if(!instance->mfu_event.data->key_request_data.key_provided) { + instance->state = MfUltralightPollerStateReadPages; + return command; + } else { + instance->auth_context.tdes_key = + instance->mfu_event.data->key_request_data.key; + } + } else { + // Fallback: use key from auth context (for sync poller compatibility) + instance->auth_context.tdes_key = instance->mfu_event.data->auth_context.tdes_key; + } + instance->auth_context.auth_success = false; + // For debugging + FURI_LOG_D( + "TAG", + "Key data: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", + instance->auth_context.tdes_key.data[0], + instance->auth_context.tdes_key.data[1], + instance->auth_context.tdes_key.data[2], + instance->auth_context.tdes_key.data[3], + instance->auth_context.tdes_key.data[4], + instance->auth_context.tdes_key.data[5], + instance->auth_context.tdes_key.data[6], + instance->auth_context.tdes_key.data[7], + instance->auth_context.tdes_key.data[8], + instance->auth_context.tdes_key.data[9], + instance->auth_context.tdes_key.data[10], + instance->auth_context.tdes_key.data[11], + instance->auth_context.tdes_key.data[12], + instance->auth_context.tdes_key.data[13], + instance->auth_context.tdes_key.data[14], + instance->auth_context.tdes_key.data[15]); do { uint8_t output[MF_ULTRALIGHT_C_AUTH_DATA_SIZE]; uint8_t RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0}; @@ -469,20 +510,40 @@ static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPol mf_ultralight_3des_shift_data(RndA); instance->auth_context.auth_success = (memcmp(RndA, decoded_shifted_RndA, sizeof(decoded_shifted_RndA)) == 0); - if(instance->auth_context.auth_success) { - FURI_LOG_D(TAG, "Auth success"); + FURI_LOG_E(TAG, "Auth success"); + if(instance->mode == MfUltralightPollerModeDictAttack) { + memcpy( + &instance->data->page[44], + instance->auth_context.tdes_key.data, + MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE); + // Continue to read pages after successful authentication + instance->state = MfUltralightPollerStateReadPages; + } } } while(false); - if(instance->error != MfUltralightErrorNone || !instance->auth_context.auth_success) { - FURI_LOG_D(TAG, "Auth failed"); + FURI_LOG_E(TAG, "Auth failed"); iso14443_3a_poller_halt(instance->iso14443_3a_poller); + if(instance->mode == MfUltralightPollerModeDictAttack) { + // Not needed? We already do a callback earlier? + instance->mfu_event.type = MfUltralightPollerEventTypeRequestKey; + command = instance->callback(instance->general_event, instance->context); + if(!instance->mfu_event.data->key_request_data.key_provided) { + instance->state = MfUltralightPollerStateReadPages; + } else { + instance->auth_context.tdes_key = + instance->mfu_event.data->key_request_data.key; + instance->state = MfUltralightPollerStateAuthMfulC; + } + } } } } - instance->state = MfUltralightPollerStateReadPages; - + // Regression review + if(instance->mode != MfUltralightPollerModeDictAttack) { + instance->state = MfUltralightPollerStateReadPages; + } return command; } @@ -505,12 +566,16 @@ static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* in instance->error = mf_ultralight_poller_read_page(instance, start_page, &data); } + // Regression review + const uint8_t read_cnt = instance->data->type == MfUltralightTypeMfulC ? 1 : 4; if(instance->error == MfUltralightErrorNone) { - if(start_page < instance->pages_total) { - FURI_LOG_D(TAG, "Read page %d success", start_page); - instance->data->page[start_page] = data.page[0]; - instance->pages_read++; - instance->data->pages_read = instance->pages_read; + for(size_t i = 0; i < read_cnt; i++) { + if(start_page + i < instance->pages_total) { + FURI_LOG_D(TAG, "Read page %d success", start_page + i); + instance->data->page[start_page + i] = data.page[i]; + instance->pages_read++; + instance->data->pages_read = instance->pages_read; + } } if(instance->pages_read == instance->pages_total) { @@ -753,7 +818,6 @@ static const MfUltralightPollerReadHandler [MfUltralightPollerStateWritePages] = mf_ultralight_poller_handler_write_pages, [MfUltralightPollerStateWriteFail] = mf_ultralight_poller_handler_write_fail, [MfUltralightPollerStateWriteSuccess] = mf_ultralight_poller_handler_write_success, - }; static NfcCommand mf_ultralight_poller_run(NfcGenericEvent event, void* context) { diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h index e50017324..2552abeb5 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h @@ -27,6 +27,7 @@ typedef enum { MfUltralightPollerEventTypeCardLocked, /**< Presented card is locked by password, AUTH0 or lock bytes. */ MfUltralightPollerEventTypeWriteSuccess, /**< Poller wrote card successfully. */ MfUltralightPollerEventTypeWriteFail, /**< Poller failed to write card. */ + MfUltralightPollerEventTypeRequestKey, /**< Poller requests key for dict attack. */ } MfUltralightPollerEventType; /** @@ -35,6 +36,7 @@ typedef enum { typedef enum { MfUltralightPollerModeRead, /**< Poller will only read card. It's a default mode. */ MfUltralightPollerModeWrite, /**< Poller will write already saved card to another presented card. */ + MfUltralightPollerModeDictAttack, /**< Poller will perform dictionary attack against card. */ } MfUltralightPollerMode; /** @@ -42,20 +44,29 @@ typedef enum { */ typedef struct { MfUltralightAuthPassword password; /**< Password to be used for authentication. */ - MfUltralightC3DesAuthKey tdes_key; - MfUltralightAuthPack pack; /**< Pack received on successfull authentication. */ + MfUltralightC3DesAuthKey tdes_key; /**< 3DES key to be used for authentication. */ + MfUltralightAuthPack pack; /**< Pack received on successful authentication. */ bool auth_success; /**< Set to true if authentication succeeded, false otherwise. */ bool skip_auth; /**< Set to true if authentication should be skipped, false otherwise. */ } MfUltralightPollerAuthContext; +/** + * @brief MfUltralight poller key request data. + */ +typedef struct { + MfUltralightC3DesAuthKey key; /**< Key to try. */ + bool key_provided; /**< Set to true if key was provided, false to stop attack. */ +} MfUltralightPollerKeyRequestData; + /** * @brief MfUltralight poller event data. */ typedef union { MfUltralightPollerAuthContext auth_context; /**< Authentication context. */ MfUltralightError error; /**< Error code indicating reading fail reason. */ - const MfUltralightData* write_data; - MfUltralightPollerMode poller_mode; + const MfUltralightData* write_data; /**< Data to be written to card. */ + MfUltralightPollerMode poller_mode; /**< Mode to operate in. */ + MfUltralightPollerKeyRequestData key_request_data; /**< Key request data. */ } MfUltralightPollerEventData; /** @@ -64,7 +75,7 @@ typedef union { * Upon emission of an event, an instance of this struct will be passed to the callback. */ typedef struct { - MfUltralightPollerEventType type; /**< Type of emmitted event. */ + MfUltralightPollerEventType type; /**< Type of emitted event. */ MfUltralightPollerEventData* data; /**< Pointer to event specific data. */ } MfUltralightPollerEvent; diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c index d84377612..82e647da8 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c @@ -134,7 +134,7 @@ MfUltralightError mf_ultralight_poller_authenticate_start( uint8_t* RndB = output + MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET; mf_ultralight_3des_decrypt( &instance->des_context, - instance->mfu_event.data->auth_context.tdes_key.data, + instance->auth_context.tdes_key.data, iv, encRndB, sizeof(encRndB), @@ -145,7 +145,7 @@ MfUltralightError mf_ultralight_poller_authenticate_start( mf_ultralight_3des_encrypt( &instance->des_context, - instance->mfu_event.data->auth_context.tdes_key.data, + instance->auth_context.tdes_key.data, encRndB, output, MF_ULTRALIGHT_C_AUTH_DATA_SIZE, @@ -179,7 +179,7 @@ MfUltralightError mf_ultralight_poller_authenticate_end( mf_ultralight_3des_decrypt( &instance->des_context, - instance->mfu_event.data->auth_context.tdes_key.data, + instance->auth_context.tdes_key.data, RndB, bit_buffer_get_data(instance->rx_buffer) + 1, MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE, diff --git a/lib/toolbox/keys_dict.c b/lib/toolbox/keys_dict.c index 602653e8f..c26e9c1e7 100644 --- a/lib/toolbox/keys_dict.c +++ b/lib/toolbox/keys_dict.c @@ -134,22 +134,21 @@ static void keys_dict_int_to_str(KeysDict* instance, const uint8_t* key_int, Fur furi_string_cat_printf(key_str, "%02X", key_int[i]); } -static void keys_dict_str_to_int(KeysDict* instance, FuriString* key_str, uint64_t* key_int) { +static void keys_dict_str_to_int(KeysDict* instance, FuriString* key_str, uint8_t* key_out) { furi_assert(instance); furi_assert(key_str); - furi_assert(key_int); + furi_assert(key_out); uint8_t key_byte_tmp; char h, l; - *key_int = 0ULL; - + // Process two hex characters at a time to create each byte for(size_t i = 0; i < instance->key_size_symbols - 1; i += 2) { h = furi_string_get_char(key_str, i); l = furi_string_get_char(key_str, i + 1); args_char_to_hex(h, l, &key_byte_tmp); - *key_int |= (uint64_t)key_byte_tmp << (8 * (instance->key_size - 1 - i / 2)); + key_out[i / 2] = key_byte_tmp; } } @@ -193,15 +192,7 @@ bool keys_dict_get_next_key(KeysDict* instance, uint8_t* key, size_t key_size) { bool key_read = keys_dict_get_next_key_str(instance, temp_key); if(key_read) { - size_t tmp_len = key_size; - uint64_t key_int = 0; - - keys_dict_str_to_int(instance, temp_key, &key_int); - - while(tmp_len--) { - key[tmp_len] = (uint8_t)key_int; - key_int >>= 8; - } + keys_dict_str_to_int(instance, temp_key, key); } furi_string_free(temp_key); From f78a8328d190bd651d7065038697e604841d7316 Mon Sep 17 00:00:00 2001 From: Leptopt1los <53914086+Leptopt1los@users.noreply.github.com> Date: Mon, 29 Sep 2025 20:13:33 +0300 Subject: [PATCH 089/192] tm01x dallas write support (#4230) Co-authored-by: hedger --- lib/ibutton/protocols/blanks/rw1990.c | 4 ++ lib/ibutton/protocols/blanks/tm01x.c | 58 +++++++++++++++++++ lib/ibutton/protocols/blanks/tm01x.h | 6 ++ lib/ibutton/protocols/blanks/tm2004.c | 2 + .../protocols/dallas/protocol_ds1990.c | 5 +- lib/one_wire/one_wire_host.c | 46 +++++++++++---- lib/one_wire/one_wire_host.h | 4 ++ targets/f18/api_symbols.csv | 2 + targets/f7/api_symbols.csv | 2 + 9 files changed, 117 insertions(+), 12 deletions(-) create mode 100644 lib/ibutton/protocols/blanks/tm01x.c create mode 100644 lib/ibutton/protocols/blanks/tm01x.h diff --git a/lib/ibutton/protocols/blanks/rw1990.c b/lib/ibutton/protocols/blanks/rw1990.c index d8017ca83..6edb4777c 100644 --- a/lib/ibutton/protocols/blanks/rw1990.c +++ b/lib/ibutton/protocols/blanks/rw1990.c @@ -38,6 +38,8 @@ static bool rw1990_read_and_compare(OneWireHost* host, const uint8_t* data, size } bool rw1990_write_v1(OneWireHost* host, const uint8_t* data, size_t data_size) { + onewire_host_set_timings_default(host); + // Unlock sequence onewire_host_reset(host); onewire_host_write(host, RW1990_1_CMD_WRITE_RECORD_FLAG); @@ -67,6 +69,8 @@ bool rw1990_write_v1(OneWireHost* host, const uint8_t* data, size_t data_size) { } bool rw1990_write_v2(OneWireHost* host, const uint8_t* data, size_t data_size) { + onewire_host_set_timings_default(host); + // Unlock sequence onewire_host_reset(host); onewire_host_write(host, RW1990_2_CMD_WRITE_RECORD_FLAG); diff --git a/lib/ibutton/protocols/blanks/tm01x.c b/lib/ibutton/protocols/blanks/tm01x.c new file mode 100644 index 000000000..6bdcb43d0 --- /dev/null +++ b/lib/ibutton/protocols/blanks/tm01x.c @@ -0,0 +1,58 @@ +#include +#include +#include +#include "tm01x.h" + +// Commands for TM01x +#define TM01X_CMD_WRITE_FLAG 0xC1 +#define TM01X_CMD_WRITE_ROM 0xC5 +#define TM01X_CMD_READ_ROM 0x33 + +#define TM01X_CMD_FINALIZE_CYFRAL 0xCA +#define TM01X_CMD_FINALIZE_METAKOM 0xCB + +static void tm01x_write_byte(OneWireHost* host, uint8_t value) { + for(uint8_t bitMask = 0x01; bitMask; bitMask <<= 1) { + onewire_host_write_bit(host, (bool)(bitMask & value)); + furi_delay_us(5000); // 5ms pause after each bit + } +} + +// Helper function to read and verify written data +static bool tm01x_read_and_verify(OneWireHost* host, const uint8_t* data, size_t data_size) { + bool success = false; + + if(onewire_host_reset(host)) { + success = true; + onewire_host_write(host, TM01X_CMD_READ_ROM); + + for(size_t i = 0; i < data_size; ++i) { + if(data[i] != onewire_host_read(host)) { + success = false; + break; + } + } + } + + return success; +} + +bool tm01x_write_dallas(OneWireHost* host, const uint8_t* data, size_t data_size) { + // Set TM01x specific timings + onewire_host_set_timings_tm01x(host); + + // Write sequence + onewire_host_reset(host); + onewire_host_write(host, TM01X_CMD_WRITE_FLAG); + onewire_host_write_bit(host, true); + furi_delay_us(5000); + + onewire_host_reset(host); + onewire_host_write(host, TM01X_CMD_WRITE_ROM); + + for(size_t i = 0; i < data_size; ++i) { + tm01x_write_byte(host, data[i]); + } + + return tm01x_read_and_verify(host, data, data_size); +} diff --git a/lib/ibutton/protocols/blanks/tm01x.h b/lib/ibutton/protocols/blanks/tm01x.h new file mode 100644 index 000000000..6c7840f6d --- /dev/null +++ b/lib/ibutton/protocols/blanks/tm01x.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include + +bool tm01x_write_dallas(OneWireHost* host, const uint8_t* data, size_t data_size); diff --git a/lib/ibutton/protocols/blanks/tm2004.c b/lib/ibutton/protocols/blanks/tm2004.c index a275dda0a..a93b69410 100644 --- a/lib/ibutton/protocols/blanks/tm2004.c +++ b/lib/ibutton/protocols/blanks/tm2004.c @@ -9,6 +9,8 @@ #define TM2004_ANSWER_READ_MEMORY 0xF5 bool tm2004_write(OneWireHost* host, const uint8_t* data, size_t data_size) { + onewire_host_set_timings_default(host); + onewire_host_reset(host); onewire_host_write(host, TM2004_CMD_WRITE_ROM); // Starting writing from address 0x0000 diff --git a/lib/ibutton/protocols/dallas/protocol_ds1990.c b/lib/ibutton/protocols/dallas/protocol_ds1990.c index 5ed2171c6..44fd60192 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1990.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1990.c @@ -7,7 +7,7 @@ #include "../blanks/rw1990.h" #include "../blanks/tm2004.h" - +#include "../blanks/tm01x.h" #define DS1990_FAMILY_CODE 0x01U #define DS1990_FAMILY_NAME "DS1990" @@ -66,7 +66,8 @@ bool dallas_ds1990_write_id(OneWireHost* host, iButtonProtocolData* protocol_dat return rw1990_write_v1(host, data->rom_data.bytes, sizeof(DallasCommonRomData)) || rw1990_write_v2(host, data->rom_data.bytes, sizeof(DallasCommonRomData)) || - tm2004_write(host, data->rom_data.bytes, sizeof(DallasCommonRomData)); + tm2004_write(host, data->rom_data.bytes, sizeof(DallasCommonRomData)) || + tm01x_write_dallas(host, data->rom_data.bytes, sizeof(DallasCommonRomData)); } static bool dallas_ds1990_reset_callback(bool is_short, void* context) { diff --git a/lib/one_wire/one_wire_host.c b/lib/one_wire/one_wire_host.c index f3f3d953e..62d325cf5 100644 --- a/lib/one_wire/one_wire_host.c +++ b/lib/one_wire/one_wire_host.c @@ -8,16 +8,16 @@ #include "one_wire_host.h" typedef struct { - uint16_t a; - uint16_t b; - uint16_t c; - uint16_t d; - uint16_t e; - uint16_t f; - uint16_t g; - uint16_t h; - uint16_t i; - uint16_t j; + uint16_t a; // Write 1 low time + uint16_t b; // Write 1 high time + uint16_t c; // Write 0 low time + uint16_t d; // Write 0 high time + uint16_t e; // Read low time + uint16_t f; // Read high time + uint16_t g; // Reset pre-delay + uint16_t h; // Reset pulse + uint16_t i; // Presence detect + uint16_t j; // Reset post-delay } OneWireHostTimings; static const OneWireHostTimings onewire_host_timings_normal = { @@ -46,6 +46,20 @@ static const OneWireHostTimings onewire_host_timings_overdrive = { .j = 40, }; +// TM01x specific timings +static const OneWireHostTimings onewire_host_timings_tm01x = { + .a = 5, + .b = 80, + .c = 70, + .d = 10, + .e = 5, + .f = 70, + .g = 0, + .h = 740, + .i = 140, + .j = 410, +}; + struct OneWireHost { const GpioPin* gpio_pin; const OneWireHostTimings* timings; @@ -354,3 +368,15 @@ void onewire_host_set_overdrive(OneWireHost* host, bool set) { host->timings = set ? &onewire_host_timings_overdrive : &onewire_host_timings_normal; } + +void onewire_host_set_timings_default(OneWireHost* host) { + furi_check(host); + + host->timings = &onewire_host_timings_normal; +} + +void onewire_host_set_timings_tm01x(OneWireHost* host) { + furi_check(host); + + host->timings = &onewire_host_timings_tm01x; +} diff --git a/lib/one_wire/one_wire_host.h b/lib/one_wire/one_wire_host.h index 9f9bd4ffd..e61dc63e2 100644 --- a/lib/one_wire/one_wire_host.h +++ b/lib/one_wire/one_wire_host.h @@ -125,6 +125,10 @@ bool onewire_host_search(OneWireHost* host, uint8_t* new_addr, OneWireHostSearch */ void onewire_host_set_overdrive(OneWireHost* host, bool set); +void onewire_host_set_timings_default(OneWireHost* host); + +void onewire_host_set_timings_tm01x(OneWireHost* host); + #ifdef __cplusplus } #endif diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 0590a16b8..a89625080 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -2283,6 +2283,8 @@ Function,+,onewire_host_reset,_Bool,OneWireHost* Function,+,onewire_host_reset_search,void,OneWireHost* Function,+,onewire_host_search,_Bool,"OneWireHost*, uint8_t*, OneWireHostSearchMode" Function,+,onewire_host_set_overdrive,void,"OneWireHost*, _Bool" +Function,+,onewire_host_set_timings_default,void,OneWireHost* +Function,+,onewire_host_set_timings_tm01x,void,OneWireHost* Function,+,onewire_host_start,void,OneWireHost* Function,+,onewire_host_stop,void,OneWireHost* Function,+,onewire_host_target_search,void,"OneWireHost*, uint8_t" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 62f0245df..ef938ab27 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -2935,6 +2935,8 @@ Function,+,onewire_host_reset,_Bool,OneWireHost* Function,+,onewire_host_reset_search,void,OneWireHost* Function,+,onewire_host_search,_Bool,"OneWireHost*, uint8_t*, OneWireHostSearchMode" Function,+,onewire_host_set_overdrive,void,"OneWireHost*, _Bool" +Function,+,onewire_host_set_timings_default,void,OneWireHost* +Function,+,onewire_host_set_timings_tm01x,void,OneWireHost* Function,+,onewire_host_start,void,OneWireHost* Function,+,onewire_host_stop,void,OneWireHost* Function,+,onewire_host_target_search,void,"OneWireHost*, uint8_t" From 1e0f3a606f654bd9047b271aadaa6d028199100c Mon Sep 17 00:00:00 2001 From: Ivan Barsukov Date: Mon, 29 Sep 2025 20:53:10 +0300 Subject: [PATCH 090/192] cli: Buzzer command (#4006) * Add args_read_float_and_trim function * Add args_read_duration function * Add notes_frequency_from_name function * Add cli_sleep function and sleep CLI command * Update CLI top command to use cli_sleep * Add buzzer CLI command * toolbox: make args_read_duration less convoluted * notification: make notification_messages_notes_frequency_from_name less convoluted * unit_tests: better float checking * fix formatting and f18 --------- Co-authored-by: Anna Antonenko Co-authored-by: hedger --- applications/debug/unit_tests/application.fam | 17 ++ .../debug/unit_tests/tests/args/args_test.c | 211 ++++++++++++++++++ applications/debug/unit_tests/tests/minunit.h | 2 +- .../tests/notification/notes_test.c | 165 ++++++++++++++ applications/services/cli/application.fam | 8 + applications/services/cli/cli_main_commands.c | 49 +++- applications/services/cli/commands/buzzer.c | 135 +++++++++++ .../notification_messages_notes.c | 33 +++ .../notification_messages_notes.h | 11 + lib/toolbox/args.c | 54 +++++ lib/toolbox/args.h | 39 +++- lib/toolbox/cli/cli_command.c | 15 ++ lib/toolbox/cli/cli_command.h | 23 +- targets/f18/api_symbols.csv | 4 + targets/f7/api_symbols.csv | 4 + 15 files changed, 747 insertions(+), 23 deletions(-) create mode 100644 applications/debug/unit_tests/tests/args/args_test.c create mode 100644 applications/debug/unit_tests/tests/notification/notes_test.c create mode 100644 applications/services/cli/commands/buzzer.c diff --git a/applications/debug/unit_tests/application.fam b/applications/debug/unit_tests/application.fam index 72b8cafcb..252eb57c5 100644 --- a/applications/debug/unit_tests/application.fam +++ b/applications/debug/unit_tests/application.fam @@ -244,3 +244,20 @@ App( entry_point="get_api", requires=["unit_tests"], ) + +App( + appid="test_args", + sources=["tests/common/*.c", "tests/args/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + + +App( + appid="test_notification", + sources=["tests/common/*.c", "tests/notification/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) diff --git a/applications/debug/unit_tests/tests/args/args_test.c b/applications/debug/unit_tests/tests/args/args_test.c new file mode 100644 index 000000000..9b1887f0b --- /dev/null +++ b/applications/debug/unit_tests/tests/args/args_test.c @@ -0,0 +1,211 @@ + +#include "../test.h" // IWYU pragma: keep +#include + +const uint32_t one_ms = 1; +const uint32_t one_s = 1000 * one_ms; +const uint32_t one_m = 60 * one_s; +const uint32_t one_h = 60 * one_m; + +MU_TEST(args_read_duration_default_values_test) { + FuriString* args_string; + uint32_t value = 0; + + // Check default == NULL (ms) + args_string = furi_string_alloc_set_str("1"); + mu_check(args_read_duration(args_string, &value, NULL)); + mu_assert_int_eq(value, one_ms); + furi_string_free(args_string); + value = 0; + + // Check default == ms + args_string = furi_string_alloc_set_str("1"); + mu_check(args_read_duration(args_string, &value, "ms")); + mu_assert_int_eq(value, one_ms); + furi_string_free(args_string); + value = 0; + + // Check default == s + args_string = furi_string_alloc_set_str("1"); + mu_check(args_read_duration(args_string, &value, "s")); + mu_assert_int_eq(value, one_s); + furi_string_free(args_string); + value = 0; + + // Check default == m + args_string = furi_string_alloc_set_str("1"); + mu_check(args_read_duration(args_string, &value, "m")); + mu_assert_int_eq(value, one_m); + furi_string_free(args_string); + value = 0; + + // Check default == h + args_string = furi_string_alloc_set_str("1"); + mu_check(args_read_duration(args_string, &value, "h")); + mu_assert_int_eq(value, one_h); + furi_string_free(args_string); + value = 0; +} + +MU_TEST(args_read_duration_suffix_values_test) { + FuriString* args_string; + uint32_t value = 0; + + // Check ms + args_string = furi_string_alloc_set_str("1ms"); + mu_check(args_read_duration(args_string, &value, NULL)); + mu_assert_int_eq(value, one_ms); + furi_string_free(args_string); + value = 0; + + // Check s + args_string = furi_string_alloc_set_str("1s"); + mu_check(args_read_duration(args_string, &value, NULL)); + mu_assert_int_eq(value, one_s); + furi_string_free(args_string); + value = 0; + + // Check m + args_string = furi_string_alloc_set_str("1m"); + mu_check(args_read_duration(args_string, &value, NULL)); + mu_assert_int_eq(value, one_m); + furi_string_free(args_string); + value = 0; + + // Check h + args_string = furi_string_alloc_set_str("1h"); + mu_check(args_read_duration(args_string, &value, NULL)); + mu_assert_int_eq(value, one_h); + furi_string_free(args_string); + value = 0; +} + +MU_TEST(args_read_duration_values_test) { + FuriString* args_string; + uint32_t value = 0; + + // Check for ms + args_string = furi_string_alloc_set_str("4294967295ms"); + mu_check(args_read_duration(args_string, &value, NULL)); + mu_assert_int_eq(value, 4294967295U); + furi_string_free(args_string); + + // Check for s + args_string = furi_string_alloc_set_str("4294967s"); + mu_check(args_read_duration(args_string, &value, NULL)); + mu_assert_int_eq(value, 4294967U * one_s); + furi_string_free(args_string); + + // Check for m + args_string = furi_string_alloc_set_str("71582m"); + mu_check(args_read_duration(args_string, &value, NULL)); + mu_assert_int_eq(value, 71582U * one_m); + furi_string_free(args_string); + + // Check for h + args_string = furi_string_alloc_set_str("1193h"); + mu_check(args_read_duration(args_string, &value, NULL)); + mu_assert_int_eq(value, 1193U * one_h); + furi_string_free(args_string); + + // Check for ms in float + args_string = furi_string_alloc_set_str("4.2ms"); + mu_check(args_read_duration(args_string, &value, NULL)); + mu_assert_int_eq(value, 4); + furi_string_free(args_string); + + // Check for s in float + args_string = furi_string_alloc_set_str("1.5s"); + mu_check(args_read_duration(args_string, &value, NULL)); + mu_assert_int_eq(value, (uint32_t)(1.5 * one_s)); + furi_string_free(args_string); + + // Check for m in float + args_string = furi_string_alloc_set_str("1.5m"); + mu_check(args_read_duration(args_string, &value, NULL)); + mu_assert_int_eq(value, (uint32_t)(1.5 * one_m)); + furi_string_free(args_string); + + // Check for h in float + args_string = furi_string_alloc_set_str("1.5h"); + mu_check(args_read_duration(args_string, &value, NULL)); + mu_assert_int_eq(value, (uint32_t)(1.5 * one_h)); + furi_string_free(args_string); +} + +MU_TEST(args_read_duration_errors_test) { + FuriString* args_string; + uint32_t value = 0; + + // Check wrong suffix + args_string = furi_string_alloc_set_str("1x"); + mu_check(!args_read_duration(args_string, &value, NULL)); + mu_assert_int_eq(value, 0); + furi_string_free(args_string); + + // Check wrong suffix + args_string = furi_string_alloc_set_str("1xs"); + mu_check(!args_read_duration(args_string, &value, NULL)); + mu_assert_int_eq(value, 0); + furi_string_free(args_string); + + // Check negative value + args_string = furi_string_alloc_set_str("-1s"); + mu_check(!args_read_duration(args_string, &value, NULL)); + mu_assert_int_eq(value, 0); + furi_string_free(args_string); + + // Check wrong values + + // Check only suffix + args_string = furi_string_alloc_set_str("s"); + mu_check(!args_read_duration(args_string, &value, NULL)); + mu_assert_int_eq(value, 0); + furi_string_free(args_string); + + // Check doubled point + args_string = furi_string_alloc_set_str("0.1.1"); + mu_check(!args_read_duration(args_string, &value, NULL)); + mu_assert_int_eq(value, 0); + furi_string_free(args_string); + + // Check overflow values + + // Check for ms + args_string = furi_string_alloc_set_str("4294967296ms"); + mu_check(!args_read_duration(args_string, &value, NULL)); + mu_assert_int_eq(value, 0); + furi_string_free(args_string); + + // Check for s + args_string = furi_string_alloc_set_str("4294968s"); + mu_check(!args_read_duration(args_string, &value, NULL)); + mu_assert_int_eq(value, 0); + furi_string_free(args_string); + + // Check for m + args_string = furi_string_alloc_set_str("71583m"); + mu_check(!args_read_duration(args_string, &value, NULL)); + mu_assert_int_eq(value, 0); + furi_string_free(args_string); + + // Check for h + args_string = furi_string_alloc_set_str("1194h"); + mu_check(!args_read_duration(args_string, &value, NULL)); + mu_assert_int_eq(value, 0); + furi_string_free(args_string); +} + +MU_TEST_SUITE(toolbox_args_read_duration_suite) { + MU_RUN_TEST(args_read_duration_default_values_test); + MU_RUN_TEST(args_read_duration_suffix_values_test); + MU_RUN_TEST(args_read_duration_values_test); + MU_RUN_TEST(args_read_duration_errors_test); +} + +int run_minunit_test_toolbox_args(void) { + MU_RUN_SUITE(toolbox_args_read_duration_suite); + return MU_EXIT_CODE; +} + +TEST_API_DEFINE(run_minunit_test_toolbox_args) diff --git a/applications/debug/unit_tests/tests/minunit.h b/applications/debug/unit_tests/tests/minunit.h index c854c4673..943ed3c67 100644 --- a/applications/debug/unit_tests/tests/minunit.h +++ b/applications/debug/unit_tests/tests/minunit.h @@ -389,8 +389,8 @@ void minunit_printf_warning(const char* format, ...); __func__, \ __FILE__, \ __LINE__, \ - minunit_tmp_e, \ minunit_tmp_r, \ + minunit_tmp_e, \ minunit_tmp_m); \ minunit_status = 1; \ return; \ diff --git a/applications/debug/unit_tests/tests/notification/notes_test.c b/applications/debug/unit_tests/tests/notification/notes_test.c new file mode 100644 index 000000000..2b6d25c13 --- /dev/null +++ b/applications/debug/unit_tests/tests/notification/notes_test.c @@ -0,0 +1,165 @@ +#include "../test.h" // IWYU pragma: keep +#include +#include + +void frequency_assert(const char* note_name, const NotificationMessage* message) { + double a = notification_messages_notes_frequency_from_name(note_name); + double b = message->data.sound.frequency; + const double epsilon = message->data.sound.frequency > 5000 ? 0.02f : 0.01f; + mu_assert_double_between(b - epsilon, b + epsilon, a); +} + +MU_TEST(notification_messages_notes_frequency_from_name_test) { + // Upper case + mu_check(float_is_equal( + notification_messages_notes_frequency_from_name("C0"), + notification_messages_notes_frequency_from_name("c0"))); + + // Mixed case + mu_check(float_is_equal( + notification_messages_notes_frequency_from_name("Cs0"), + notification_messages_notes_frequency_from_name("cs0"))); + + // Check errors + mu_check( + float_is_equal(notification_messages_notes_frequency_from_name("0"), 0.0)); // Without note + mu_check(float_is_equal( + notification_messages_notes_frequency_from_name("C"), 0.0)); // Without octave + mu_check(float_is_equal( + notification_messages_notes_frequency_from_name("C9"), 0.0)); // Unsupported octave + mu_check(float_is_equal( + notification_messages_notes_frequency_from_name("C10"), 0.0)); // Unsupported octave + mu_check(float_is_equal( + notification_messages_notes_frequency_from_name("X0"), 0.0)); // Unknown note + mu_check(float_is_equal( + notification_messages_notes_frequency_from_name("CCC0"), 0.0)); // Note name overflow + + // Notes and structures + frequency_assert("c0", &message_note_c0); + frequency_assert("cs0", &message_note_cs0); + frequency_assert("d0", &message_note_d0); + frequency_assert("ds0", &message_note_ds0); + frequency_assert("e0", &message_note_e0); + frequency_assert("f0", &message_note_f0); + frequency_assert("fs0", &message_note_fs0); + frequency_assert("g0", &message_note_g0); + frequency_assert("gs0", &message_note_gs0); + frequency_assert("a0", &message_note_a0); + frequency_assert("as0", &message_note_as0); + frequency_assert("b0", &message_note_b0); + + frequency_assert("c1", &message_note_c1); + frequency_assert("cs1", &message_note_cs1); + frequency_assert("d1", &message_note_d1); + frequency_assert("ds1", &message_note_ds1); + frequency_assert("e1", &message_note_e1); + frequency_assert("f1", &message_note_f1); + frequency_assert("fs1", &message_note_fs1); + frequency_assert("g1", &message_note_g1); + frequency_assert("gs1", &message_note_gs1); + frequency_assert("a1", &message_note_a1); + frequency_assert("as1", &message_note_as1); + frequency_assert("b1", &message_note_b1); + + frequency_assert("c2", &message_note_c2); + frequency_assert("cs2", &message_note_cs2); + frequency_assert("d2", &message_note_d2); + frequency_assert("ds2", &message_note_ds2); + frequency_assert("e2", &message_note_e2); + frequency_assert("f2", &message_note_f2); + frequency_assert("fs2", &message_note_fs2); + frequency_assert("g2", &message_note_g2); + frequency_assert("gs2", &message_note_gs2); + frequency_assert("a2", &message_note_a2); + frequency_assert("as2", &message_note_as2); + frequency_assert("b2", &message_note_b2); + + frequency_assert("c3", &message_note_c3); + frequency_assert("cs3", &message_note_cs3); + frequency_assert("d3", &message_note_d3); + frequency_assert("ds3", &message_note_ds3); + frequency_assert("e3", &message_note_e3); + frequency_assert("f3", &message_note_f3); + frequency_assert("fs3", &message_note_fs3); + frequency_assert("g3", &message_note_g3); + frequency_assert("gs3", &message_note_gs3); + frequency_assert("a3", &message_note_a3); + frequency_assert("as3", &message_note_as3); + frequency_assert("b3", &message_note_b3); + + frequency_assert("c4", &message_note_c4); + frequency_assert("cs4", &message_note_cs4); + frequency_assert("d4", &message_note_d4); + frequency_assert("ds4", &message_note_ds4); + frequency_assert("e4", &message_note_e4); + frequency_assert("f4", &message_note_f4); + frequency_assert("fs4", &message_note_fs4); + frequency_assert("g4", &message_note_g4); + frequency_assert("gs4", &message_note_gs4); + frequency_assert("a4", &message_note_a4); + frequency_assert("as4", &message_note_as4); + frequency_assert("b4", &message_note_b4); + + frequency_assert("c5", &message_note_c5); + frequency_assert("cs5", &message_note_cs5); + frequency_assert("d5", &message_note_d5); + frequency_assert("ds5", &message_note_ds5); + frequency_assert("e5", &message_note_e5); + frequency_assert("f5", &message_note_f5); + frequency_assert("fs5", &message_note_fs5); + frequency_assert("g5", &message_note_g5); + frequency_assert("gs5", &message_note_gs5); + frequency_assert("a5", &message_note_a5); + frequency_assert("as5", &message_note_as5); + frequency_assert("b5", &message_note_b5); + + frequency_assert("c6", &message_note_c6); + frequency_assert("cs6", &message_note_cs6); + frequency_assert("d6", &message_note_d6); + frequency_assert("ds6", &message_note_ds6); + frequency_assert("e6", &message_note_e6); + frequency_assert("f6", &message_note_f6); + frequency_assert("fs6", &message_note_fs6); + frequency_assert("g6", &message_note_g6); + frequency_assert("gs6", &message_note_gs6); + frequency_assert("a6", &message_note_a6); + frequency_assert("as6", &message_note_as6); + frequency_assert("b6", &message_note_b6); + + frequency_assert("c7", &message_note_c7); + frequency_assert("cs7", &message_note_cs7); + frequency_assert("d7", &message_note_d7); + frequency_assert("ds7", &message_note_ds7); + frequency_assert("e7", &message_note_e7); + frequency_assert("f7", &message_note_f7); + frequency_assert("fs7", &message_note_fs7); + frequency_assert("g7", &message_note_g7); + frequency_assert("gs7", &message_note_gs7); + frequency_assert("a7", &message_note_a7); + frequency_assert("as7", &message_note_as7); + frequency_assert("b7", &message_note_b7); + + frequency_assert("c8", &message_note_c8); + frequency_assert("cs8", &message_note_cs8); + frequency_assert("d8", &message_note_d8); + frequency_assert("ds8", &message_note_ds8); + frequency_assert("e8", &message_note_e8); + frequency_assert("f8", &message_note_f8); + frequency_assert("fs8", &message_note_fs8); + frequency_assert("g8", &message_note_g8); + frequency_assert("gs8", &message_note_gs8); + frequency_assert("a8", &message_note_a8); + frequency_assert("as8", &message_note_as8); + frequency_assert("b8", &message_note_b8); +} + +MU_TEST_SUITE(notes_suite) { + MU_RUN_TEST(notification_messages_notes_frequency_from_name_test); +} + +int run_minunit_test_notes(void) { + MU_RUN_SUITE(notes_suite); + return MU_EXIT_CODE; +} + +TEST_API_DEFINE(run_minunit_test_notes) diff --git a/applications/services/cli/application.fam b/applications/services/cli/application.fam index b305fb6b0..d5acf7752 100644 --- a/applications/services/cli/application.fam +++ b/applications/services/cli/application.fam @@ -49,3 +49,11 @@ App( requires=["cli"], sources=["commands/subshell_demo.c"], ) + +App( + appid="cli_buzzer", + apptype=FlipperAppType.PLUGIN, + entry_point="cli_buzzer_ep", + requires=["cli"], + sources=["commands/buzzer.c"], +) diff --git a/applications/services/cli/cli_main_commands.c b/applications/services/cli/cli_main_commands.c index 508a650de..a478512d5 100644 --- a/applications/services/cli/cli_main_commands.c +++ b/applications/services/cli/cli_main_commands.c @@ -356,11 +356,13 @@ void cli_command_led(PipeSide* pipe, FuriString* args, void* context) { static void cli_command_top(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); - int interval = 1000; - args_read_int_and_trim(args, &interval); + uint32_t interval; + if(!args_read_duration(args, &interval, NULL)) { + interval = 1000; + } FuriThreadList* thread_list = furi_thread_list_alloc(); - while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { + do { uint32_t tick = furi_get_tick(); furi_thread_enumerate(thread_list); @@ -416,12 +418,8 @@ static void cli_command_top(PipeSide* pipe, FuriString* args, void* context) { printf(ANSI_ERASE_DISPLAY(ANSI_ERASE_FROM_CURSOR_TO_END)); fflush(stdout); - if(interval > 0) { - furi_delay_ms(interval); - } else { - break; - } - } + } while(interval > 0 && cli_sleep(pipe, interval)); + furi_thread_list_free(thread_list); } @@ -491,6 +489,37 @@ void cli_command_echo(PipeSide* pipe, FuriString* args, void* context) { } } +/** + * @brief Pause for a specified duration or until Ctrl+C is pressed or the + * session is terminated. + * + * The duration can be specified in various units such as milliseconds (ms), + * seconds (s), minutes (m), or hours (h). If the unit is not specified, the + * second is used by default. + * + * Example: + * sleep 5s + */ +void cli_command_sleep(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(context); + FuriString* duration_string; + duration_string = furi_string_alloc(); + + do { + uint32_t duration_in_ms = 0; + if(!args_read_string_and_trim(args, duration_string) || + !args_read_duration(duration_string, &duration_in_ms, "s")) { + cli_print_usage("sleep", "[<0-...>[]]", furi_string_get_cstr(args)); + break; + } + + cli_sleep(pipe, duration_in_ms); + + } while(false); + + furi_string_free(duration_string); +} + void cli_main_commands_init(CliRegistry* registry) { cli_registry_add_command( registry, "!", CliCommandFlagParallelSafe, cli_command_info, (void*)true); @@ -508,6 +537,8 @@ void cli_main_commands_init(CliRegistry* registry) { cli_registry_add_command( registry, "free_blocks", CliCommandFlagParallelSafe, cli_command_free_blocks, NULL); cli_registry_add_command(registry, "echo", CliCommandFlagParallelSafe, cli_command_echo, NULL); + cli_registry_add_command( + registry, "sleep", CliCommandFlagParallelSafe, cli_command_sleep, NULL); cli_registry_add_command(registry, "vibro", CliCommandFlagDefault, cli_command_vibro, NULL); cli_registry_add_command(registry, "led", CliCommandFlagDefault, cli_command_led, NULL); diff --git a/applications/services/cli/commands/buzzer.c b/applications/services/cli/commands/buzzer.c new file mode 100644 index 000000000..3c1673149 --- /dev/null +++ b/applications/services/cli/commands/buzzer.c @@ -0,0 +1,135 @@ +#include "../cli_main_commands.h" +#include +#include +#include + +void cli_command_buzzer_print_usage(bool is_freq_subcommand, FuriString* args) { + if(is_freq_subcommand) { + cli_print_usage( + "buzzer freq", " [<0-...>[]]", furi_string_get_cstr(args)); + + } else { + cli_print_usage("buzzer note", " [<0-...>[]]", furi_string_get_cstr(args)); + } +} + +float cli_command_buzzer_read_frequency(bool is_freq_subcommand, FuriString* args) { + float frequency = 0.0f; + + if(is_freq_subcommand) { + args_read_float_and_trim(args, &frequency); + return frequency; + } + + // Extract note frequency from name + + FuriString* note_name_string; + note_name_string = furi_string_alloc(); + + do { + if(!args_read_string_and_trim(args, note_name_string)) { + break; + } + const char* note_name = furi_string_get_cstr(note_name_string); + frequency = notification_messages_notes_frequency_from_name(note_name); + } while(false); + + furi_string_free(note_name_string); + + return frequency; +} + +void cli_command_buzzer_play( + PipeSide* pipe, + NotificationApp* notification, + bool is_freq_subcommand, + FuriString* args) { + FuriString* duration_string; + duration_string = furi_string_alloc(); + + do { + float frequency = cli_command_buzzer_read_frequency(is_freq_subcommand, args); + if(frequency <= 0.0f) { + cli_command_buzzer_print_usage(is_freq_subcommand, args); + break; + } + + const NotificationMessage notification_buzzer_message = { + .type = NotificationMessageTypeSoundOn, + .data.sound.frequency = frequency, + .data.sound.volume = 1.0, + }; + + // Optional duration + uint32_t duration_ms = 100; + if(args_read_string_and_trim(args, duration_string)) { + if(!args_read_duration(duration_string, &duration_ms, NULL)) { + cli_command_buzzer_print_usage(is_freq_subcommand, args); + break; + } + } + + const NotificationSequence sound_on_sequence = { + ¬ification_buzzer_message, + &message_do_not_reset, + NULL, + }; + + // Play sound + notification_message_block(notification, &sound_on_sequence); + + cli_sleep(pipe, duration_ms); + + // Stop sound + const NotificationSequence sound_off_sequence = { + &message_sound_off, + NULL, + }; + notification_message_block(notification, &sound_off_sequence); + + } while(false); + + furi_string_free(duration_string); +} + +void execute(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(context); + + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + + FuriString* command_string; + command_string = furi_string_alloc(); + + do { + if(!args_read_string_and_trim(args, command_string)) { + cli_print_usage("buzzer", "", furi_string_get_cstr(args)); + break; + } + + // Check volume + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode)) { + printf("Flipper is in stealth mode. Unmute the device to control buzzer."); + break; + } + if(notification->settings.speaker_volume == 0.0f) { + printf("Sound is disabled in settings. Increase volume to control buzzer."); + break; + } + + if(furi_string_cmp(command_string, "freq") == 0) { + cli_command_buzzer_play(pipe, notification, true, args); + break; + } else if(furi_string_cmp(command_string, "note") == 0) { + cli_command_buzzer_play(pipe, notification, false, args); + break; + } + + cli_print_usage("buzzer", "", furi_string_get_cstr(args)); + + } while(false); + + furi_string_free(command_string); + furi_record_close(RECORD_NOTIFICATION); +} + +CLI_COMMAND_INTERFACE(buzzer, execute, CliCommandFlagDefault, 2048, CLI_APPID); diff --git a/applications/services/notification/notification_messages_notes.c b/applications/services/notification/notification_messages_notes.c index 18ff94aaf..4a13fe0e6 100644 --- a/applications/services/notification/notification_messages_notes.c +++ b/applications/services/notification/notification_messages_notes.c @@ -1,4 +1,5 @@ #include "notification.h" +#include /* Python script for note messages generation @@ -571,3 +572,35 @@ const NotificationMessage message_note_b8 = { .data.sound.frequency = 7902.13f, .data.sound.volume = 1.0f, }; + +float notification_messages_notes_frequency_from_name(const char* note_name) { + const float base_note = 16.3515979f; // C0 + + const char* note_names[] = {"c", "cs", "d", "ds", "e", "f", "fs", "g", "gs", "a", "as", "b"}; + const size_t notes_count = COUNT_OF(note_names); + + char note_wo_octave[3] = {0}; + for(size_t i = 0; i < sizeof(note_wo_octave) - 1; i++) { + char in = *note_name; + if(!in) break; + if(!isalpha(in)) break; + note_wo_octave[i] = in; + note_name++; + } + + int note_index = -1; + for(size_t i = 0; i < notes_count; i++) { + if(strcasecmp(note_wo_octave, note_names[i]) == 0) note_index = i; + } + if(note_index < 0) return 0.0; + + uint16_t octave; + StrintParseError error = strint_to_uint16(note_name, NULL, &octave, 10); + if(error != StrintParseNoError) return 0.0; + if(octave > 8) return 0.0; + + int semitone_index = octave * notes_count + note_index; + float frequency = base_note * powf(2.0f, semitone_index / 12.0f); + + return roundf(frequency * 100) / 100.0f; +} diff --git a/applications/services/notification/notification_messages_notes.h b/applications/services/notification/notification_messages_notes.h index b1040a01e..dffa2519e 100644 --- a/applications/services/notification/notification_messages_notes.h +++ b/applications/services/notification/notification_messages_notes.h @@ -115,6 +115,17 @@ extern const NotificationMessage message_note_a8; extern const NotificationMessage message_note_as8; extern const NotificationMessage message_note_b8; +/** + * @brief Returns the frequency of the given note + * + * This function calculates and returns the frequency (in Hz) of the specified note. + * If the input note name is invalid, the function returns 0.0. + * + * @param [in] note_name The name of the note (e.g., "A4", cs5") + * @return The frequency of the note in Hz, or 0.0 if the note name is invalid + */ +extern float notification_messages_notes_frequency_from_name(const char* note_name); + #ifdef __cplusplus } #endif diff --git a/lib/toolbox/args.c b/lib/toolbox/args.c index 914b093ba..f6a1cfda5 100644 --- a/lib/toolbox/args.c +++ b/lib/toolbox/args.c @@ -2,6 +2,7 @@ #include "hex.h" #include "strint.h" #include "m-core.h" +#include size_t args_get_first_word_length(FuriString* args) { size_t ws = furi_string_search_char(args, ' '); @@ -34,6 +35,24 @@ bool args_read_int_and_trim(FuriString* args, int* value) { return false; } +bool args_read_float_and_trim(FuriString* args, float* value) { + size_t cmd_length = args_get_first_word_length(args); + if(cmd_length == 0) { + return false; + } + + char* end_ptr; + float temp = strtof(furi_string_get_cstr(args), &end_ptr); + if(end_ptr == furi_string_get_cstr(args)) { + return false; + } + + *value = temp; + furi_string_right(args, cmd_length); + furi_string_trim(args); + return true; +} + bool args_read_string_and_trim(FuriString* args, FuriString* word) { size_t cmd_length = args_get_first_word_length(args); @@ -97,3 +116,38 @@ bool args_read_hex_bytes(FuriString* args, uint8_t* bytes, size_t bytes_count) { return result; } + +bool args_read_duration(FuriString* args, uint32_t* value, const char* default_unit) { + const char* args_cstr = furi_string_get_cstr(args); + + const char* unit; + errno = 0; + double duration_ms = strtod(args_cstr, (char**)&unit); + if(errno) return false; + if(duration_ms < 0) return false; + if(unit == args_cstr) return false; + + if(strcmp(unit, "") == 0) { + unit = default_unit; + if(!unit) unit = "ms"; + } + + uint32_t multiplier; + if(strcasecmp(unit, "ms") == 0) { + multiplier = 1; + } else if(strcasecmp(unit, "s") == 0) { + multiplier = 1000; + } else if(strcasecmp(unit, "m") == 0) { + multiplier = 60 * 1000; + } else if(strcasecmp(unit, "h") == 0) { + multiplier = 60 * 60 * 1000; + } else { + return false; + } + + const uint32_t max_pre_multiplication = UINT32_MAX / multiplier; + if(duration_ms > max_pre_multiplication) return false; + + *value = round(duration_ms * multiplier); + return true; +} diff --git a/lib/toolbox/args.h b/lib/toolbox/args.h index 556fd4a72..fecf33599 100644 --- a/lib/toolbox/args.h +++ b/lib/toolbox/args.h @@ -9,17 +9,26 @@ extern "C" { #endif /** Extract int value and trim arguments string - * - * @param args - arguments string - * @param word first argument, output + * + * @param args - arguments string + * @param value first argument, output * @return true - success * @return false - arguments string does not contain int */ bool args_read_int_and_trim(FuriString* args, int* value); +/** Extract float value and trim arguments string + * + * @param [in, out] args arguments string + * @param [out] value first argument + * @return true - success + * @return false - arguments string does not contain float + */ +bool args_read_float_and_trim(FuriString* args, float* value); + /** * @brief Extract first argument from arguments string and trim arguments string - * + * * @param args arguments string * @param word first argument, output * @return true - success @@ -29,7 +38,7 @@ bool args_read_string_and_trim(FuriString* args, FuriString* word); /** * @brief Extract the first quoted argument from the argument string and trim the argument string. If the argument is not quoted, calls args_read_string_and_trim. - * + * * @param args arguments string * @param word first argument, output, without quotes * @return true - success @@ -39,7 +48,7 @@ bool args_read_probably_quoted_string_and_trim(FuriString* args, FuriString* wor /** * @brief Convert hex ASCII values to byte array - * + * * @param args arguments string * @param bytes byte array pointer, output * @param bytes_count needed bytes count @@ -48,11 +57,23 @@ bool args_read_probably_quoted_string_and_trim(FuriString* args, FuriString* wor */ bool args_read_hex_bytes(FuriString* args, uint8_t* bytes, size_t bytes_count); +/** + * @brief Parses a duration value from a given string and converts it to milliseconds + * + * @param [in] args the input string containing the duration value. The string may include units (e.g., "10s", "0.5m"). + * @param [out] value pointer to store the parsed value in milliseconds + * @param [in] default_unit A default unit to be used if the input string does not contain a valid suffix. + * Supported units: `"ms"`, `"s"`, `"m"`, `"h"` + * If NULL, the function will assume milliseconds by default. + * @return `true` if the parsing and conversion succeeded, `false` otherwise. + */ +bool args_read_duration(FuriString* args, uint32_t* value, const char* default_unit); + /************************************ HELPERS ***************************************/ /** * @brief Get length of first word from arguments string - * + * * @param args arguments string * @return size_t length of first word */ @@ -60,7 +81,7 @@ size_t args_get_first_word_length(FuriString* args); /** * @brief Get length of arguments string - * + * * @param args arguments string * @return size_t length of arguments string */ @@ -68,7 +89,7 @@ size_t args_length(FuriString* args); /** * @brief Convert ASCII hex values to byte - * + * * @param hi_nibble ASCII hi nibble character * @param low_nibble ASCII low nibble character * @param byte byte pointer, output diff --git a/lib/toolbox/cli/cli_command.c b/lib/toolbox/cli/cli_command.c index a3c9ff292..60aa351e7 100644 --- a/lib/toolbox/cli/cli_command.c +++ b/lib/toolbox/cli/cli_command.c @@ -15,3 +15,18 @@ void cli_print_usage(const char* cmd, const char* usage, const char* arg) { printf("%s: illegal option -- %s\r\nusage: %s %s", cmd, arg, cmd, usage); } + +bool cli_sleep(PipeSide* side, uint32_t duration_in_ms) { + uint32_t passed_time = 0; + bool is_interrupted = false; + + do { + uint32_t left_time = duration_in_ms - passed_time; + uint32_t check_interval = left_time >= 100 ? 100 : left_time; + furi_delay_ms(check_interval); + passed_time += check_interval; + is_interrupted = cli_is_pipe_broken_or_is_etx_next_char(side); + } while(!is_interrupted && passed_time < duration_in_ms); + + return !is_interrupted; +} diff --git a/lib/toolbox/cli/cli_command.h b/lib/toolbox/cli/cli_command.h index 2d1d851d6..9d341f6d2 100644 --- a/lib/toolbox/cli/cli_command.h +++ b/lib/toolbox/cli/cli_command.h @@ -29,14 +29,14 @@ typedef enum { CliCommandFlagExternal = (1 << 4), /**< The command comes from a .fal file */ } CliCommandFlag; -/** +/** * @brief CLI command execution callback pointer - * + * * This callback will be called from a separate thread spawned just for your * command. The pipe will be installed as the thread's stdio, so you can use * `printf`, `getchar` and other standard functions to communicate with the * user. - * + * * @param [in] pipe Pipe that can be used to send and receive data. If * `CliCommandFlagDontAttachStdio` was not set, you can * also use standard C functions (printf, getc, etc.) to @@ -64,7 +64,7 @@ typedef struct { /** * @brief Detects if Ctrl+C has been pressed or session has been terminated - * + * * @param [in] side Pointer to pipe side given to the command thread * @warning This function also assumes that the pipe is installed as the * thread's stdio @@ -80,6 +80,21 @@ bool cli_is_pipe_broken_or_is_etx_next_char(PipeSide* side); */ void cli_print_usage(const char* cmd, const char* usage, const char* arg); +/** + * @brief Pause for a specified duration or until Ctrl+C is pressed or the + * session is terminated. + * + * @param [in] side Pointer to pipe side given to the command thread. + * @param [in] duration_in_ms Duration of sleep in milliseconds. + * @return `true` if the sleep completed without interruption. + * @return `false` if interrupted. + * + * @warning This function also assumes that the pipe is installed as the + * thread's stdio. + * @warning This function will consume 0 or 1 bytes from the pipe. + */ +bool cli_sleep(PipeSide* side, uint32_t duration_in_ms); + #define CLI_COMMAND_INTERFACE(name, execute_callback, flags, stack_depth, app_id) \ static const CliCommandDescriptor cli_##name##_desc = { \ #name, \ diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index a89625080..27b65e202 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -546,6 +546,8 @@ Function,-,arc4random_uniform,__uint32_t,__uint32_t Function,+,args_char_to_hex,_Bool,"char, char, uint8_t*" Function,+,args_get_first_word_length,size_t,FuriString* Function,+,args_length,size_t,FuriString* +Function,+,args_read_duration,_Bool,"FuriString*, uint32_t*, const char*" +Function,+,args_read_float_and_trim,_Bool,"FuriString*, float*" Function,+,args_read_hex_bytes,_Bool,"FuriString*, uint8_t*, size_t" Function,+,args_read_int_and_trim,_Bool,"FuriString*, int*" Function,+,args_read_probably_quoted_string_and_trim,_Bool,"FuriString*, FuriString*" @@ -802,6 +804,7 @@ Function,+,cli_shell_free,void,CliShell* Function,+,cli_shell_join,void,CliShell* Function,+,cli_shell_set_prompt,void,"CliShell*, const char*" Function,+,cli_shell_start,void,CliShell* +Function,+,cli_sleep,_Bool,"PipeSide*, uint32_t" Function,+,cli_vcp_disable,void,CliVcp* Function,+,cli_vcp_enable,void,CliVcp* Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*" @@ -2267,6 +2270,7 @@ Function,+,notification_internal_message,void,"NotificationApp*, const Notificat Function,+,notification_internal_message_block,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_message,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_message_block,void,"NotificationApp*, const NotificationSequence*" +Function,+,notification_messages_notes_frequency_from_name,float,const char* Function,-,nrand48,long,unsigned short[3] Function,+,number_input_alloc,NumberInput*, Function,+,number_input_free,void,NumberInput* diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index ef938ab27..de385684d 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -625,6 +625,8 @@ Function,-,arc4random_uniform,__uint32_t,__uint32_t Function,+,args_char_to_hex,_Bool,"char, char, uint8_t*" Function,+,args_get_first_word_length,size_t,FuriString* Function,+,args_length,size_t,FuriString* +Function,+,args_read_float_and_trim,_Bool,"FuriString*, float*" +Function,+,args_read_duration,_Bool,"FuriString*, uint32_t*, const char*" Function,+,args_read_hex_bytes,_Bool,"FuriString*, uint8_t*, size_t" Function,+,args_read_int_and_trim,_Bool,"FuriString*, int*" Function,+,args_read_probably_quoted_string_and_trim,_Bool,"FuriString*, FuriString*" @@ -881,6 +883,7 @@ Function,+,cli_shell_free,void,CliShell* Function,+,cli_shell_join,void,CliShell* Function,+,cli_shell_set_prompt,void,"CliShell*, const char*" Function,+,cli_shell_start,void,CliShell* +Function,+,cli_sleep,_Bool,"PipeSide*, uint32_t" Function,+,cli_vcp_disable,void,CliVcp* Function,+,cli_vcp_enable,void,CliVcp* Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*" @@ -2919,6 +2922,7 @@ Function,+,notification_internal_message,void,"NotificationApp*, const Notificat Function,+,notification_internal_message_block,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_message,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_message_block,void,"NotificationApp*, const NotificationSequence*" +Function,+,notification_messages_notes_frequency_from_name,float,const char* Function,-,nrand48,long,unsigned short[3] Function,+,number_input_alloc,NumberInput*, Function,+,number_input_free,void,NumberInput* From 3e45fce96d25c55bb4565d9a5d99a42fa771408b Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 30 Sep 2025 02:07:29 +0300 Subject: [PATCH 091/192] Try to decode BFT 2 buttons remotes on the fly --- lib/subghz/protocols/keeloq.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index e1ccf8c2f..921c3fadc 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -858,6 +858,11 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( } break; case KEELOQ_LEARNING_SECURE: + if((strcmp(furi_string_get_cstr(manufacture_code->name), "BFT") == 0)) { + if(instance->seed == 0) { + instance->seed = (fix & 0xFFFFFFF); + } + } man = subghz_protocol_keeloq_common_secure_learning( fix, instance->seed, manufacture_code->key); decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); From 999afe3f5b7d7a40f645173e8df87f8e6f66bde0 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 30 Sep 2025 02:16:07 +0300 Subject: [PATCH 092/192] post merge fix --- .../main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c | 2 +- applications/system/mfkey/mfkey.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c index aa4c2fe1c..63fdaed49 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c @@ -41,7 +41,7 @@ bool nfc_scene_mf_ultralight_c_keys_add_on_event(void* context, SceneManagerEven instance->scene_manager, NfcSceneMfUltralightCKeysWarnDuplicate); } else if(keys_dict_add_key(dict, key.data, sizeof(MfUltralightC3DesAuthKey))) { scene_manager_next_scene(instance->scene_manager, NfcSceneSaveSuccess); - dolphin_deed(DolphinDeedNfcMfcAdd); + dolphin_deed(DolphinDeedNfcKeyAdd); } else { scene_manager_previous_scene(instance->scene_manager); } diff --git a/applications/system/mfkey/mfkey.c b/applications/system/mfkey/mfkey.c index 4d55a5b03..9e3e71847 100644 --- a/applications/system/mfkey/mfkey.c +++ b/applications/system/mfkey/mfkey.c @@ -955,7 +955,7 @@ void mfkey(ProgramState* program_state) { keys_dict_add_key(user_dict, keyarray[i].data, sizeof(MfClassicKey)); } if(keyarray_size > 0) { - dolphin_deed(DolphinDeedNfcMfcAdd); + dolphin_deed(DolphinDeedNfcKeyAdd); } free(nonce_arr); keys_dict_free(user_dict); From a4f5f1e862c5b7adfb9ff7dfa84481e63f53d7af Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 30 Sep 2025 02:49:51 +0300 Subject: [PATCH 093/192] add elplast subghz protocol --- lib/subghz/protocols/elplast.c | 322 ++++++++++++++++++++++++++ lib/subghz/protocols/elplast.h | 109 +++++++++ lib/subghz/protocols/protocol_items.c | 1 + lib/subghz/protocols/protocol_items.h | 1 + 4 files changed, 433 insertions(+) create mode 100644 lib/subghz/protocols/elplast.c create mode 100644 lib/subghz/protocols/elplast.h diff --git a/lib/subghz/protocols/elplast.c b/lib/subghz/protocols/elplast.c new file mode 100644 index 000000000..c95eb84ba --- /dev/null +++ b/lib/subghz/protocols/elplast.c @@ -0,0 +1,322 @@ +#include "elplast.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolElplast" + +static const SubGhzBlockConst subghz_protocol_elplast_const = { + .te_short = 230, + .te_long = 1550, + .te_delta = 160, + .min_count_bit_for_found = 18, +}; + +struct SubGhzProtocolDecoderElplast { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderElplast { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + ElplastDecoderStepReset = 0, + ElplastDecoderStepSaveDuration, + ElplastDecoderStepCheckDuration, +} ElplastDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_elplast_decoder = { + .alloc = subghz_protocol_decoder_elplast_alloc, + .free = subghz_protocol_decoder_elplast_free, + + .feed = subghz_protocol_decoder_elplast_feed, + .reset = subghz_protocol_decoder_elplast_reset, + + .get_hash_data = subghz_protocol_decoder_elplast_get_hash_data, + .serialize = subghz_protocol_decoder_elplast_serialize, + .deserialize = subghz_protocol_decoder_elplast_deserialize, + .get_string = subghz_protocol_decoder_elplast_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_elplast_encoder = { + .alloc = subghz_protocol_encoder_elplast_alloc, + .free = subghz_protocol_encoder_elplast_free, + + .deserialize = subghz_protocol_encoder_elplast_deserialize, + .stop = subghz_protocol_encoder_elplast_stop, + .yield = subghz_protocol_encoder_elplast_yield, +}; + +const SubGhzProtocol subghz_protocol_elplast = { + .name = SUBGHZ_PROTOCOL_ELPLAST_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_elplast_decoder, + .encoder = &subghz_protocol_elplast_encoder, +}; + +void* subghz_protocol_encoder_elplast_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderElplast* instance = malloc(sizeof(SubGhzProtocolEncoderElplast)); + + instance->base.protocol = &subghz_protocol_elplast; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 256; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_elplast_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderElplast* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderElplast instance + */ +static void subghz_protocol_encoder_elplast_get_upload(SubGhzProtocolEncoderElplast* instance) { + furi_assert(instance); + size_t index = 0; + + // Send key and GAP + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + // Send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_elplast_const.te_long); + if(i == 1) { + //Send gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_elplast_const.te_long * 8); + } else { + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_elplast_const.te_short); + } + } else { + // Send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_elplast_const.te_short); + if(i == 1) { + //Send gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_elplast_const.te_long * 8); + } else { + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_elplast_const.te_long); + } + } + } + + instance->encoder.size_upload = index; + return; +} + +SubGhzProtocolStatus + subghz_protocol_encoder_elplast_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderElplast* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_elplast_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_encoder_elplast_get_upload(instance); + instance->encoder.is_running = true; + } while(false); + + return ret; +} + +void subghz_protocol_encoder_elplast_stop(void* context) { + SubGhzProtocolEncoderElplast* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_elplast_yield(void* context) { + SubGhzProtocolEncoderElplast* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_elplast_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderElplast* instance = malloc(sizeof(SubGhzProtocolDecoderElplast)); + instance->base.protocol = &subghz_protocol_elplast; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_elplast_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderElplast* instance = context; + free(instance); +} + +void subghz_protocol_decoder_elplast_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderElplast* instance = context; + instance->decoder.parser_step = ElplastDecoderStepReset; +} + +void subghz_protocol_decoder_elplast_feed(void* context, bool level, volatile uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderElplast* instance = context; + + // Elplast/P-11B/3BK/E.C.A Decoder + // 2025.09 - @xMasterX (MMX) + + // Key samples + // 00110010110000001010 = 32C0A + // 00110010110010000010 = 32C82 + + switch(instance->decoder.parser_step) { + case ElplastDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_elplast_const.te_long * 8) < + subghz_protocol_elplast_const.te_delta * 13)) { + //Found GAP + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = ElplastDecoderStepSaveDuration; + } + break; + case ElplastDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = ElplastDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = ElplastDecoderStepReset; + } + break; + case ElplastDecoderStepCheckDuration: + if(!level) { + // Bit 1 is long and short timing = 1550us HIGH (te_last) and 230us LOW + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_elplast_const.te_long) < + subghz_protocol_elplast_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_elplast_const.te_short) < + subghz_protocol_elplast_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = ElplastDecoderStepSaveDuration; + // Bit 0 is short and long timing = 230us HIGH (te_last) and 1550us LOW + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_elplast_const.te_short) < + subghz_protocol_elplast_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_elplast_const.te_long) < + subghz_protocol_elplast_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = ElplastDecoderStepSaveDuration; + } else if( + // End of the key + DURATION_DIFF(duration, subghz_protocol_elplast_const.te_long * 8) < + subghz_protocol_elplast_const.te_delta * 13) { + //Found next GAP and add bit 0 or 1 (only bit 0 was found on the remotes) + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_elplast_const.te_long) < + subghz_protocol_elplast_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_elplast_const.te_short) < + subghz_protocol_elplast_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } + // If got 18 bits key reading is finished + if(instance->decoder.decode_count_bit == + subghz_protocol_elplast_const.min_count_bit_for_found) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = ElplastDecoderStepReset; + } else { + instance->decoder.parser_step = ElplastDecoderStepReset; + } + } else { + instance->decoder.parser_step = ElplastDecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_elplast_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderElplast* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus subghz_protocol_decoder_elplast_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderElplast* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + subghz_protocol_decoder_elplast_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderElplast* instance = context; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, subghz_protocol_elplast_const.min_count_bit_for_found); +} + +void subghz_protocol_decoder_elplast_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderElplast* instance = context; + + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + + uint32_t code_found_reverse_lo = code_found_reverse & 0x000003ffffffffff; + + furi_string_cat_printf( + output, + "%s %db\r\n" + "Key: 0x%05lX\r\n" + "Yek: 0x%05lX", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0xFFFFFF), + code_found_reverse_lo); +} diff --git a/lib/subghz/protocols/elplast.h b/lib/subghz/protocols/elplast.h new file mode 100644 index 000000000..3f5f34002 --- /dev/null +++ b/lib/subghz/protocols/elplast.h @@ -0,0 +1,109 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_ELPLAST_NAME "Elplast" + +typedef struct SubGhzProtocolDecoderElplast SubGhzProtocolDecoderElplast; +typedef struct SubGhzProtocolEncoderElplast SubGhzProtocolEncoderElplast; + +extern const SubGhzProtocolDecoder subghz_protocol_elplast_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_elplast_encoder; +extern const SubGhzProtocol subghz_protocol_elplast; + +/** + * Allocate SubGhzProtocolEncoderElplast. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderElplast* pointer to a SubGhzProtocolEncoderElplast instance + */ +void* subghz_protocol_encoder_elplast_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderElplast. + * @param context Pointer to a SubGhzProtocolEncoderElplast instance + */ +void subghz_protocol_encoder_elplast_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderElplast instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_encoder_elplast_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderElplast instance + */ +void subghz_protocol_encoder_elplast_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderElplast instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_elplast_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderElplast. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderElplast* pointer to a SubGhzProtocolDecoderElplast instance + */ +void* subghz_protocol_decoder_elplast_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderElplast. + * @param context Pointer to a SubGhzProtocolDecoderElplast instance + */ +void subghz_protocol_decoder_elplast_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderElplast. + * @param context Pointer to a SubGhzProtocolDecoderElplast instance + */ +void subghz_protocol_decoder_elplast_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderElplast instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_elplast_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderElplast instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_elplast_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderElplast. + * @param context Pointer to a SubGhzProtocolDecoderElplast instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus subghz_protocol_decoder_elplast_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderElplast. + * @param context Pointer to a SubGhzProtocolDecoderElplast instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_decoder_elplast_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderElplast instance + * @param output Resulting text + */ +void subghz_protocol_decoder_elplast_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index 91ceaec32..27297f293 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -53,6 +53,7 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = { &subghz_protocol_revers_rb2, &subghz_protocol_feron, &subghz_protocol_roger, + &subghz_protocol_elplast, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index 1cde46ef5..ab7fd14f8 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -54,3 +54,4 @@ #include "revers_rb2.h" #include "feron.h" #include "roger.h" +#include "elplast.h" From 5a8a0dff5e9db128c7096ae076183b69a02f6f06 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 30 Sep 2025 02:54:11 +0300 Subject: [PATCH 094/192] if got no luck with seed set it back to zero --- lib/subghz/protocols/keeloq.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 921c3fadc..e9542eb34 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -858,9 +858,11 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( } break; case KEELOQ_LEARNING_SECURE: + bool reset_seed_back = false; if((strcmp(furi_string_get_cstr(manufacture_code->name), "BFT") == 0)) { if(instance->seed == 0) { instance->seed = (fix & 0xFFFFFFF); + reset_seed_back = true; } } man = subghz_protocol_keeloq_common_secure_learning( @@ -870,6 +872,8 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( *manufacture_name = furi_string_get_cstr(manufacture_code->name); keystore->mfname = *manufacture_name; return 1; + } else { + if(reset_seed_back) instance->seed = 0; } break; case KEELOQ_LEARNING_MAGIC_XOR_TYPE_1: From fdf532066ff1d990019e34e59e0f238f5e4817d5 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 30 Sep 2025 03:03:16 +0300 Subject: [PATCH 095/192] upd changelog --- CHANGELOG.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f3e3e1ac..d6654804a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,21 +1,25 @@ ## Main changes - Current API: 87.0 -* SubGHz: Tune Linear (edited by @WillyJL in PR #919 #920) (add better EZCode support) and Dickert MAHS protocol decoders +* SubGHz: Add **Elplast 18bit** static code protocol (hello Hackcat ^_^) +* SubGHz: Try to **decode BFT** (2 buttons remotes only) **on the fly** in regular Read mode (no more KL Unknown and all of that for free?!) (for 4 button remote follow docs [here](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md)) +* SubGHz: **Tune Linear** (edited by @WillyJL in PR #919 #920) (add better EZCode support) and **Dickert MAHS** protocol decoders * SubGHz: RAW protocol fixes (by @WillyJL) -* SubGHz: Add ZKTeco 430.5 MHz add manually support +* SubGHz: Add **ZKTeco 430.5 MHz** add manually support * SubGHz: Add variant of 'Add Manually' menu with manual editing for each value (PR #909 #911 #914 | by @MrLego8-9) * SubGHz: Temporarily remove HoneywellSec protocol due to unstable decoding and incorrect encoding -* OFW: LFRFID: Show ISO-3166 Country Names For Pet Chips -* OFW: JS views finished +* OFW: **NFC CLI commands** +* OFW: LFRFID: **Show ISO-3166 Country Names For Pet Chips** +* OFW: **JS views finished** * OFW: BLE: improved pairing security * OFW: FeliCa Emulation: Handle certain Polling commands in firmware -* OFW PR 4271: NFC: Ultralight C NFC App Key Management, Dictionary Attack (by @noproto) -* OFW PR 4265: NFC: Fix read crash with unexpectedly large MFC AUTH(0) response (by @WillyJL) -* OFW PR 4251: CLI: Fix long delay with quick connect/disconnect (by @WillyJL) -* LFRFID: Add additional procotols supported by EM4305 chipset (by @jamisonderek) +* OFW PR 4271: NFC: **Ultralight C NFC App Key Management, Dictionary Attack** (by @noproto) +* OFW PR 4265: NFC: **Fix read crash** with unexpectedly large MFC AUTH(0) response (by @WillyJL) +* OFW PR 4251: CLI: **Fix long delay** with quick connect/disconnect (by @WillyJL) +* LFRFID: Add additional procotols supported by **EM4305** chipset (by @jamisonderek) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * SubGHz: Fix crash in add manually menu +* OFW: cli: Buzzer command * OFW: Update demo_windows.txt * OFW: Fix PVS warnings * OFW: NFC: Amusement IC Card Parser (FeliCa Lite & Lite-S) @@ -32,7 +36,7 @@

#### Known NFC post-refactor regressions list: - Mifare Mini clones reading is broken (original mini working fine) (OFW) -- NFC CLI was removed with refactoring (OFW) (will be back soon) +- While reading some EMV capable cards via NFC->Read flipper may crash due to Desfire poller issue, read those cards via Extra actions->Read specific card type->EMV ---- From b604514a2b74dbdb32e95bf301df1aceafef5589 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 30 Sep 2025 03:30:45 +0300 Subject: [PATCH 096/192] upd readme --- ReadMe.md | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index eaa221ea7..5e8903f9c 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -54,6 +54,7 @@ Before getting started: > Sub‑GHz Library & HAL >
> +> - Many new protocols added > - Regional TX restrictions removed > - Extra Sub-GHz frequencies added > - Frequency range can be extended in settings file _(warning: It can damage Flipper's hardware)_ @@ -80,7 +81,7 @@ Before getting started: > - FAAC SLH, BFT Mitto / Somfy Telis / Nice Flor S / CAME Atomo, etc. manual creation with programming new remote into receiver (use button 0xF for BFT Mitto, 0x8 (Prog) on Somfy Telis, (right arrow button for other protocols)) > - Debug mode counter increase settings (+1 → +5, +10, default: +1) > - Debug PIN output settings for protocol development -> - Ignore options - Alarms: Hollarm, GangQi | Cars: Kia, Starline, ScherKhan | Sensors: Magellan, Honeywell, Honeywell WDB (doorbells), Legrand (doorbells), Feron (RGB lights) +> - Ignore options - Alarms: Hollarm, GangQi | Cars: Kia, Starline, ScherKhan | Sensors: Magellan, Honeywell WDB (doorbells), Legrand (doorbells), Feron (RGB lights) > >
@@ -167,9 +168,10 @@ Thanks to Official team (to their SubGHz Developer, Skorp) for implementing supp Decoders/Encoders or emulation (+ programming mode) support made by @xMasterX
-- Roger (static 28 bit) with add manually support (by @xMasterX & @mishamyte) -- V2 Phoenix (Phox) (dynamic 52 bit) (by @xMasterX & @RocketGod-git) -- Marantec (static 49 bit) (add manually support and CRC verify) (by @xMasterX & @li0ard) +- Elplast/P-11B/3BK/E.C.A (static 18 bit) +- Roger (static 28 bit) with add manually support (thanks @mishamyte) +- V2 Phoenix (Phox) (dynamic 52 bit) (thanks @RocketGod-git) +- Marantec (static 49 bit) (add manually support and CRC verify) (thanks @li0ard) - Feron (static 32 bit) - ReversRB2 / RB2M (static 64 bit) with add manually support - Marantec24 (static 24 bit) with add manually support @@ -208,10 +210,14 @@ Thanks to Official team (to their SubGHz Developer, Skorp) for implementing supp ## ❤️ Please support development of the project The majority of this project is developed and maintained by me, @xMasterX. -Our team is small and the guys are working on this project as much as they can solely based on the enthusiasm they have for this project and the community. -- `@Leptopt1los` - NFC, RFID, Plugins, and many other things +Our team is small and the guys are working on this project as much as they can solely based on the enthusiasm they have for this project and the community. +- `@mishamyte` - NFC, RFID, SubGHz and chats moderation +- `@quen0n` - Hardware, SubGHz and chats moderation +- `@Drone1950` - Reverse Engineering, telegram bot and chats moderation +- `@HackcatDev` - Support and chats moderation +- `@Leptopt1los` - NFC, RFID, Plugins, chat moderation and many other things - `@gid9798` - SubGHz, Plugins, many other things - currently offline :( -- `@assasinfil` - SubGHz protocols, NFC parsers +- `@assasinfil` - SubGHz protocols, NFC parsers, chat moderation - `@Svaarich` - UI design and animations - `@amec0e` - Infrared assets - Community moderators in Telegram, Discord, and Reddit From df4bf6981857bcafb0c755d6818da9ab6688b0be Mon Sep 17 00:00:00 2001 From: Null <143842038+MrNull1@users.noreply.github.com> Date: Tue, 30 Sep 2025 06:39:58 -0500 Subject: [PATCH 097/192] IR: Add Onn Roku TV control to universal remote (#4201) * added ONN brand roku TV to ir lib * pvs: fixed warning --------- Co-authored-by: hedger Co-authored-by: hedger --- .../infrared/resources/infrared/assets/tv.ir | 26 +++++++++++++++++++ applications/services/cli/commands/buzzer.c | 6 ++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/applications/main/infrared/resources/infrared/assets/tv.ir b/applications/main/infrared/resources/infrared/assets/tv.ir index bff8adfa0..2305c4eee 100644 --- a/applications/main/infrared/resources/infrared/assets/tv.ir +++ b/applications/main/infrared/resources/infrared/assets/tv.ir @@ -4833,3 +4833,29 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 590 412 592 4026 593 5030 593 412 592 4025 594 5029 594 412 616 4002 618 5003 593 439 564 4027 592 439 564 4029 590 5033 590 5035 588 440 563 +# +# Model: ONN Roku TV +# +name: Vol_dn +type: parsed +protocol: NECext +address: EA C7 00 00 +command: 10 EF 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: EA C7 00 00 +command: 0F F0 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: EA C7 00 00 +command: 20 DF 00 00 +# +name: Power +type: parsed +protocol: NECext +address: EA C7 00 00 +command: 17 E8 00 00 diff --git a/applications/services/cli/commands/buzzer.c b/applications/services/cli/commands/buzzer.c index 3c1673149..5a5f49c42 100644 --- a/applications/services/cli/commands/buzzer.c +++ b/applications/services/cli/commands/buzzer.c @@ -1,5 +1,6 @@ #include "../cli_main_commands.h" #include +#include #include #include @@ -13,6 +14,9 @@ void cli_command_buzzer_print_usage(bool is_freq_subcommand, FuriString* args) { } } +// Consider volume effectively zero if below this threshold +#define BUZZER_VOLUME_EPSILON (1e-3f) + float cli_command_buzzer_read_frequency(bool is_freq_subcommand, FuriString* args) { float frequency = 0.0f; @@ -111,7 +115,7 @@ void execute(PipeSide* pipe, FuriString* args, void* context) { printf("Flipper is in stealth mode. Unmute the device to control buzzer."); break; } - if(notification->settings.speaker_volume == 0.0f) { + if(fabsf(notification->settings.speaker_volume) < BUZZER_VOLUME_EPSILON) { printf("Sound is disabled in settings. Increase volume to control buzzer."); break; } From a5dedec00ab132025e3be51870459433aec3c187 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 1 Oct 2025 02:34:46 +0300 Subject: [PATCH 098/192] ensure correct byte shifts --- .../main/subghz/helpers/subghz_txrx_create_protocol_key.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c index 5a7e07e0e..783273e6b 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c @@ -422,7 +422,8 @@ void subghz_txrx_gen_serial_gangqi(uint64_t* result_key) { // Add bytesum to the end // serial | const_and_button - *result_key = (serial << 18) | (const_and_button << 10) | (bytesum << 2); + *result_key = ((uint64_t)serial << 18) | ((uint64_t)const_and_button << 10) | + ((uint64_t)bytesum << 2); } void subghz_txrx_gen_key_marantec(uint64_t* result_key) { From 7db92ed1f815f4a2c9d7cd0393a93e7fea8c5ba2 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 1 Oct 2025 03:00:43 +0300 Subject: [PATCH 099/192] decoders reset step at reset func --- lib/subghz/protocols/marantec.c | 1 + lib/subghz/protocols/revers_rb2.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/lib/subghz/protocols/marantec.c b/lib/subghz/protocols/marantec.c index edb176635..8ca1f8b38 100644 --- a/lib/subghz/protocols/marantec.c +++ b/lib/subghz/protocols/marantec.c @@ -265,6 +265,7 @@ void subghz_protocol_decoder_marantec_free(void* context) { void subghz_protocol_decoder_marantec_reset(void* context) { furi_assert(context); SubGhzProtocolDecoderMarantec* instance = context; + instance->decoder.parser_step = MarantecDecoderStepReset; manchester_advance( instance->manchester_saved_state, ManchesterEventReset, diff --git a/lib/subghz/protocols/revers_rb2.c b/lib/subghz/protocols/revers_rb2.c index 510e2698a..e6524174f 100644 --- a/lib/subghz/protocols/revers_rb2.c +++ b/lib/subghz/protocols/revers_rb2.c @@ -227,6 +227,8 @@ void subghz_protocol_decoder_revers_rb2_free(void* context) { void subghz_protocol_decoder_revers_rb2_reset(void* context) { furi_assert(context); SubGhzProtocolDecoderRevers_RB2* instance = context; + instance->decoder.parser_step = Revers_RB2DecoderStepReset; + instance->header_count = 0; manchester_advance( instance->manchester_saved_state, ManchesterEventReset, From fad487df0ebbf7b7ab41b70e7240a9299b7c6230 Mon Sep 17 00:00:00 2001 From: MMX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 1 Oct 2025 17:05:50 +0300 Subject: [PATCH 100/192] SubGHz: Added 9 new protocols, fixes to existing protocols (#4255) * Fix Typos * Tune decoders * Better parsing, show more data in existing protocols * Add new protocols * Update keeloqs * Add unit tests & raws * Add honeywell unittest * Comment until better solution is found Adding GAPs to be sent first to make signal better suitable for decoder (decoding from only one signal sample) does nothing, needs something else TODO: Fix encoders? * suppressed missing issue warning * subghz: re-enabled failing encoder tests * Fix two? 3 left * properly do gangqi and marantec for unit test and real use * fix unit tests now * fix possible memory leak * reset decoder step too * subghz: extra encoder safety; report random signal test results on failure * unit_tests: subghz: renamed test file for consistency * subghz: more explicit buffer position resets * Fix gangqi samples --------- Co-authored-by: hedger Co-authored-by: hedger --- .../resources/unit_tests/subghz/feron.sub | 7 + .../resources/unit_tests/subghz/feron_raw.sub | 31 ++ .../resources/unit_tests/subghz/gangqi.sub | 7 + .../unit_tests/subghz/gangqi_raw.sub | 12 + .../resources/unit_tests/subghz/hay21_raw.sub | 10 + .../resources/unit_tests/subghz/hollarm.sub | 7 + .../unit_tests/subghz/hollarm_raw.sub | 10 + .../resources/unit_tests/subghz/legrand.sub | 8 + .../unit_tests/subghz/legrand_raw.sub | 22 + .../unit_tests/subghz/marantec24.sub | 7 + .../unit_tests/subghz/marantec24_raw.sub | 14 + .../unit_tests/subghz/revers_rb2.sub | 7 + .../unit_tests/subghz/revers_rb2_raw.sub | 17 + .../resources/unit_tests/subghz/roger.sub | 7 + .../resources/unit_tests/subghz/roger_raw.sub | 23 + .../unit_tests/tests/subghz/subghz_test.c | 115 ++++- .../resources/subghz/assets/keeloq_mfcodes | 119 ++--- lib/subghz/protocols/alutech_at_4n.h | 2 +- lib/subghz/protocols/came.c | 5 +- lib/subghz/protocols/came_twee.c | 2 + lib/subghz/protocols/dooya.c | 4 +- lib/subghz/protocols/feron.c | 352 +++++++++++++++ lib/subghz/protocols/feron.h | 109 +++++ lib/subghz/protocols/gangqi.c | 407 +++++++++++++++++ lib/subghz/protocols/gangqi.h | 109 +++++ lib/subghz/protocols/hay21.c | 269 ++++++++++++ lib/subghz/protocols/hay21.h | 74 ++++ lib/subghz/protocols/hollarm.c | 408 +++++++++++++++++ lib/subghz/protocols/hollarm.h | 109 +++++ lib/subghz/protocols/holtek_ht12x.c | 6 +- lib/subghz/protocols/hormann.c | 2 + lib/subghz/protocols/intertechno_v3.c | 1 + lib/subghz/protocols/keeloq.c | 3 +- lib/subghz/protocols/legrand.c | 398 +++++++++++++++++ lib/subghz/protocols/legrand.h | 117 +++++ lib/subghz/protocols/magellan.c | 120 ++++- lib/subghz/protocols/marantec.c | 37 +- lib/subghz/protocols/marantec.h | 8 + lib/subghz/protocols/marantec24.c | 352 +++++++++++++++ lib/subghz/protocols/marantec24.h | 109 +++++ lib/subghz/protocols/phoenix_v2.c | 67 ++- lib/subghz/protocols/power_smart.c | 2 + lib/subghz/protocols/protocol_items.c | 8 + lib/subghz/protocols/protocol_items.h | 8 + lib/subghz/protocols/public_api.h | 2 +- lib/subghz/protocols/raw.c | 6 +- lib/subghz/protocols/revers_rb2.c | 415 ++++++++++++++++++ lib/subghz/protocols/revers_rb2.h | 109 +++++ lib/subghz/protocols/roger.c | 341 ++++++++++++++ lib/subghz/protocols/roger.h | 109 +++++ lib/subghz/protocols/scher_khan.c | 42 +- lib/subghz/protocols/secplus_v2.c | 2 + 52 files changed, 4432 insertions(+), 105 deletions(-) create mode 100644 applications/debug/unit_tests/resources/unit_tests/subghz/feron.sub create mode 100644 applications/debug/unit_tests/resources/unit_tests/subghz/feron_raw.sub create mode 100644 applications/debug/unit_tests/resources/unit_tests/subghz/gangqi.sub create mode 100644 applications/debug/unit_tests/resources/unit_tests/subghz/gangqi_raw.sub create mode 100644 applications/debug/unit_tests/resources/unit_tests/subghz/hay21_raw.sub create mode 100644 applications/debug/unit_tests/resources/unit_tests/subghz/hollarm.sub create mode 100644 applications/debug/unit_tests/resources/unit_tests/subghz/hollarm_raw.sub create mode 100644 applications/debug/unit_tests/resources/unit_tests/subghz/legrand.sub create mode 100644 applications/debug/unit_tests/resources/unit_tests/subghz/legrand_raw.sub create mode 100644 applications/debug/unit_tests/resources/unit_tests/subghz/marantec24.sub create mode 100644 applications/debug/unit_tests/resources/unit_tests/subghz/marantec24_raw.sub create mode 100644 applications/debug/unit_tests/resources/unit_tests/subghz/revers_rb2.sub create mode 100644 applications/debug/unit_tests/resources/unit_tests/subghz/revers_rb2_raw.sub create mode 100644 applications/debug/unit_tests/resources/unit_tests/subghz/roger.sub create mode 100644 applications/debug/unit_tests/resources/unit_tests/subghz/roger_raw.sub create mode 100644 lib/subghz/protocols/feron.c create mode 100644 lib/subghz/protocols/feron.h create mode 100644 lib/subghz/protocols/gangqi.c create mode 100644 lib/subghz/protocols/gangqi.h create mode 100644 lib/subghz/protocols/hay21.c create mode 100644 lib/subghz/protocols/hay21.h create mode 100644 lib/subghz/protocols/hollarm.c create mode 100644 lib/subghz/protocols/hollarm.h create mode 100644 lib/subghz/protocols/legrand.c create mode 100644 lib/subghz/protocols/legrand.h create mode 100644 lib/subghz/protocols/marantec24.c create mode 100644 lib/subghz/protocols/marantec24.h create mode 100644 lib/subghz/protocols/revers_rb2.c create mode 100644 lib/subghz/protocols/revers_rb2.h create mode 100644 lib/subghz/protocols/roger.c create mode 100644 lib/subghz/protocols/roger.h diff --git a/applications/debug/unit_tests/resources/unit_tests/subghz/feron.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/feron.sub new file mode 100644 index 000000000..14c293ca8 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/subghz/feron.sub @@ -0,0 +1,7 @@ +Filetype: Flipper SubGhz Key File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: Feron +Bit: 32 +Key: 00 00 00 00 63 38 84 7B diff --git a/applications/debug/unit_tests/resources/unit_tests/subghz/feron_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/feron_raw.sub new file mode 100644 index 000000000..16249dce5 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/subghz/feron_raw.sub @@ -0,0 +1,31 @@ +Filetype: Flipper SubGhz RAW File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: RAW +RAW_Data: 329 -296 295 -66 163 -98 2441 -66 9337 -5690 131 -2574 65 -692 65 -460 165 -230 329 -266 131 -134 97 -400 197 -66 197 -66 12303 -5844 97 -200 167 -102 197 -334 297 -100 559 -166 133 -132 3425 -98 99 -13334 65 -66 129 -822 99 -598 133 -366 131 -68 163 -100 131 -66 99 -98 361 -130 63 -296 2175 -100 163 -100 1095 -100 859 -7534 165 -5584 99 -198 99 -1066 99 -232 99 -266 67 -66 99 -132 97 -564 131 -66 197 -132 231 -98 261 -560 195 -166 885 -98 1579 -198 729 -134 431 -68 5783 -5922 131 -992 65 -166 1459 -262 65 -526 97 -100 163 -230 65 -362 197 -958 99 -634 99 -132 65 -234 99 -394 527 -132 461 -130 323 -18704 231 -564 99 -600 133 -98 1359 -66 165 -166 133 -232 1657 -98 1907 -132 4497 -496 99 -66 167 -100 133 -168 233 -264 167 -66 863 -296 399 -68 2453 -134 635 -66 331 -200 395 -134 7025 -4436 165 -528 99 -298 229 -162 97 -362 197 -524 361 -428 65 -66 99 -166 199 -66 67 -198 165 -466 99 -428 263 -330 361 -132 293 -296 225 -66 3753 -66 291 -66 133 -66 6907 -4898 63 -558 65 -918 99 -100 623 -132 359 -100 457 -98 97 -98 363 -100 559 -132 297 -166 129 -296 97 -262 99 -722 297 -196 1187 -266 199 -168 133 -68 597 -66 301 -132 429 -64 6665 -5372 65 -1054 329 -200 167 -100 629 -100 293 -200 165 -100 133 -132 133 -332 197 -100 233 -496 65 -166 561 -232 165 -296 1127 -64 2753 -66 755 -164 12059 -360 65 -266 99 -66 65 -2612 99 -1780 97 -164 1083 -330 197 -100 397 -66 2095 -98 793 -102 65 -100 10223 -594 65 -232 97 -984 131 -296 99 -166 99 -664 197 -66 261 -230 97 -68 1683 -266 691 -98 335 -66 133 -8384 99 -2300 97 -1710 165 -230 229 -426 97 -134 359 -300 555 -164 193 -132 99 -132 163 -100 497 -298 823 -12964 99 -1874 97 -132 97 -262 97 -328 133 -100 231 -198 131 -132 195 -460 63 -198 131 -66 195 -66 1791 -100 97 -66 131 -64 955 -66 559 -98 233 -66 4633 -5270 263 -100 131 -198 99 -164 331 -132 303 -134 133 -430 233 -1192 67 -498 99 -302 233 -300 597 -66 99 -132 1261 -134 65 -5292 63 -756 131 -884 131 -130 231 -98 197 -198 195 -66 97 -592 261 -298 163 -922 163 -164 65 -656 131 -68 231 -66 99 -430 1295 -68 529 -100 1263 -68 1687 -14880 99 -236 65 -566 163 -758 99 -68 461 -98 559 -130 165 -162 995 -100 1787 -196 195 -524 295 -66 821 -66 5703 -3860 63 -3736 523 -166 161 -64 97 -132 163 -164 257 -164 197 -656 229 -1420 +RAW_Data: 99 -598 97 -168 297 -132 199 -232 629 -132 131 -100 97 -266 499 -66 597 -166 229 -66 429 -68 3463 -3518 97 -2346 265 -66 433 -66 503 -464 65 -68 987 -98 131 -98 557 -66 1583 -66 431 -100 1097 -134 5651 -6742 99 -830 197 -102 233 -398 67 -66 197 -168 267 -366 197 -332 363 -364 131 -2374 329 -66 331 -200 133 -100 333 -134 299 -66 4079 -5972 101 -1558 393 -230 331 -330 63 -98 65 -328 131 -426 133 -66 197 -196 65 -1352 65 -590 97 -230 161 -96 163 -230 329 -198 97 -134 1525 -66 433 -168 1455 -230 6225 -2024 99 -266 99 -268 133 -264 297 -100 167 -168 233 -168 267 -660 369 -234 337 -300 97 -132 263 -134 1133 -200 137 -136 265 -166 167 -232 99 -300 167 -66 99 -200 197 -462 131 -432 99 -300 165 -862 197 -496 531 -332 231 -132 1015 -100 427 -100 197 -1018 99 -66 97 -200 199 -168 199 -100 465 -100 197 -68 65 -132 3155 -66 12365 -328 131 -696 133 -896 265 -472 165 -132 133 -232 129 -68 165 -66 723 -564 529 -66 1491 -266 99 -132 397 -66 501 -6672 65 -1580 97 -1420 199 -166 265 -134 699 -168 133 -100 663 -66 637 -68 2119 -66 697 -66 1027 -66 3319 -7648 97 -162 293 -460 197 -230 591 -166 227 -98 65 -262 329 -198 163 -128 97 -328 131 -426 229 -368 299 -202 265 -168 331 -100 729 -68 1597 -66 133 -66 961 -68 231 -166 99 -234 5397 -5158 65 -1328 97 -502 99 -166 199 -198 131 -68 199 -264 231 -436 163 -332 199 -100 131 -166 165 -168 97 -100 133 -600 165 -1754 99 -66 195 -228 263 -164 297 -232 365 -200 499 -66 297 -66 1625 -66 399 -166 5011 -6110 97 -1482 65 -264 367 -166 65 -68 465 -892 197 -330 231 -430 133 -12408 129 -5982 295 -562 165 -132 1033 -66 997 -66 799 -66 297 -196 959 -100 729 -66 2115 -66 2639 -5752 99 -802 365 -232 97 -362 97 -2510 131 -534 99 -296 131 -268 1161 -66 131 -66 1057 -100 1325 -66 463 -4028 97 -364 65 -728 197 -234 99 -100 97 -100 65 -132 265 -428 333 -628 195 -262 99 -228 131 -1766 163 -562 297 -132 465 -302 165 -100 133 -198 365 -132 465 -66 1787 -4594 65 -2792 65 -164 131 -66 131 -98 197 -296 131 -332 165 -330 693 -298 101 -366 265 -1892 65 -134 65 -164 201 -200 65 -266 265 -232 133 -200 235 -200 1001 -68 991 -66 597 -100 963 -200 2913 -9784 65 -426 229 -954 131 -398 531 -198 97 -98 163 -130 131 -326 359 -100 2367 -168 691 -100 13125 -362 65 -394 197 -332 133 -200 299 -366 167 -264 299 -132 835 -234 2493 -132 +RAW_Data: 99 -134 565 -334 231 -134 167 -200 1425 -132 3943 -3864 563 -66 233 -2296 99 -198 165 -234 199 -298 263 -134 131 -530 299 -398 233 -1336 165 -166 65 -132 99 -232 295 -130 99 -328 97 -132 97 -66 97 -132 65 -262 2145 -132 195 -196 65 -362 231 -196 261 -98 65 -198 229 -132 229 -196 793 -66 555 -100 297 -66 493 -230 359 -100 195 -296 229 -66 99 -166 10343 -3654 165 -1392 65 -834 65 -132 495 -100 395 -232 331 -360 265 -132 1681 -5474 165 -2122 65 -200 65 -232 99 -1132 65 -200 165 -102 133 -68 199 -100 663 -234 165 -298 163 -68 1597 -98 1199 -66 197 -332 5539 -1934 65 -3354 133 -168 167 -2354 101 -930 65 -1266 131 -298 99 -1328 259 -232 563 -200 297 -100 65 -132 131 -66 1681 -100 267 -98 331 -168 299 -132 629 -66 5981 -2810 165 -3282 99 -132 227 -98 163 -430 231 -330 293 -66 65 -164 99 -958 65 -134 67 -432 99 -1426 297 -264 295 -132 363 -332 97 -366 133 -98 2869 -102 1755 -100 2911 -7466 261 -196 193 -260 259 -298 135 -66 133 -400 199 -266 195 -334 97 -268 365 -864 129 -1132 395 -300 395 -132 933 -200 367 -134 997 -66 233 -198 5415 -5556 65 -1292 65 -68 131 -268 165 -264 231 -1792 65 -764 65 -1394 299 -396 451 -854 323 -808 97 -62 227 -838 231 -868 635 -460 669 -402 275 -810 271 -838 681 -398 711 -388 691 -392 275 -832 279 -780 311 -774 741 -376 285 -776 321 -762 325 -776 297 -776 765 -324 345 -752 345 -724 341 -756 767 -326 773 -334 743 -340 771 -348 313 -770 741 -350 753 -556 497 -4666 359 -736 779 -318 759 -326 351 -758 347 -726 339 -760 767 -322 775 -334 351 -734 341 -732 769 -318 781 -326 777 -334 355 -724 349 -740 347 -724 775 -324 349 -734 353 -734 355 -756 347 -724 771 -326 347 -758 345 -732 353 -732 755 -350 771 -320 765 -322 763 -356 317 -758 755 -352 755 -524 557 -4642 323 -746 795 -320 773 -296 389 -722 347 -726 375 -724 767 -324 779 -332 355 -724 347 -724 775 -326 779 -334 747 -338 357 -730 355 -728 355 -756 743 -350 355 -722 353 -720 349 -754 345 -758 743 -346 331 -756 323 -744 361 -752 761 -322 773 -332 747 -336 779 -310 343 -764 735 -348 773 -516 561 -4620 349 -744 781 -320 763 -330 351 -756 347 -724 341 -758 769 -324 775 -332 351 -736 341 -734 771 -324 777 -322 773 -332 355 -724 349 -724 377 -724 769 -324 347 -758 345 -734 351 -732 351 -722 791 -320 351 -726 359 -736 321 -768 753 -322 801 -330 753 -334 755 -336 353 -732 765 -354 727 -548 +RAW_Data: 545 -4646 351 -738 755 -322 801 -296 389 -722 347 -740 347 -726 773 -326 777 -334 355 -730 355 -730 779 -308 777 -346 743 -348 321 -758 353 -722 351 -750 769 -314 333 -742 351 -740 361 -720 363 -754 761 -322 345 -756 347 -724 339 -756 767 -324 775 -332 745 -338 779 -310 357 -728 777 -348 739 -550 513 -4672 323 -776 749 -320 763 -328 353 -758 347 -740 347 -726 771 -326 777 -334 355 -724 349 -758 743 -326 777 -332 747 -340 351 -732 343 -736 351 -756 755 -320 371 -724 375 -724 335 -746 337 -746 771 -326 349 -756 347 -722 341 -756 767 -326 775 -332 747 -338 777 -310 343 -766 745 -346 759 -546 509 -4650 345 -770 743 -324 785 -326 347 -758 345 -734 349 -734 769 -344 763 -296 373 -744 333 -754 765 -322 777 -332 745 -340 351 -732 343 -734 351 -756 753 -322 367 -724 341 -760 335 -748 337 -756 767 -324 345 -756 345 -734 351 -732 767 -314 797 -296 769 -322 801 -296 353 -756 777 -332 747 -536 537 -4634 359 -742 777 -320 763 -328 349 -756 347 -726 339 -756 767 -322 775 -334 355 -732 355 -728 779 -310 757 -338 757 -342 351 -732 343 -734 353 -758 753 -322 367 -724 341 -760 335 -748 337 -756 765 -324 347 -736 351 -724 377 -726 769 -324 777 -334 747 -338 779 -310 343 -764 739 -330 769 -546 523 -4664 343 -758 743 -346 761 -324 347 -752 335 -754 333 -746 769 -324 775 -334 351 -734 341 -734 773 -324 775 -322 779 -332 355 -722 349 -740 347 -724 773 -324 351 -756 347 -724 339 -758 337 -748 769 -322 347 -756 347 -722 341 -756 767 -324 777 -332 747 -338 779 -310 341 -766 771 -298 767 -548 523 -4658 329 -770 777 -320 755 -328 349 -756 347 -740 347 -726 773 -324 777 -332 357 -722 349 -754 743 -326 779 -334 745 -342 351 -734 349 -752 347 -722 781 -312 333 -780 321 -742 361 -752 331 -752 763 -324 343 -756 347 -734 351 -734 767 -322 765 -352 753 -346 753 -296 373 -750 763 -322 775 -530 535 -4632 359 -736 779 -320 761 -328 351 -732 351 -758 347 -724 775 -326 781 -330 355 -724 349 -756 743 -326 779 -334 745 -340 351 -732 341 -762 317 -776 749 -320 367 -722 339 -758 337 -746 337 -746 773 -326 347 -736 351 -754 347 -726 771 -324 779 -332 747 -340 779 -310 343 -768 745 -348 753 -516 543 -4650 383 -716 769 -348 753 -326 343 -750 333 -754 335 -744 769 -324 775 -334 351 -734 343 -736 771 -324 775 -322 775 -332 351 -734 353 -722 349 -754 755 -318 337 -768 319 -766 325 -746 361 -752 763 -320 345 -756 345 -726 +RAW_Data: 337 -746 771 -324 777 -334 743 -340 769 -352 311 -768 741 -324 781 -520 561 -4636 359 -736 777 -320 757 -326 349 -756 347 -724 341 -758 769 -324 777 -332 351 -734 341 -734 773 -324 779 -320 773 -332 349 -734 353 -722 349 -756 753 -352 301 -766 319 -768 325 -746 365 -752 761 -322 345 -724 377 -734 351 -732 767 -316 761 -332 767 -322 769 -330 351 -766 745 -336 777 -512 541 -4668 325 -742 779 -320 761 -330 351 -756 347 -740 347 -724 773 -324 779 -332 355 -730 355 -726 777 -346 755 -308 777 -346 319 -760 309 -764 347 -752 751 -320 367 -724 375 -722 337 -746 337 -746 771 -326 349 -756 347 -724 339 -756 767 -324 775 -330 745 -340 769 -322 341 -766 743 -322 781 -552 529 -4628 359 -736 781 -318 759 -328 351 -734 341 -766 323 -746 779 -322 761 -328 351 -766 353 -724 777 -330 751 -336 753 -336 355 -728 355 -726 355 -756 741 -352 353 -722 351 -722 347 -752 349 -738 777 -320 361 -724 339 -756 335 -746 769 -326 775 -334 747 -338 755 -340 357 -724 783 -330 747 -538 539 -4658 347 -750 753 -320 763 -330 351 -732 353 -756 347 -724 775 -326 781 -332 355 -724 347 -756 741 -326 779 -334 747 -340 351 -734 349 -752 347 -722 779 -312 333 -780 323 -740 361 -752 331 -754 763 -320 345 -756 345 -724 339 -746 771 -324 779 -332 747 -338 769 -320 343 -766 741 -348 753 -556 531 -4624 349 -776 747 -320 765 -328 351 -758 347 -726 339 -758 767 -324 777 -332 351 -732 343 -732 771 -318 781 -324 777 -332 355 -722 349 -722 379 -734 745 -340 351 -732 345 -734 349 -760 323 -746 793 -322 341 -722 379 -722 339 -744 771 -324 775 -334 745 -340 757 -340 355 -728 779 -346 741 -548 511 -4676 323 -744 777 -318 763 -328 351 -758 347 -724 377 -722 767 -326 777 -332 355 -732 353 -724 777 -332 745 -338 777 -310 343 -734 349 -744 353 -742 759 -322 371 -734 351 -732 345 -734 353 -756 755 -322 367 -724 341 -770 349 -734 767 -318 765 -332 765 -320 761 -328 351 -756 745 -362 747 -536 537 -4662 317 -778 747 -322 797 -294 389 -724 347 -728 337 -746 771 -326 777 -334 351 -734 343 -734 777 -326 773 -322 775 -330 355 -724 349 -732 351 -732 767 -352 333 -736 351 -738 325 -780 331 -752 763 -322 345 -736 351 -758 345 -724 769 -324 777 -332 743 -340 769 -356 317 -752 769 -314 787 -514 539 -4638 345 -768 773 -320 753 -326 351 -760 347 -712 379 -724 773 -322 777 -334 351 -734 355 -718 789 -320 769 -324 759 -348 323 -758 355 -720 351 -720 +RAW_Data: 801 -314 333 -740 351 -740 361 -720 363 -754 761 -322 345 -726 377 -724 337 -746 769 -324 777 -334 743 -340 769 -320 343 -768 739 -324 783 -552 529 -4634 359 -736 779 -320 757 -326 351 -760 345 -722 341 -756 767 -322 777 -334 349 -736 353 -720 763 -354 759 -320 755 -350 331 -740 353 -740 327 -748 793 -320 341 -758 345 -722 337 -746 339 -746 773 -324 349 -756 345 -724 341 -756 767 -326 775 -334 745 -338 777 -310 343 -764 771 -296 767 -548 523 -4650 347 -742 781 -320 765 -328 351 -732 351 -758 347 -724 777 -326 779 -332 353 -724 349 -754 741 -326 779 -334 745 -342 351 -732 349 -754 347 -722 779 -348 299 -778 321 -742 363 -718 363 -754 763 -322 343 -758 345 -732 351 -732 769 -354 725 -350 755 -348 737 -350 353 -722 763 -354 757 -510 543 -4652 353 -742 791 -288 773 -330 351 -734 387 -722 347 -724 775 -326 781 -330 355 -732 355 -730 745 -342 777 -346 755 -310 357 -726 355 -724 353 -758 755 -348 355 -722 351 -722 349 -756 345 -756 747 -346 329 -752 321 -746 361 -752 763 -322 773 -332 751 -336 745 -342 341 -768 749 -346 753 -514 543 -4656 349 -756 755 -318 789 -316 333 -744 353 -742 359 -718 791 -320 773 -332 357 -724 349 -740 779 -296 785 -334 755 -336 353 -732 341 -736 353 -750 753 -320 369 -724 375 -712 375 -712 375 -736 749 -338 353 -730 343 -734 349 -754 753 -320 801 -294 779 -338 777 -310 341 -764 771 -296 799 -514 523 -4664 347 -756 745 -346 757 -318 349 -756 335 -756 335 -746 767 -324 777 -332 353 -730 343 -732 771 -326 775 -324 777 -332 357 -724 347 -726 375 -736 747 -338 353 -732 341 -734 349 -756 325 -744 793 -320 343 -726 377 -724 337 -746 769 -324 777 -334 747 -338 767 -322 341 -766 741 -322 785 -518 563 -4622 349 -744 783 -320 761 -326 353 -756 347 -724 375 -724 767 -324 777 -334 355 -724 349 -740 745 -330 779 -336 747 -342 353 -732 351 -722 383 -718 781 -314 333 -742 353 -742 361 -716 363 -754 765 -322 345 -724 379 -724 337 -746 769 -324 779 -334 745 -340 777 -312 341 -764 771 -296 769 -546 523 -4654 359 -734 779 -320 757 -328 349 -756 347 -720 377 -742 743 -326 777 -334 353 -722 349 -754 743 -360 745 -336 743 -342 343 -736 329 -768 321 -768 757 -322 369 -722 341 -770 349 -734 311 -768 777 -326 311 -778 331 -746 335 -754 767 -324 775 -332 745 -340 777 -312 355 -760 755 -346 741 -88244 133 -132 99 -232 165 -100 97 -730 99 -66 2213 -264 995 -100 631 -132 6925 -4032 +RAW_Data: 133 -66 65 -2118 165 -1032 197 -66 65 -66 197 -66 99 -132 267 -66 297 -434 133 -134 133 -68 65 -66 131 -266 299 -64 67 -362 161 -296 165 -132 229 -66 361 -498 197 -132 657 -132 1031 -98 1595 -100 965 -132 165 -7880 99 -164 99 -564 67 -928 165 -166 231 -100 65 -166 333 -66 165 -266 165 -398 953 -132 1755 -66 1541 -98 4449 -8044 99 -166 303 -368 591 -132 359 -264 227 -164 99 -1550 299 -200 435 -100 67 -66 593 -132 2943 -134 895 -198 3879 -7540 99 -938 99 -860 99 -1392 99 -1474 197 -166 199 -134 99 -264 233 -134 265 -100 397 -134 1497 -2162 99 -1496 67 -1392 99 -230 233 -66 735 -66 567 -364 197 -66 99 -232 2109 -64 429 -98 99 -98 131 -66 359 -198 195 -162 4861 -4980 131 -950 131 -228 359 -1078 297 -398 231 -262 231 -66 65 -726 295 -366 199 -232 229 -328 459 -68 231 -132 367 -132 3483 -100 597 -134 7113 -1274 99 -236 165 -4414 199 -1432 67 -298 267 -166 329 -234 529 -200 197 -296 129 -294 163 -98 65 -362 131 -100 65 -1426 959 -332 265 -66 297 -166 2683 -66 535 -66 199 -4286 99 -164 525 -1224 363 -166 199 -270 131 -1758 165 -200 557 -298 163 -132 329 -68 233 -66 965 -100 99 -166 365 -234 1391 -9070 131 -1526 97 -1546 131 -98 329 -364 363 -132 329 -164 1183 -10266 65 -6530 65 -732 131 -398 197 -230 195 -98 65 -364 63 -458 65 -198 65 -658 65 -196 163 -98 1805 -98 463 -164 99 -66 623 -98 423 -228 325 -66 233 -100 5543 -5982 97 -1486 331 -198 131 -526 133 -232 131 -132 131 -66 231 -98 131 -134 629 -2026 65 -266 163 -334 265 -66 363 -134 2383 -134 131 -68 233 -98 6503 -98 1329 -9556 65 -166 463 -66 133 -166 233 -266 203 -68 67 -168 99 -66 1161 -66 1667 -132 265 -168 197 -100 199 -200 889 -15904 99 -132 133 -300 1093 -198 201 -132 133 -366 1193 -166 101 -66 935 -134 367 -14672 195 -824 131 -788 395 -430 231 -166 99 -68 99 -17390 65 -1062 65 -498 99 -134 99 -368 65 -66 65 -464 65 -332 265 -132 231 -66 597 -266 133 -330 165 -100 2127 -234 1395 -132 10439 -1004 67 -698 97 -2784 65 -66 265 -264 133 -528 331 -100 131 -234 2027 -164 693 -100 65 -134 197 -130 97 -66 457 -100 4649 -4556 65 -3626 67 -464 65 -100 133 -100 131 -532 67 -566 133 -134 231 -564 65 -166 99 -134 165 -102 65 -1206 199 -666 1261 -66 1031 -132 869 -134 199 -66 597 -100 7409 -7344 97 -296 99 -232 99 -232 65 -662 263 -66 131 -266 397 -100 363 -66 2553 -98 531 -166 +RAW_Data: 7313 -3784 133 -168 133 -1022 99 -558 361 -68 65 -66 97 -166 229 -132 265 -198 97 -792 97 -132 97 -558 163 -264 161 -658 197 -294 231 -328 193 -130 295 -166 231 -166 65 -98 489 -66 885 -100 921 -66 461 -66 323 -304 99 -3408 65 -166 99 -66 195 -296 265 -134 133 -364 65 -296 197 -228 97 -294 231 -368 1387 -66 133 -166 1133 -100 333 -68 431 -66 165 -134 4955 -5736 97 -1088 99 -100 99 -166 199 -264 131 -68 165 -398 199 -630 99 -66 233 -68 403 -98 233 -662 133 -1126 65 -228 131 -98 65 -490 199 -132 229 -232 491 -64 429 -66 667 -100 167 -200 531 -100 263 -264 295 -100 429 -14212 97 -956 65 -930 363 -66 299 -200 331 -66 63 -100 131 -230 1215 -66 1425 -132 927 -132 8041 -732 133 -1064 97 -270 269 -298 129 -228 65 -262 65 -198 459 -100 667 -166 129 -688 67 -2106 97 -232 329 -64 1065 -66 99 -334 167 -132 969 -100 1855 -98 1189 -100 5599 -4798 99 -2932 557 -200 163 -492 295 -758 457 -1788 99 -198 131 -402 197 -134 129 -524 231 -266 1295 -68 1135 -102 701 -10576 65 -5130 63 -100 97 -464 395 -298 99 -100 365 -134 201 -100 99 -100 2515 -98 1611 -132 327 -3806 97 -2502 197 -654 131 -196 97 -196 163 -392 263 -1018 97 -826 197 -130 331 -298 367 -98 267 -68 803 -236 367 -132 851 -166 299 -100 6833 -6158 65 -1062 199 -294 163 -326 227 -724 65 -164 1215 -132 855 -198 467 -66 431 -200 393 -100 229 -98 10229 -338 197 -1756 97 -298 263 -166 97 -164 195 -228 131 -198 199 -234 533 -66 529 -134 397 -100 165 -166 199 -168 99 -100 99 -166 301 -202 529 -66 921 -98 6861 -7702 65 -400 65 -1856 201 -134 65 -100 197 -166 199 -300 165 -134 65 -20006 99 -332 561 -164 199 -630 133 -66 99 -134 131 -200 2393 -134 199 -132 1127 -6284 67 -1900 97 -494 131 -490 163 -264 231 -66 197 -130 327 -164 129 -332 133 -132 3021 -13676 97 -1056 63 -232 65 -264 295 -596 731 -66 295 -66 161 -66 589 -68 3121 -66 199 -68 299 -100 629 -68 8427 -262 405 -744 739 -362 743 -338 353 -760 317 -756 313 -768 741 -360 741 -356 339 -756 307 -786 741 -358 743 -334 743 -374 311 -772 295 -774 319 -764 753 -352 341 -756 307 -758 337 -744 339 -772 743 -338 353 -726 353 -760 313 -766 773 -312 753 -330 771 -354 737 -362 317 -754 745 -364 743 -538 539 -4668 325 -742 781 -320 761 -328 349 -756 347 -724 341 -744 775 -326 777 -334 355 -732 353 -722 779 -332 745 -338 769 -352 311 -768 317 -760 325 -776 +RAW_Data: 761 -320 345 -734 351 -732 345 -770 317 -760 753 -322 369 -724 341 -756 335 -756 765 -324 775 -332 747 -338 755 -340 343 -736 781 -346 755 -516 543 -4650 349 -756 755 -352 755 -314 333 -776 321 -740 361 -752 763 -322 773 -332 353 -722 347 -736 781 -332 745 -338 771 -318 343 -736 351 -758 325 -774 761 -322 341 -736 349 -736 349 -756 347 -724 773 -326 347 -756 345 -724 341 -768 747 -338 777 -344 741 -344 739 -348 353 -722 763 -354 757 -510 543 -4654 353 -740 757 -354 771 -296 353 -756 347 -724 341 -758 769 -326 777 -332 349 -736 341 -736 775 -326 775 -320 773 -330 355 -720 349 -736 349 -754 741 -330 351 -756 347 -722 343 -758 337 -744 771 -324 347 -756 345 -736 349 -734 767 -346 765 -296 769 -354 739 -330 353 -758 743 -364 747 -536 535 -4668 325 -740 777 -320 761 -328 349 -758 347 -722 341 -758 769 -324 777 -334 353 -732 353 -722 777 -332 745 -338 769 -354 315 -752 317 -768 319 -766 755 -322 371 -724 375 -724 337 -746 339 -754 767 -326 345 -756 345 -734 351 -732 755 -350 767 -314 765 -346 757 -324 349 -744 767 -326 775 -530 537 -4636 359 -738 777 -318 761 -328 353 -732 389 -696 389 -724 777 -330 747 -336 357 -730 355 -728 779 -310 779 -348 741 -348 321 -758 353 -722 351 -752 769 -314 333 -742 353 -740 361 -718 363 -752 763 -322 345 -734 351 -726 377 -726 771 -324 777 -332 747 -338 769 -320 343 -766 739 -324 783 -522 561 -4654 315 -778 747 -320 767 -328 351 -758 347 -726 341 -758 765 -324 779 -330 357 -724 347 -724 773 -326 779 -334 747 -340 357 -728 355 -728 355 -758 739 -352 355 -722 341 -736 353 -754 321 -744 793 -318 345 -724 379 -724 337 -746 771 -324 777 -332 755 -336 779 -310 343 -732 779 -346 757 -514 543 -4650 351 -756 753 -352 751 -348 329 -752 321 -746 361 -752 761 -322 775 -298 387 -730 355 -724 781 -296 781 -336 755 -336 353 -730 343 -736 349 -756 751 -322 369 -734 351 -732 345 -732 353 -756 751 -322 369 -726 375 -712 375 -734 747 -338 777 -310 771 -352 733 -344 341 -740 757 -322 803 -528 533 -4634 359 -736 779 -318 763 -328 349 -758 347 -724 339 -756 767 -324 777 -334 351 -734 343 -732 773 -326 775 -322 777 -332 355 -730 357 -722 347 -726 773 -326 349 -734 353 -732 387 -698 389 -724 777 -296 387 -722 347 -726 375 -714 773 -324 777 -334 745 -342 779 -310 343 -766 747 -346 757 -514 543 -4654 341 -764 779 -296 767 -320 373 -724 377 -724 335 -746 771 -324 777 -332 +RAW_Data: 353 -734 343 -732 771 -324 777 -322 779 -332 355 -732 355 -724 347 -724 777 -324 349 -756 347 -724 339 -746 375 -734 747 -338 351 -732 343 -734 349 -756 753 -320 801 -328 747 -336 757 -336 357 -724 781 -332 745 -536 539 -4642 359 -738 779 -318 761 -328 351 -736 353 -756 347 -724 771 -324 777 -334 357 -724 349 -724 775 -326 779 -334 745 -340 351 -732 345 -734 351 -756 753 -322 369 -724 377 -702 383 -734 341 -734 773 -324 345 -752 333 -748 335 -748 771 -324 775 -334 745 -338 779 -312 357 -728 777 -350 741 -548 511 -4676 325 -740 781 -320 763 -328 351 -758 347 -724 341 -746 775 -326 775 -334 355 -732 355 -724 779 -330 747 -336 779 -310 357 -728 355 -724 353 -756 767 -322 351 -722 345 -766 319 -762 325 -778 761 -320 345 -724 377 -724 337 -758 767 -322 775 -332 755 -336 745 -342 341 -732 769 -330 769 -546 523 -4664 345 -742 779 -320 759 -326 349 -736 353 -756 347 -726 771 -324 779 -334 351 -734 341 -736 779 -324 773 -320 777 -296 389 -728 357 -722 349 -724 777 -326 347 -726 379 -734 351 -734 351 -722 793 -322 351 -720 343 -762 323 -744 779 -320 763 -328 781 -334 755 -336 353 -734 767 -316 761 -536 541 -4650 349 -756 753 -352 755 -312 333 -746 353 -742 361 -752 765 -322 773 -298 389 -722 349 -724 775 -326 781 -332 747 -338 357 -728 355 -726 355 -724 799 -322 351 -718 347 -740 353 -738 359 -720 793 -320 343 -724 379 -726 337 -748 767 -324 779 -332 747 -338 757 -340 357 -728 755 -342 757 -538 539 -4660 317 -780 751 -320 767 -328 353 -756 347 -740 347 -726 771 -326 777 -334 355 -730 357 -724 781 -330 747 -336 747 -342 353 -732 349 -754 347 -720 777 -316 341 -746 361 -720 363 -754 331 -748 767 -322 347 -732 355 -758 321 -754 769 -320 765 -354 757 -322 757 -352 331 -736 777 -320 757 -556 533 -4634 359 -738 779 -320 761 -326 351 -756 347 -722 373 -726 767 -322 775 -334 355 -724 349 -724 773 -326 779 -334 747 -338 353 -732 343 -734 349 -760 751 -320 371 -740 347 -726 375 -726 335 -748 769 -322 347 -734 351 -732 345 -768 743 -322 785 -324 777 -334 753 -336 357 -728 777 -312 777 -546 509 -4670 323 -744 779 -320 763 -328 353 -732 353 -732 389 -722 777 -330 747 -338 357 -724 349 -756 741 -328 779 -336 745 -340 341 -736 347 -742 351 -740 791 -288 373 -724 379 -712 377 -724 337 -748 769 -322 347 -758 347 -724 339 -746 771 -324 777 -334 747 -340 767 -322 341 -766 739 -348 755 -558 527 -4632 +RAW_Data: 359 -736 781 -318 761 -326 351 -756 347 -726 339 -748 771 -324 783 -332 351 -736 341 -734 771 -324 775 -322 775 -332 351 -736 341 -734 347 -750 753 -322 367 -742 347 -724 339 -746 375 -734 747 -338 357 -728 353 -728 353 -724 775 -350 767 -320 757 -350 739 -352 353 -724 765 -322 761 -556 523 -4648 347 -754 753 -322 767 -328 351 -734 387 -732 355 -722 779 -330 747 -338 355 -730 355 -728 753 -340 777 -312 791 -310 357 -728 355 -728 353 -758 741 -352 355 -722 351 -720 383 -720 345 -758 745 -346 331 -756 323 -742 361 -752 763 -322 773 -332 749 -336 777 -310 343 -734 781 -346 755 -514 543 -4652 349 -752 769 -348 749 -318 347 -746 337 -754 335 -756 767 -320 775 -332 351 -734 343 -736 775 -326 775 -320 773 -332 351 -734 341 -732 349 -754 749 -322 367 -732 353 -732 343 -734 349 -756 753 -322 371 -734 349 -734 343 -734 775 -322 783 -322 777 -332 747 -338 357 -728 777 -312 791 -508 541 -4660 349 -750 751 -320 765 -330 351 -732 353 -732 389 -722 781 -296 785 -334 357 -730 355 -724 779 -330 747 -338 779 -310 357 -728 355 -726 355 -758 737 -352 341 -732 349 -752 323 -744 363 -746 763 -322 347 -756 345 -726 337 -758 767 -322 775 -332 747 -338 779 -310 343 -764 771 -296 767 -548 523 -4660 345 -756 771 -326 771 -318 345 -724 379 -722 337 -758 767 -324 777 -330 351 -734 341 -738 775 -326 773 -320 775 -300 383 -736 309 -764 347 -754 749 -322 367 -722 341 -760 337 -746 339 -746 773 -324 347 -756 345 -724 339 -746 773 -326 777 -332 747 -340 779 -310 357 -726 765 -356 757 -524 545 -4646 351 -740 791 -320 771 -328 357 -722 349 -724 377 -726 767 -322 777 -334 351 -734 341 -734 769 -316 783 -324 777 -332 353 -732 343 -734 349 -752 751 -322 367 -736 349 -732 353 -752 347 -722 781 -312 333 -744 353 -744 361 -754 763 -320 771 -332 753 -336 747 -340 353 -734 787 -318 759 -548 511 -4672 323 -778 749 -320 763 -328 353 -758 347 -740 347 -726 775 -326 777 -332 357 -722 349 -726 775 -326 777 -334 755 -338 351 -732 343 -732 353 -756 755 -320 369 -722 377 -712 375 -702 389 -724 779 -294 353 -758 347 -724 377 -722 769 -322 777 -332 747 -338 779 -310 343 -764 771 -296 767 -548 523 -4666 345 -758 745 -346 759 -324 347 -752 335 -754 333 -748 769 -322 777 -332 353 -732 343 -730 771 -326 777 -322 777 -334 351 -734 353 -720 351 -754 755 -318 335 -766 319 -764 357 -718 363 -754 765 -318 345 -724 379 -732 351 -732 +RAW_Data: 765 -316 791 -316 775 -322 775 -330 357 -730 747 -340 781 -510 543 -4670 325 -740 779 -322 761 -328 351 -764 319 -756 347 -756 741 -328 777 -336 349 -736 313 -766 771 -326 777 -322 773 -332 353 -720 349 -734 349 -764 735 -350 333 -742 351 -740 325 -780 331 -746 767 -324 343 -756 345 -724 339 -754 767 -324 775 -334 745 -338 771 -350 311 -768 741 -356 751 -91544 97 -1916 131 -560 161 -784 65 -64 495 -98 589 -296 2305 -15072 197 -1258 65 -130 97 -164 97 -98 297 -198 201 -234 261 -98 99 -66 1333 -166 759 -98 95 -196 295 -66 491 -64 163 -66 4795 -4508 131 -922 65 -166 129 -164 163 -66 131 -164 65 -164 99 -196 163 -264 229 -164 65 -164 131 -1932 231 -164 261 -130 65 -264 65 -362 727 -130 2635 -66 327 -66 2397 -4778 65 -364 67 -1134 67 -438 99 -864 163 -366 1187 -830 99 -328 165 -100 131 -134 199 -568 99 -100 99 -466 231 -560 131 -100 129 -132 329 -64 263 -296 1049 -132 333 -98 703 -364 231 -13082 99 -2554 133 -832 133 -164 395 -132 295 -164 65 -66 131 -66 131 -132 65 -134 955 -100 99 -66 961 -66 4911 -4040 65 -2524 165 -896 65 -498 65 -66 99 -232 133 -268 197 -300 265 -266 133 -400 395 -166 297 -98 131 -1020 97 -492 199 -100 563 -132 99 -66 133 -100 3051 -66 591 -196 195 -162 1921 -66 9139 -3400 129 -692 301 -1262 131 -100 265 -166 337 -134 363 -66 133 -332 233 -134 919 -64 2019 -132 295 -132 165 -200 6921 -4912 133 -336 165 -98 99 -66 261 -298 63 -100 65 -264 163 -296 297 -132 465 -100 65 -332 195 -196 165 -334 97 -100 131 -132 165 -300 629 -66 65 -66 165 -134 663 -134 233 -66 861 -66 3939 -134 567 -1310 163 -2894 229 -168 99 -234 431 -494 97 -396 133 -66 1563 -100 497 -198 265 -14052 65 -524 99 -398 923 -66 3991 -6098 99 -1626 99 -1394 99 -100 265 -200 163 -168 97 -432 99 -100 133 -200 4699 -4592 63 -1486 65 -1786 131 -66 197 -334 101 -400 99 -200 99 -168 131 -100 99 -430 231 -234 167 -2338 763 -364 263 -66 131 -200 263 -132 333 -66 3823 -198 1091 -1186 65 -756 65 -494 99 -1546 97 -1432 131 -66 131 -132 295 -132 131 -300 231 -562 99 -66 363 -100 663 -166 457 -66 3199 -6668 99 -962 65 -164 133 -132 131 -266 363 -430 363 -364 99 -66 365 -132 67 -2376 299 -166 165 -130 161 -360 97 -132 953 -198 2041 -66 855 -12212 95 -330 97 -164 97 -1388 65 -366 99 -964 165 -398 163 -232 427 -66 363 -98 133 -166 895 -98 1519 -132 101 -100 +RAW_Data: 133 -166 691 -100 8965 -4740 131 -196 99 -162 231 -226 195 -524 263 -98 391 -66 97 -98 489 -98 2167 -66 129 -198 263 -66 397 -9262 65 -164 97 -2798 197 -830 265 -232 463 -228 425 -230 2667 -132 67 -164 197 -98 2023 -14336 99 -922 65 -1262 531 -100 65 -200 97 -200 197 -102 133 -134 99 -168 395 -100 133 -68 199 -100 855 -66 9821 -5296 65 -4678 229 -166 265 -134 131 -100 65 -166 167 -132 1217 -66 1351 -98 393 -164 487 -12590 99 -2428 131 -434 131 -330 65 -98 131 -298 163 -66 229 -232 559 -66 1805 -66 3565 -100 4163 -4266 67 -3536 65 -824 229 -132 399 -166 67 -100 231 -166 497 -1526 99 -560 231 -134 133 -68 665 -230 291 -230 4463 -66 199 -100 1419 -832 65 -200 65 -464 199 -866 265 -264 231 -100 331 -100 65 -100 131 -100 2415 -132 827 -168 1991 -3852 65 -1928 67 -2620 99 -560 65 -362 65 -198 493 -132 525 -360 231 -100 1017 -132 555 -198 825 -100 10151 -296 131 -698 131 -300 99 -502 99 -966 293 -264 97 -66 395 -430 65 -164 131 -96 97 -98 1617 -132 455 -132 1017 -16428 65 -164 97 -458 131 -66 163 -132 429 -98 463 -66 397 -68 97 -100 367 -100 829 -332 231 -134 1655 -4488 165 -102 165 -830 197 -458 163 -164 65 -132 97 -694 297 -2498 99 -302 165 -764 133 -166 131 -100 763 -98 657 -66 819 -132 229 -130 97 -64 361 -66 401 -66 2079 -5522 99 -2848 297 -298 165 -332 99 -562 99 -1280 99 -594 165 -264 491 -132 99 -164 393 -132 461 -100 465 -132 799 -12900 65 -1660 133 -2006 613 -792 669 -430 671 -436 219 -842 289 -810 259 -826 689 -390 699 -396 311 -782 311 -774 739 -346 737 -348 741 -354 327 -774 307 -772 349 -734 769 -314 345 -742 353 -740 327 -748 363 -750 763 -324 343 -756 347 -722 337 -756 767 -324 775 -334 745 -338 777 -310 343 -766 739 -330 767 -548 523 -4662 345 -760 745 -346 757 -324 347 -752 333 -754 333 -746 769 -326 779 -332 319 -768 309 -766 773 -324 777 -322 773 -330 355 -730 353 -730 353 -722 779 -332 349 -736 311 -768 323 -750 353 -742 759 -352 341 -738 347 -756 345 -720 769 -326 775 -334 745 -340 779 -310 355 -728 787 -346 739 -546 511 -4674 323 -746 781 -320 763 -328 351 -756 347 -726 339 -746 771 -324 779 -334 357 -724 349 -724 777 -326 779 -332 747 -338 351 -732 345 -736 347 -758 751 -320 371 -734 351 -732 343 -736 351 -756 751 -322 369 -736 349 -734 343 -736 771 -324 779 -324 775 -334 755 -336 357 -728 777 -312 775 -548 509 -4676 323 -742 +RAW_Data: 783 -320 759 -328 351 -758 345 -726 375 -722 767 -326 777 -332 357 -722 349 -740 781 -296 781 -336 745 -342 351 -732 353 -754 347 -718 769 -344 333 -756 323 -744 361 -718 365 -752 763 -320 345 -758 345 -734 349 -732 767 -346 761 -316 775 -322 773 -332 351 -734 769 -320 757 -548 513 -4668 347 -752 751 -322 765 -328 353 -756 347 -726 375 -714 773 -326 775 -334 355 -730 353 -724 783 -330 745 -338 777 -312 355 -728 353 -726 353 -756 765 -322 351 -722 345 -770 319 -760 325 -750 791 -320 343 -724 379 -726 335 -746 771 -324 775 -334 745 -340 769 -322 341 -768 741 -322 783 -520 563 -4658 325 -738 777 -318 765 -326 351 -758 347 -724 341 -758 769 -324 775 -332 351 -734 341 -734 773 -324 777 -320 775 -332 355 -724 347 -740 349 -740 777 -296 353 -756 347 -724 375 -724 337 -748 767 -324 349 -732 351 -730 345 -770 743 -324 783 -324 779 -334 745 -340 357 -730 775 -312 791 -508 541 -4664 323 -776 747 -320 767 -328 351 -734 353 -732 353 -766 751 -336 747 -340 357 -730 355 -726 777 -348 755 -310 791 -310 357 -728 355 -726 355 -758 755 -348 319 -758 341 -736 323 -780 319 -744 791 -320 341 -724 377 -724 339 -756 765 -324 775 -332 745 -340 779 -310 343 -764 735 -350 771 -516 527 -4664 345 -760 743 -346 759 -324 349 -752 333 -754 335 -746 767 -324 777 -334 351 -734 341 -734 773 -324 777 -322 775 -332 351 -734 341 -734 347 -754 749 -322 367 -734 351 -732 351 -754 347 -720 785 -314 333 -742 351 -740 361 -752 763 -322 773 -332 751 -334 745 -342 341 -764 739 -332 769 -546 521 -4664 347 -756 745 -346 757 -324 347 -752 333 -754 335 -748 767 -322 777 -332 355 -724 347 -758 741 -328 779 -334 753 -338 351 -732 343 -732 349 -756 753 -322 369 -734 351 -732 343 -736 349 -756 751 -322 371 -734 349 -734 343 -736 775 -324 779 -322 779 -332 745 -338 351 -732 767 -344 765 -512 549 -4650 343 -756 771 -326 769 -320 345 -722 379 -724 339 -744 771 -324 779 -334 349 -736 341 -734 771 -324 779 -322 775 -330 351 -734 343 -732 349 -752 751 -320 369 -734 349 -734 349 -756 333 -732 775 -324 347 -752 333 -746 337 -746 771 -324 777 -332 755 -338 747 -340 353 -732 755 -354 765 -512 543 -4646 351 -744 789 -322 773 -296 387 -724 347 -724 375 -724 771 -324 775 -332 351 -734 341 -736 771 -324 777 -322 775 -332 355 -724 347 -724 377 -736 743 -340 351 -732 343 -734 349 -758 325 -746 795 -288 373 -724 377 -732 351 -732 769 -320 +RAW_Data: 765 -354 759 -320 753 -352 335 -736 779 -320 757 -556 533 -4636 359 -734 779 -318 761 -328 349 -756 347 -742 347 -722 773 -326 777 -332 357 -724 349 -756 743 -326 781 -334 747 -338 353 -732 351 -720 383 -718 783 -312 333 -744 353 -740 363 -720 363 -752 763 -322 343 -758 345 -734 351 -732 765 -346 765 -296 801 -320 773 -298 353 -766 745 -338 779 -510 543 -4670 325 -742 777 -320 761 -328 353 -766 355 -722 349 -738 777 -330 747 -338 357 -728 353 -728 777 -348 741 -350 739 -350 355 -722 351 -720 381 -722 781 -314 341 -740 361 -720 363 -754 331 -748 767 -322 347 -732 351 -730 345 -770 743 -324 785 -324 777 -334 747 -338 353 -732 765 -350 769 -498 565 -4652 333 -746 767 -324 777 -334 355 -724 347 -726 377 -724 767 -324 777 -334 351 -734 341 -734 771 -324 779 -322 775 -332 355 -730 355 -732 353 -724 779 -330 351 -734 341 -734 347 -754 323 -742 795 -320 341 -724 379 -724 337 -744 771 -324 777 -334 745 -340 777 -312 357 -728 787 -310 779 -546 511 -4674 325 -738 779 -320 763 -328 353 -732 353 -766 355 -722 779 -328 747 -336 357 -724 349 -758 741 -328 779 -334 747 -340 353 -732 351 -720 383 -716 767 -318 341 -776 333 -748 335 -756 333 -744 769 -324 347 -732 355 -758 355 -722 755 -350 757 -348 741 -350 737 -352 353 -722 765 -354 759 -524 521 -4650 349 -758 751 -322 799 -296 353 -758 349 -724 375 -724 767 -322 775 -334 353 -734 353 -720 765 -344 767 -320 757 -326 351 -734 351 -758 347 -726 773 -326 347 -758 345 -722 339 -758 335 -746 769 -326 347 -734 341 -766 323 -744 781 -320 763 -328 783 -334 749 -338 355 -728 755 -342 779 -512 543 -4670 325 -742 779 -320 761 -328 351 -756 347 -724 377 -726 767 -322 777 -334 355 -732 355 -730 745 -340 757 -338 779 -312 355 -730 353 -726 351 -756 767 -322 351 -724 383 -720 345 -758 325 -740 777 -320 363 -722 341 -758 335 -746 769 -324 777 -334 745 -340 769 -322 341 -766 739 -324 781 -522 563 -4652 317 -778 747 -320 763 -328 353 -732 387 -724 347 -722 775 -326 781 -332 355 -724 347 -724 775 -326 781 -332 747 -340 353 -732 343 -736 353 -754 749 -320 369 -726 375 -724 335 -748 339 -756 767 -322 345 -736 351 -756 345 -724 775 -324 777 -332 753 -336 747 -340 341 -764 739 -330 769 -546 521 -4660 329 -766 777 -320 757 -326 349 -758 347 -724 373 -724 769 -324 777 -332 353 -732 343 -734 777 -326 773 -320 775 -332 355 -722 349 -732 351 -732 765 -346 339 -754 +RAW_Data: 321 -744 363 -718 363 -748 765 -324 345 -734 349 -732 345 -768 745 -322 783 -326 779 -332 747 -340 353 -730 769 -352 753 -524 557 -4630 351 -740 755 -322 803 -296 387 -722 347 -724 377 -724 769 -322 779 -332 357 -722 349 -724 773 -326 779 -334 747 -338 353 -732 351 -754 345 -722 783 -312 333 -744 353 -744 363 -718 365 -754 761 -322 343 -736 351 -732 349 -756 751 -352 755 -346 753 -326 773 -322 345 -756 743 -326 781 -534 537 -4638 359 -738 779 -320 759 -326 351 -756 347 -726 341 -746 771 -326 777 -334 351 -734 341 -734 777 -324 777 -320 773 -332 355 -732 355 -730 355 -728 777 -310 357 -728 353 -758 341 -732 347 -752 751 -322 367 -724 375 -712 375 -724 767 -324 779 -332 753 -336 747 -342 353 -732 765 -344 763 -516 543 -4650 351 -756 753 -352 753 -314 333 -748 351 -744 361 -752 761 -320 773 -332 355 -724 347 -724 777 -326 781 -332 747 -338 351 -732 343 -734 349 -758 751 -322 371 -724 377 -722 335 -746 339 -746 773 -324 349 -734 353 -734 353 -756 743 -328 781 -336 747 -340 757 -340 355 -730 757 -340 769 -516 549 -4642 353 -758 755 -320 767 -330 353 -758 347 -726 339 -748 771 -324 779 -334 353 -732 343 -732 773 -324 777 -322 775 -332 355 -730 355 -724 349 -758 743 -324 351 -758 347 -724 339 -748 373 -736 747 -336 357 -726 357 -726 355 -726 797 -324 755 -320 783 -316 767 -320 367 -724 773 -326 781 -530 535 -4632 359 -738 781 -318 761 -328 351 -734 387 -724 347 -726 773 -326 779 -332 355 -730 355 -732 745 -342 777 -312 777 -314 357 -726 353 -758 353 -718 789 -322 353 -720 343 -734 349 -760 355 -718 791 -320 343 -724 377 -726 339 -748 771 -324 781 -330 747 -338 777 -312 343 -766 745 -348 757 -514 543 -4678 347 -716 769 -346 757 -324 347 -746 337 -760 333 -758 763 -320 775 -330 357 -730 355 -730 747 -342 777 -310 777 -348 319 -758 341 -734 347 -752 749 -320 371 -722 377 -724 337 -756 333 -748 767 -324 347 -734 349 -732 345 -766 775 -320 755 -328 783 -332 747 -338 357 -730 779 -310 779 -512 543 -4642 359 -738 779 -318 763 -328 351 -734 353 -734 353 -764 747 -336 757 -338 355 -728 355 -726 789 -310 769 -350 733 -342 339 -758 323 -744 363 -746 765 -322 345 -756 345 -742 345 -742 345 -742 743 -328 351 -764 319 -764 353 -728 751 -340 777 -346 739 -346 737 -352 351 -722 767 -350 755 -89978 97 -228 99 -392 97 -364 131 -364 261 -98 491 -198 265 -200 595 -166 2189 -134 1781 -98 131 -66 +RAW_Data: 4895 -3912 129 -1214 65 -132 99 -266 131 -232 201 -200 65 -100 199 -166 265 -100 263 -998 165 -198 97 -298 329 -198 563 -870 99 -166 299 -332 231 -98 163 -98 99 -164 591 -66 1607 -98 327 -11988 65 -134 131 -162 193 -1974 97 -1982 65 -300 265 -230 493 -66 99 -17484 65 -458 165 -362 99 -664 131 -1126 99 -100 99 -168 431 -98 133 -498 197 -66 2277 -100 133 -66 533 -8150 97 -328 97 -100 291 -98 1015 -96 3367 -168 133 -100 1163 -66 667 -66 393 -66 425 -66 5567 -7622 659 -366 199 -330 361 -66 129 -494 131 -264 367 -334 165 -1022 165 -66 133 -166 199 -432 595 -66 131 -98 1363 -6728 65 -2332 65 -7664 99 -1682 99 -196 163 -924 65 -330 65 -196 589 -132 229 -228 229 -198 1909 -100 1477 -66 701 -66 5773 -4316 163 -232 131 -194 65 -392 229 -458 165 -1216 461 -562 263 -462 131 -166 97 -528 97 -394 131 -558 367 -100 233 -68 133 -366 167 -166 267 -66 333 -132 2219 -168 431 -100 1461 -66 1433 -100 5641 -4648 65 -802 97 -794 65 -2290 133 -266 167 -100 165 -334 365 -134 263 -168 65 -266 67 -132 365 -100 599 -168 297 -98 567 -66 331 -198 65 -100 599 -100 1065 -100 5343 -4728 99 -1760 165 -1430 165 -198 99 -202 299 -294 197 -198 129 -64 131 -328 229 -100 357 -464 197 -100 565 -134 133 -102 401 -134 601 -164 129 -66 895 -168 397 -198 397 -100 197 -68 9979 -3582 65 -300 99 -400 63 -694 131 -798 133 -898 167 -200 131 -134 229 -264 197 -360 65 -264 97 -100 763 -100 295 -166 867 -68 65 -132 559 -12458 65 -2028 97 -132 163 -166 129 -100 195 -262 97 -296 263 -130 991 -66 1361 -134 661 -66 361 -66 197 -1088 167 -336 65 -366 65 -794 165 -68 133 -100 131 -266 133 -100 65 -198 135 -366 65 -198 729 -68 995 -100 397 -134 97 -132 363 -232 431 -64 1573 -4672 63 -8966 99 -464 197 -828 165 -602 131 -360 295 -164 63 -66 625 -398 197 -100 2475 -66 367 -132 133 -100 6651 -4462 99 -3092 101 -100 99 -234 99 -198 233 -66 165 -300 165 -132 65 -100 99 -264 65 -598 429 -2318 399 -200 67 -198 133 -166 199 -164 65 -132 165 -98 263 -64 2613 -166 697 -66 6631 -6580 131 -890 65 -66 165 -166 165 -300 133 -368 265 -726 65 -98 161 -164 97 -296 131 -98 131 -988 65 -232 201 -132 165 -100 165 -300 229 -100 261 -268 1459 -132 1575 -364 461 -132 297 -332 8997 -9896 229 -134 265 -164 267 -300 299 -300 763 -134 1933 -100 465 -100 431 -98 199 -134 6761 -2074 65 -3566 65 -132 195 -166 +RAW_Data: 133 -462 67 -166 99 -100 265 -100 131 -364 925 -132 763 -700 231 -856 165 -100 299 -164 631 -166 231 -132 429 -134 463 -66 4979 -4996 165 -430 165 -502 99 -132 97 -1160 457 -100 261 -296 131 -134 201 -98 99 -100 363 -200 361 -134 363 -198 131 -1496 67 -264 329 -360 327 -132 163 -264 985 -68 565 -66 429 -66 263 -130 229 -134 8225 -200 101 -136 101 -368 265 -330 65 -598 195 -304 169 -268 101 -696 99 -1484 67 -1952 99 -1386 165 -66 233 -134 101 -164 65 -364 65 -298 2491 -132 367 -100 1295 -66 6535 -5946 65 -1186 97 -298 623 -426 99 -66 99 -66 559 -134 133 -168 331 -200 133 -132 133 -462 459 -1088 65 -264 131 -164 65 -98 493 -98 97 -198 723 -134 1283 -64 819 -66 459 -66 621 -14282 65 -398 97 -396 97 -366 131 -162 99 -602 165 -66 369 -134 363 -100 561 -134 1261 -100 99 -134 831 -132 8053 -3428 65 -2474 65 -1484 265 -196 131 -98 197 -230 225 -196 97 -232 65 -234 795 -232 397 -234 335 -100 1425 -66 329 -68 527 -134 595 -100 165 -68 165 -200 329 -66 233 -68 889 -64 6761 -1518 97 -5252 133 -1660 65 -528 131 -292 97 -66 165 -66 293 -98 329 -164 97 -130 97 -260 627 -68 1533 -132 867 -132 5585 -6044 97 -1858 99 -132 65 -264 229 -198 97 -924 131 -2698 65 -362 527 -66 263 -100 963 -164 231 -132 295 -166 1093 -66 167 -100 635 -100 165 -68 133 -168 495 -100 299 -100 4855 -6142 65 -1458 97 -134 135 -66 267 -598 131 -168 201 -266 165 -66 133 -232 593 -68 131 -200 97 -198 553 -200 597 -66 1261 -132 2119 -66 199 -168 199 -230 1975 -4282 65 -8382 63 -3286 65 -460 65 -164 97 -100 97 -198 293 -198 63 -326 361 -164 297 -198 1985 -166 297 -100 431 -200 233 -66 65 -68 329 -168 8161 -5090 99 -64 97 -398 67 -2510 263 -1116 65 -230 161 -66 689 -130 129 -132 131 -426 3359 -98 3431 -8782 97 -930 67 -920 97 -786 565 -198 163 -66 461 -198 3347 -164 231 -11298 419 -750 715 -378 721 -360 317 -774 333 -774 305 -782 735 -360 745 -336 315 -768 313 -772 747 -360 741 -356 735 -362 317 -752 347 -752 345 -750 739 -328 347 -756 345 -754 345 -718 339 -772 743 -338 349 -734 349 -754 345 -756 749 -334 737 -354 753 -326 775 -336 347 -736 769 -344 733 -554 507 -4680 351 -722 761 -354 757 -352 317 -760 325 -740 351 -742 757 -354 739 -362 319 -756 347 -756 741 -326 779 -334 745 -340 355 -726 353 -758 319 -758 767 -318 353 -758 311 -754 345 -758 325 -740 777 -322 363 -722 +RAW_Data: 337 -756 337 -756 767 -322 775 -332 747 -338 777 -310 355 -760 739 -350 735 -550 547 -4640 353 -750 751 -320 767 -328 353 -758 347 -724 339 -758 767 -322 777 -332 351 -734 341 -734 773 -324 777 -322 773 -332 355 -732 355 -730 355 -728 753 -338 357 -728 355 -724 355 -758 355 -722 765 -320 351 -754 313 -768 319 -766 753 -352 769 -330 747 -336 779 -310 357 -726 779 -332 747 -540 537 -4670 323 -740 779 -320 761 -328 351 -758 347 -724 375 -724 769 -322 775 -332 351 -734 343 -732 771 -326 777 -322 775 -330 357 -722 347 -726 377 -734 745 -338 353 -732 343 -734 349 -756 325 -744 795 -320 341 -726 377 -724 339 -756 765 -322 777 -332 747 -338 777 -310 343 -764 771 -296 769 -548 521 -4660 329 -768 777 -316 759 -326 351 -758 347 -740 347 -726 771 -324 779 -334 355 -722 349 -724 775 -326 777 -334 747 -340 353 -732 343 -736 351 -756 751 -322 367 -736 351 -732 343 -736 353 -754 751 -322 367 -744 347 -724 339 -746 771 -324 777 -334 745 -340 769 -320 353 -752 769 -314 787 -518 545 -4642 351 -722 789 -310 765 -318 355 -754 337 -758 335 -746 769 -324 775 -334 353 -724 349 -724 773 -326 779 -336 755 -336 353 -732 343 -734 353 -752 753 -320 367 -722 343 -758 337 -746 337 -758 765 -322 345 -756 347 -724 339 -756 767 -324 775 -334 745 -338 777 -312 343 -764 769 -296 769 -548 523 -4650 347 -744 779 -320 763 -330 351 -758 347 -724 341 -758 767 -324 777 -330 351 -734 341 -736 777 -324 775 -320 773 -332 355 -730 357 -722 347 -726 773 -328 349 -756 347 -722 339 -746 375 -734 747 -338 357 -728 353 -728 353 -758 765 -322 757 -352 753 -314 785 -316 345 -752 765 -322 775 -530 535 -4634 359 -736 781 -320 759 -326 351 -756 347 -726 339 -758 769 -324 775 -334 351 -734 341 -734 777 -326 775 -320 773 -298 389 -730 355 -724 347 -722 775 -326 349 -758 345 -724 341 -744 375 -712 773 -324 349 -758 347 -724 339 -756 769 -322 775 -332 745 -340 769 -320 351 -752 771 -314 753 -554 513 -4672 343 -732 771 -318 783 -324 349 -756 347 -724 339 -746 771 -326 775 -336 351 -734 341 -734 777 -326 773 -324 775 -330 355 -724 347 -724 377 -722 767 -324 347 -734 353 -760 353 -722 351 -722 791 -322 351 -720 349 -740 351 -742 757 -352 773 -296 781 -334 755 -338 355 -728 777 -312 777 -544 513 -4674 325 -742 779 -320 759 -328 353 -756 347 -724 341 -758 767 -324 777 -332 357 -730 355 -730 747 -340 781 -310 757 -340 353 -732 343 -738 +RAW_Data: 351 -754 753 -320 369 -724 341 -770 351 -734 341 -732 771 -324 345 -752 333 -748 337 -746 769 -324 777 -332 747 -338 755 -340 353 -732 765 -354 761 -524 515 -4676 319 -764 755 -322 767 -330 351 -758 347 -724 341 -746 773 -324 779 -332 353 -734 343 -732 769 -318 783 -324 779 -332 351 -734 341 -734 347 -754 753 -320 365 -744 347 -724 339 -748 337 -746 773 -324 349 -758 347 -724 339 -744 771 -326 777 -332 745 -340 769 -320 343 -766 741 -322 783 -554 529 -4624 349 -744 779 -320 761 -328 353 -758 347 -724 341 -756 769 -324 775 -334 355 -722 349 -756 741 -326 779 -334 747 -338 353 -732 343 -734 353 -756 751 -322 369 -724 341 -758 337 -746 337 -748 769 -324 349 -732 341 -768 325 -742 781 -322 759 -328 783 -334 747 -338 357 -728 777 -346 755 -510 543 -4664 323 -778 747 -320 763 -330 351 -758 347 -724 339 -746 773 -324 779 -336 351 -734 341 -734 771 -324 777 -324 775 -332 355 -724 347 -724 377 -724 769 -324 347 -756 345 -734 351 -732 343 -732 773 -316 353 -754 337 -746 337 -756 767 -324 777 -334 745 -340 755 -338 353 -732 767 -342 767 -512 549 -4654 345 -754 769 -296 803 -320 341 -724 379 -724 337 -746 769 -326 777 -332 353 -734 341 -732 775 -324 779 -322 773 -332 351 -734 341 -736 323 -780 753 -320 367 -722 375 -722 337 -746 339 -768 747 -336 353 -732 343 -732 351 -754 753 -320 801 -330 745 -338 777 -310 341 -764 767 -318 775 -516 525 -4682 315 -780 749 -320 767 -328 351 -732 353 -758 347 -740 777 -330 749 -336 357 -730 355 -726 779 -310 791 -310 777 -312 357 -726 353 -756 353 -720 765 -354 333 -736 351 -738 359 -716 363 -754 765 -322 343 -724 379 -732 351 -732 767 -314 799 -296 767 -322 801 -296 383 -734 767 -316 763 -522 539 -4680 341 -734 775 -326 773 -322 345 -722 377 -726 337 -756 769 -322 775 -332 351 -736 353 -718 767 -344 765 -320 761 -326 349 -758 347 -740 347 -726 771 -326 347 -758 345 -724 339 -746 375 -734 747 -338 353 -732 341 -734 349 -758 751 -322 767 -330 779 -336 779 -308 357 -726 779 -334 747 -538 539 -4662 317 -780 749 -320 765 -328 353 -732 389 -722 347 -724 775 -326 777 -334 355 -730 355 -724 779 -330 747 -338 777 -310 343 -732 349 -746 353 -740 759 -320 373 -736 349 -734 343 -734 349 -758 755 -320 371 -734 349 -732 343 -760 773 -296 769 -322 801 -328 747 -336 357 -724 781 -332 747 -536 539 -4662 323 -744 781 -318 763 -328 353 -758 347 -726 339 -756 767 -324 +RAW_Data: 777 -334 349 -734 343 -734 777 -326 773 -320 773 -330 355 -724 347 -724 379 -722 769 -324 347 -756 345 -734 351 -732 343 -734 777 -324 347 -752 333 -746 337 -746 769 -326 777 -334 755 -336 779 -308 343 -732 783 -346 755 -514 543 -4646 349 -754 751 -350 753 -346 331 -758 323 -744 361 -754 763 -320 775 -296 389 -732 355 -722 777 -330 747 -336 755 -338 353 -732 351 -720 383 -716 771 -346 329 -756 319 -742 363 -718 365 -752 765 -322 343 -756 347 -732 351 -732 769 -310 799 -316 755 -326 777 -334 353 -732 767 -316 761 -552 545 -4640 345 -732 771 -324 781 -324 347 -758 345 -722 341 -756 767 -326 775 -334 351 -734 341 -736 775 -326 773 -322 771 -332 355 -732 355 -724 349 -724 775 -326 349 -756 345 -724 339 -746 375 -736 745 -338 357 -728 353 -726 353 -758 765 -322 755 -352 753 -348 749 -318 347 -752 765 -322 775 -530 537 -4632 359 -738 779 -320 759 -328 353 -756 347 -726 339 -758 767 -322 777 -334 351 -734 341 -734 771 -318 779 -324 777 -332 357 -730 355 -730 357 -724 777 -332 349 -734 341 -734 323 -782 321 -742 793 -290 373 -722 379 -734 355 -730 747 -340 777 -310 777 -350 739 -348 319 -760 769 -322 767 -520 553 -4646 323 -778 749 -320 767 -328 351 -758 347 -726 339 -756 769 -324 775 -332 353 -734 341 -734 773 -326 775 -322 775 -300 353 -764 309 -766 347 -752 749 -322 367 -722 341 -746 375 -736 351 -732 767 -318 341 -742 353 -738 327 -778 761 -322 773 -332 747 -338 777 -310 355 -728 779 -348 739 -550 511 -4674 323 -746 779 -320 763 -330 351 -758 347 -740 347 -742 743 -330 781 -334 357 -730 357 -728 751 -340 755 -340 769 -318 343 -766 317 -760 325 -778 759 -322 343 -734 351 -732 345 -766 317 -760 755 -322 369 -736 351 -732 353 -756 757 -322 755 -352 753 -346 755 -326 343 -750 767 -322 771 -530 537 -4664 325 -740 777 -320 761 -328 351 -758 347 -726 341 -756 767 -324 777 -332 351 -734 341 -736 775 -326 775 -320 773 -330 351 -734 341 -736 323 -780 749 -320 367 -724 375 -722 337 -744 339 -746 771 -326 347 -758 345 -722 373 -726 767 -324 775 -332 753 -338 745 -340 353 -732 765 -352 753 -526 553 -4646 355 -730 753 -340 779 -312 357 -726 353 -726 353 -758 765 -322 765 -356 317 -758 345 -722 781 -346 755 -326 771 -320 345 -724 377 -726 337 -746 769 -326 347 -732 355 -758 355 -722 351 -754 757 -310 343 -760 325 -746 363 -752 763 -322 771 -332 753 -336 747 -338 353 -732 767 -344 763 -514 +RAW_Data: 541 -4674 313 -768 779 -320 755 -324 349 -734 353 -758 347 -722 773 -326 777 -334 351 -734 341 -734 773 -324 777 -320 775 -332 355 -724 347 -724 379 -732 747 -340 353 -732 343 -732 349 -760 323 -746 795 -290 373 -724 377 -724 337 -746 769 -324 777 -332 747 -338 769 -320 341 -766 743 -322 783 -522 561 -4630 359 -736 777 -320 757 -328 351 -758 347 -724 341 -758 767 -324 779 -300 383 -734 341 -734 779 -296 803 -320 773 -298 387 -724 347 -740 347 -726 775 -322 349 -756 345 -742 347 -724 341 -756 769 -324 345 -724 377 -736 351 -732 765 -312 797 -320 753 -326 777 -334 353 -732 765 -316 761 -536 541 -4648 345 -766 775 -290 785 -324 349 -758 347 -726 339 -746 771 -324 779 -332 353 -730 343 -734 777 -324 777 -322 775 -332 355 -730 357 -724 347 -724 773 -328 349 -758 347 -734 351 -732 343 -732 777 -324 347 -754 335 -758 333 -748 765 -322 775 -332 745 -340 757 -340 355 -728 779 -312 777 -548 511 -4672 323 -742 781 -320 761 -328 387 -698 387 -698 389 -722 781 -296 781 -336 357 -726 349 -732 779 -312 767 -356 759 -322 351 -722 359 -736 351 -740 755 -320 373 -726 375 -726 337 -754 333 -748 767 -324 347 -724 379 -724 339 -758 765 -322 779 -332 745 -340 777 -310 357 -728 775 -348 757 -512 545 -4640 359 -738 779 -320 759 -328 353 -756 347 -726 375 -726 765 -324 777 -300 389 -722 347 -726 775 -324 779 -332 747 -338 353 -730 345 -734 351 -754 753 -322 369 -734 351 -732 343 -728 349 -744 781 -320 365 -726 339 -758 335 -748 769 -324 777 -332 745 -340 779 -312 357 -730 777 -310 795 -510 541 -4666 315 -746 783 -320 763 -326 355 -758 347 -726 339 -748 769 -326 777 -334 353 -730 343 -732 773 -324 779 -322 777 -298 389 -724 347 -726 375 -724 767 -324 347 -724 379 -724 339 -746 339 -760 765 -324 345 -724 377 -726 339 -758 765 -322 777 -332 747 -338 779 -310 341 -734 771 -346 757 -546 543 -4620 383 -716 769 -346 757 -324 345 -752 333 -750 335 -758 765 -320 775 -332 353 -724 349 -756 741 -328 777 -334 753 -338 351 -732 343 -732 349 -756 751 -322 369 -724 341 -746 375 -736 349 -734 769 -322 351 -720 345 -734 349 -762 755 -322 767 -328 781 -334 747 -342 357 -728 777 -346 741 -548 511 -4670 323 -744 779 -320 763 -330 351 -758 349 -724 339 -758 767 -324 775 -330 353 -732 341 -736 769 -324 779 -320 777 -330 357 -724 347 -740 347 -724 775 -324 347 -756 345 -724 341 -744 341 -770 743 -338 353 -732 343 -734 +RAW_Data: 349 -758 753 -320 767 -360 747 -336 777 -308 357 -726 789 -312 777 -79086 67 -500 65 -496 63 -458 131 -856 133 -438 131 -202 99 -398 263 -328 195 -198 363 -100 395 -100 961 -66 393 -132 365 -102 199 -66 5167 -4206 99 -640 99 -726 65 -1814 293 -98 163 -98 97 -164 327 -64 259 -132 231 -428 97 -232 197 -134 65 -398 197 -1124 99 -462 465 -68 397 -132 303 -66 665 -66 3199 -66 1825 -5202 65 -8960 231 -460 131 -396 97 -166 99 -132 133 -330 99 -328 425 -166 133 -100 199 -68 131 -168 133 -66 3381 -66 13735 -270 199 -134 99 -166 133 -302 1029 -100 165 -200 201 -198 301 -132 1467 -66 501 -68 367 -168 1925 -66 495 -98 299 -66 455 -262 5365 -6980 65 -66 129 -462 65 -130 97 -98 327 -394 65 -98 359 -232 97 -330 295 -430 365 -2796 263 -134 199 -198 291 -164 3701 -66 9467 -9052 99 -802 329 -200 197 -64 129 -294 131 -300 891 -266 133 -166 329 -102 99 -132 2173 -64 161 -98 6545 -8052 129 -562 429 -100 563 -168 335 -134 133 -98 133 -232 363 -66 299 -66 1129 -164 397 -464 561 -68 795 -100 1687 -13310 133 -1490 101 -170 231 -66 199 -66 197 -134 65 -334 365 -134 297 -66 959 -100 293 -198 795 -362 231 -100 365 -166 365 -100 4957 -7546 231 -268 397 -232 131 -398 231 -730 65 -434 199 -1492 65 -728 65 -528 97 -164 229 -66 131 -64 197 -132 391 -164 2959 -66 65 -12414 65 -1126 65 -534 67 -400 231 -366 99 -134 65 -166 67 -166 167 -166 167 -298 335 -232 299 -66 1697 -66 233 -134 67 -166 529 -100 67 -66 7763 -728 99 -932 99 -1296 197 -100 99 -630 65 -794 165 -634 165 -130 97 -198 199 -132 133 -198 99 -262 2175 -66 97 -132 1645 -164 563 -12852 131 -1032 67 -1730 99 -166 199 -236 99 -300 263 -196 293 -198 357 -66 1263 -100 1363 -98 725 -134 231 -66 627 -66 5743 -4102 263 -230 131 -262 131 -66 67 -198 99 -498 165 -364 131 -1000 263 -100 101 -464 197 -198 399 -98 1029 -166 133 -198 99 -300 131 -100 199 -432 99 -230 1829 -66 429 -134 131 -66 131 -230 133 -394 395 -196 397 -3828 63 -960 65 -458 197 -198 129 -1480 97 -1948 97 -428 195 -66 229 -426 197 -426 1185 -64 1181 -132 633 -166 363 -170 167 -66 329 -232 4851 -4466 67 -930 65 -434 165 -268 65 -166 133 -234 133 -330 261 -132 199 -68 99 -134 99 -68 65 -234 99 -2214 63 -132 361 -164 297 -98 227 -456 163 -130 127 -228 397 -13124 131 -3210 365 -722 163 -360 97 -362 427 -66 395 -66 163 -132 6621 -9772 +RAW_Data: 231 -332 265 -132 665 -66 531 -232 693 -66 363 -66 1253 -132 621 -428 599 -98 7479 -6028 99 -296 97 -654 99 -724 97 -328 97 -98 435 -166 295 -264 129 -132 589 -66 627 -66 397 -98 199 -66 199 -1528 297 -6504 63 -900 65 -1130 133 -166 663 -398 395 -298 131 -100 599 -98 67 -66 695 -166 403 -164 1807 -200 4679 -3774 131 -130 99 -98 457 -2774 231 -396 525 -262 197 -294 97 -68 163 -134 331 -298 365 -166 99 -634 99 -362 165 -1196 163 -166 231 -98 267 -132 197 -300 2521 -100 895 -100 401 -134 503 -12428 163 -362 231 -396 165 -166 199 -66 99 -232 167 -134 197 -100 99 -330 365 -168 1927 -334 231 -198 495 -66 8407 -5784 229 -1188 163 -492 65 -336 99 -602 99 -100 365 -200 431 -334 331 -100 1791 -134 397 -132 65 -166 359 -66 297 -100 465 -68 1097 -4396 67 -564 431 -298 99 -132 429 -198 1053 -66 327 -98 723 -12552 65 -1232 199 -864 131 -1890 99 -132 133 -134 99 -66 233 -100 165 -628 65 -132 2857 -66 295 -166 631 -100 197 -100 4333 -1082 131 -1476 359 -366 97 -166 99 -100 231 -166 861 -100 529 -98 863 -200 99 -102 99 -132 493 -132 325 -100 8365 -2990 65 -302 133 -964 97 -1100 65 -266 867 -166 99 -100 131 -134 565 -166 695 -100 901 -100 497 -66 401 -12558 63 -830 99 -100 131 -694 99 -498 129 -200 99 -402 99 -266 231 -66 199 -134 65 -200 99 -132 133 -68 167 -166 67 -168 599 -202 1633 -66 723 -100 1547 -64 7133 -5950 97 -522 97 -428 97 -554 65 -2048 133 -132 363 -66 297 -234 2847 -164 791 -132 301 -3286 131 -6472 99 -364 99 -1096 199 -590 65 -362 131 -66 133 -134 299 -98 369 -66 2063 -166 99 -132 599 -100 7507 -8234 199 -232 97 -168 201 -530 229 -132 163 -66 427 -68 165 -100 1981 -66 463 -100 65 -166 1017 -132 6219 -6002 131 -64 165 -428 199 -432 131 -530 133 -200 67 -11500 65 -6344 65 -1958 165 -100 97 -664 65 -334 99 -730 331 -236 367 -168 329 -200 395 -66 131 -98 527 -68 99 -132 1493 -66 197 -14856 199 -1364 197 -132 165 -366 163 -234 65 -98 131 -428 65 -98 65 -232 2273 -164 553 -392 6131 -6662 233 -832 231 -432 63 -1328 263 -300 131 -866 197 -926 65 -166 65 -232 227 -98 263 -198 131 -164 65 -166 791 -100 525 -98 361 -198 365 -132 859 -66 329 -228 529 -7030 131 -620 97 -164 163 -132 131 -66 195 -100 163 -394 229 -262 131 -66 131 -198 527 -100 133 -400 133 -166 99 -1232 65 -134 197 -66 131 -232 431 -596 131 -166 2673 -134 295 -166 +RAW_Data: 5357 -11284 99 -1290 65 -134 1501 -200 263 -100 325 -132 399 -166 1997 -15232 131 -1320 99 -168 65 -466 65 -134 65 -100 165 -98 97 -196 425 -134 331 -132 1491 -168 395 -134 299 -100 1519 -12916 99 -998 197 -794 131 -1654 165 -100 97 -362 163 -66 163 -230 231 -434 497 -100 1499 -100 1495 -166 65 -298 229 -5848 99 -134 99 -8920 133 -1126 131 -234 231 -134 265 -66 131 -134 365 -166 829 -100 1793 -134 395 -12288 97 -2094 65 -430 65 -268 65 -400 65 -232 335 -166 199 -132 231 -100 2191 -100 929 -15146 299 -230 129 -788 591 -100 231 -100 165 -66 431 -100 65 -100 65 -166 133 -264 199 -134 595 -100 397 -132 927 -68 435 -166 267 -232 65 -166 99 -68 7469 -3932 99 -360 231 -2184 131 -166 293 -200 229 -262 99 -1020 65 -656 261 -66 229 -132 163 -66 97 -760 1663 -66 233 -166 397 -100 329 -132 955 -166 491 -14812 167 -1052 163 -528 131 -132 227 -66 659 -66 367 -100 2749 -68 1595 -66 10545 -1162 67 -3130 131 -1224 131 -66 99 -230 199 -166 529 -662 299 -18024 131 -298 201 -366 131 -100 65 -166 333 -166 65 -132 297 -164 395 -392 131 -166 9295 -9924 133 -1430 133 -334 397 -134 131 -68 65 -100 331 -100 197 -200 691 -166 329 -13826 131 -1978 131 -66 531 -66 265 -268 165 -136 165 -100 263 -166 467 -66 365 -102 467 -166 631 -136 67 -696 133 -396 97 -758 165 -3180 131 -362 163 -132 63 -164 461 -164 229 -164 197 -296 2399 -194 293 -13158 131 -1494 231 -524 327 -198 97 -394 297 -68 2025 -66 227 -296 365 -13564 65 -1380 131 -954 67 -658 429 -166 195 -66 65 -98 163 -130 823 -100 99 -64 757 -66 131 -98 99 -64 165 -132 11025 -21882 65 -1816 65 -822 229 -326 163 -100 723 -264 427 -66 329 -98 985 -68 1827 -66 133 -168 401 -98 301 -132 399 -100 1725 -4022 65 -1726 67 -732 99 -1070 99 -198 65 -494 197 -298 165 -100 131 -66 823 -134 299 -166 363 -102 363 -66 823 -132 7641 -4542 65 -834 65 -1220 67 -1164 133 -562 65 -698 67 -464 197 -736 263 -134 231 -332 99 -302 131 -264 361 -164 1245 -66 65 -66 395 -130 161 -98 65 -12312 65 -1324 65 -572 67 -166 101 -202 65 -702 395 -198 401 -100 333 -68 331 -66 133 -132 1281 -68 367 -132 433 -132 99 -66 8729 -2158 65 -3984 65 -1562 265 -166 161 -194 165 -132 497 -300 1055 -166 425 -100 599 -134 991 -66 6793 -5222 99 -1602 65 -328 129 -132 291 -330 99 -228 97 -196 97 -98 297 -624 297 -1186 165 -502 165 -498 97 -1086 525 -230 261 -292 199 -298 +RAW_Data: 2443 -100 8837 -7844 99 -300 101 -3474 65 -426 131 -786 131 -66 261 -66 131 -132 623 -198 431 -98 3805 -64 163 -132 4689 -2290 65 -5964 129 -996 197 -2760 165 -230 395 -300 263 -234 301 -166 2423 -68 367 -200 197 -134 429 -66 131 -100 231 -100 1455 -100 7041 -260 63 -2606 163 -262 97 -164 261 -166 161 -130 493 -200 799 -132 525 -132 129 -1222 97 -918 919 -68 65 -264 165 -168 3119 -64 97 -98 265 -66 293 -66 9961 -3892 97 -168 461 -362 165 -438 267 -100 531 -134 1659 -168 167 -66 101 -762 99 -330 231 -8514 65 -4398 99 -1312 99 -130 163 -294 163 -822 197 -270 429 -100 461 -66 329 -130 295 -330 829 -66 1461 -66 629 -66 233 -168 593 -66 13101 -102 133 -232 1405 -198 327 -296 331 -100 197 -200 365 -66 369 -98 1419 -230 527 -66 529 -302 5013 -3942 99 -1150 165 -298 65 -296 197 -822 133 -528 97 -230 163 -762 99 -666 65 -432 265 -332 201 -66 431 -198 267 -132 2459 -132 403 -100 1359 -68 7933 -3998 67 -4454 131 -198 97 -132 131 -198 295 -162 329 -298 1189 -132 327 -366 435 -66 503 -66 983 -132 4845 -5250 131 -132 133 -98 165 -134 101 -300 233 -332 231 -66 99 -132 99 -132 335 -198 165 -366 131 -1060 197 -402 99 -132 197 -168 67 -266 629 -330 65 -134 131 -336 721 -100 1501 -132 731 -66 1127 -132 5451 -4352 131 -854 163 -296 165 -432 99 -396 493 -132 131 -294 163 -1052 131 -14776 65 -5234 65 -164 129 -852 65 -98 131 -66 165 -164 97 -330 429 -66 165 -432 329 -98 491 -100 857 -198 889 -164 329 -66 295 -806 65 -722 99 -3564 199 -864 397 -400 231 -98 367 -100 797 -164 395 -98 393 -166 6901 -1552 65 -530 165 -66 99 -1186 65 -394 131 -198 99 -366 165 -198 99 -404 99 -2092 261 -264 331 -132 231 -360 297 -234 331 -64 2139 -100 399 -13226 99 -2394 65 -502 63 -394 163 -260 65 -66 131 -198 229 -132 99 -132 297 -296 263 -196 987 -132 265 -198 163 -12344 99 -696 65 -1292 65 -198 99 -894 163 -68 431 -102 65 -102 231 -166 199 -64 263 -164 359 -66 2025 -130 327 -98 11033 -788 99 -2174 65 -722 97 -100 163 -66 263 -362 789 -100 2493 -100 299 -200 7351 -4204 65 -1286 67 -1766 67 -566 99 -266 65 -200 67 -930 97 -134 429 -100 99 -132 65 -198 229 -262 163 -460 789 -98 361 -130 259 -66 657 -134 491 -166 197 -164 163 -166 5757 -6452 65 -864 65 -368 395 -230 165 -468 99 -796 133 -166 99 -134 101 -828 131 -600 163 -560 197 -328 263 -200 295 -66 97 -294 97 -198 diff --git a/applications/debug/unit_tests/resources/unit_tests/subghz/gangqi.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/gangqi.sub new file mode 100644 index 000000000..33f1ff79f --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/subghz/gangqi.sub @@ -0,0 +1,7 @@ +Filetype: Flipper SubGhz Key File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: GangQi +Bit: 34 +Key: 00 00 00 02 9D DB 77 38 diff --git a/applications/debug/unit_tests/resources/unit_tests/subghz/gangqi_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/gangqi_raw.sub new file mode 100644 index 000000000..fd76548dd --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/subghz/gangqi_raw.sub @@ -0,0 +1,12 @@ +Filetype: Flipper SubGhz RAW File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: RAW +RAW_Data: 1223 -572304 1675 -132 1183 -536 465 -1214 1203 -502 489 -1210 509 -1202 1171 -518 1175 -522 1189 -508 489 -1176 1229 -504 1177 -520 1205 -482 479 -1232 1203 -478 1233 -486 481 -1216 1197 -480 1221 -486 505 -1182 1241 -486 1175 -482 1245 -484 505 -1218 1185 -498 1211 -490 1185 -482 545 -1174 511 -1206 1177 -516 1189 -516 1165 -508 521 -1184 503 -1232 477 -2210 1171 -510 473 -1232 1183 -514 517 -1174 513 -1194 1209 -478 1219 -486 1191 -492 513 -1204 1199 -532 1159 -500 1237 -486 481 -1206 1209 -480 1203 -480 535 -1198 1189 -496 1187 -520 483 -1230 1175 -478 1219 -506 1199 -510 505 -1180 1231 -496 1179 -486 1211 -482 517 -1214 501 -1182 1199 -508 1179 -502 1205 -516 477 -1240 473 -1206 477 -2200 1231 -478 485 -1222 1189 -520 463 -1202 515 -1208 1199 -494 1207 -486 1209 -516 483 -1182 1217 -488 1197 -534 1159 -520 481 -1234 1179 -478 1219 -486 505 -1216 1183 -506 1201 -510 491 -1174 1229 -506 1177 -542 1185 -496 483 -1208 1195 -502 1213 -490 1169 -528 493 -1208 475 -1242 1171 -482 1235 -484 1181 -542 499 -1180 473 -1242 479 -2204 1201 -490 475 -1248 1177 -488 515 -1206 489 -1236 1153 -520 1215 -480 1175 -514 531 -1192 1167 -538 1195 -484 1215 -482 515 -1176 1207 -512 1165 -548 477 -1184 1233 -478 1187 -520 519 -1174 1185 -536 1191 -474 1231 -478 493 -1212 1203 -492 1177 -544 1179 -480 487 -1250 497 -1176 1197 -496 1211 -486 1211 -480 517 -1214 501 -1178 507 -2208 1209 -486 507 -1182 1197 -526 483 -1204 507 -1198 1187 -508 1183 -506 1217 -490 475 -1244 1175 -486 1237 -484 1181 -492 547 -1176 1167 -528 1185 -520 485 -1232 1175 -478 1223 -486 519 -1216 1169 -520 1203 -482 1197 -480 493 -1242 1167 -504 1217 -490 1201 -518 489 -1176 519 -1210 1175 -514 1191 -486 1213 -518 489 -1176 505 -1214 515 -2198 1167 -506 541 -1174 1175 -520 505 -1184 505 -1216 1183 -504 1211 -490 1199 -530 495 -1178 1199 -494 1205 -484 1207 -514 479 -1200 1203 -516 1175 -512 507 -1184 1227 -498 1181 -486 515 -1204 1185 -532 1159 -504 1231 -478 493 -1210 1215 -486 1171 -544 1173 -512 475 -1216 513 -1194 1185 -510 1181 -520 1185 -514 483 -1216 513 -1158 531 -2174 1243 -486 499 -1176 1217 -490 479 -1236 505 -1184 1203 -522 1181 -484 1213 -502 485 -1206 1197 -500 1213 -490 1197 -498 515 -1180 1197 -532 1161 -502 511 -1202 1201 -482 1225 -476 485 -1248 1191 -486 1211 -482 1207 -484 537 -1196 1179 -516 1179 -506 1199 -508 489 -1172 545 -1178 1201 -520 1179 -488 1215 -494 515 -1170 513 -1204 481 -2238 1157 -520 519 -1176 1183 -540 499 -1178 473 -1236 1179 -520 1209 -480 1175 -552 475 -1188 1225 -502 1177 -488 1207 -510 473 -1232 1207 -480 1203 -520 479 -1184 +RAW_Data: 1227 -480 1189 -554 487 -1174 1217 -502 1181 -486 1211 -514 481 -1216 1207 -484 1221 -478 1185 -520 487 -1196 483 -1218 1215 -490 1229 -478 1183 -520 483 -1228 483 -1188 531 -2150 1239 -480 481 -1246 1181 -482 489 -1218 495 -1206 1197 -506 1183 -520 1205 -480 479 -1232 1181 -514 1209 -486 1199 -526 491 -1184 1207 -520 1183 -484 485 -1208 1209 -506 1181 -518 479 -1242 1169 -482 1235 -474 1201 -480 541 -1174 1205 -512 1207 -474 1215 -516 479 -1174 547 -1176 1185 -536 1193 -462 1249 -486 481 -1206 513 -1176 485 -2240 1173 -518 517 -1158 1223 -506 487 -1174 545 -1172 1207 -514 1175 -484 1223 -510 489 -1178 1219 -516 1173 -486 1235 -484 489 -1218 1185 -520 1217 -480 481 -1214 1205 -484 1183 -540 499 -1182 1201 -494 1207 -476 1241 -478 475 -1244 1171 -512 1203 -520 1173 -490 521 -1184 479 -1246 1179 -482 1243 -486 1195 -484 515 -1210 485 -1214 499 -2204 1175 -482 519 -1224 1171 -490 503 -1210 481 -1210 1213 -482 1205 -518 1175 -486 501 -1246 1179 -486 1211 -480 1213 -512 503 -1182 1231 -498 1179 -486 513 -1202 1183 -534 1159 -498 511 -1204 1199 -482 1227 -480 1199 -514 505 -1182 1229 -478 1185 -520 1209 -482 509 -1202 513 -1174 1215 -516 1153 -520 1211 -480 511 -1204 485 -1208 489 -2210 1175 -548 475 -1186 1217 -492 515 -1174 513 -1210 1177 -510 1201 -490 1233 -496 483 -1174 1215 -516 1173 -514 1209 -484 491 -1232 1159 -520 1209 -480 513 -1210 1183 -482 1245 -486 501 -1178 1231 -496 1177 -508 1205 -482 481 -1244 1171 -510 1211 -486 1199 -494 509 -1210 479 -1212 1181 -514 1207 -486 1201 -492 507 -1216 479 -1210 519 -2198 1165 -494 513 -1210 1193 -488 511 -1208 473 -1246 1175 -474 1239 -478 1203 -486 539 -1194 1155 -528 1187 -520 1211 -480 481 -1210 1203 -512 1169 -548 477 -1186 1215 -492 1205 -486 481 -1220 1177 -510 1219 -488 1199 -522 487 -1206 1197 -504 1181 -486 1241 -478 475 -1244 485 -1202 1219 -478 1187 -486 1241 -478 475 -1244 485 -1202 461 -2214 1245 -482 499 -1178 1231 -478 491 -1240 471 -1184 1243 -486 1175 -480 1241 -482 503 -1182 1233 -478 1215 -506 1165 -510 489 -1208 1195 -506 1211 -506 473 -1214 1185 -522 1207 -478 509 -1174 1243 -480 1199 -514 1169 -506 507 -1214 1197 -486 1209 -482 1201 -520 481 -1192 519 -1208 1175 -514 1199 -510 1175 -514 507 -1186 503 -1210 479 -2234 1171 -490 503 -1208 1177 -520 511 -1198 489 -1232 1169 -490 1215 -492 1205 -488 513 -1202 1185 -534 1161 -518 1209 -482 513 -1178 1205 -516 1175 -512 507 -1184 1233 -498 1175 -474 551 -1182 1177 -546 1165 -510 1189 -510 487 -1208 1215 -486 1175 -546 1173 -480 509 -1216 479 -1190 1223 -506 1179 -520 1183 -516 479 -1234 481 -1198 491 -2210 +RAW_Data: 1211 -482 501 -1184 1227 -500 483 -1208 505 -1200 1205 -474 1213 -484 1205 -520 481 -1232 1177 -478 1219 -486 1197 -504 519 -1184 1193 -522 1181 -520 481 -1230 1177 -478 1217 -508 473 -1248 1155 -504 1231 -500 1179 -486 481 -1224 1179 -510 1217 -490 1229 -480 493 -1210 477 -1238 1169 -486 1235 -484 1181 -524 491 -1206 477 -1240 477 -2206 1199 -492 511 -1180 1205 -518 481 -1190 499 -1212 1207 -486 1213 -480 1213 -512 503 -1182 1213 -490 1205 -486 1209 -512 475 -1216 1209 -484 1211 -504 487 -1174 1237 -488 1173 -518 511 -1200 1189 -530 1155 -500 1237 -486 477 -1200 1205 -516 1169 -552 1179 -482 493 -1232 477 -1182 1233 -478 1189 -554 1169 -480 509 -1210 485 -1206 527 -2182 1203 -484 517 -1194 1179 -510 489 -1210 519 -1184 1209 -484 1203 -494 1197 -508 489 -1210 1193 -490 1243 -486 1177 -482 519 -1212 1173 -522 1181 -510 491 -1206 1197 -506 1215 -492 473 -1246 1177 -506 1217 -486 1171 -514 507 -1202 1187 -534 1155 -498 1235 -488 483 -1206 513 -1208 1173 -512 1203 -490 1201 -494 515 -1174 519 -1214 481 -2198 1187 -504 513 -1208 1175 -512 505 -1182 503 -1200 1201 -490 1229 -498 1173 -520 487 -1208 1221 -478 1185 -520 1181 -512 475 -1246 1175 -478 1235 -486 505 -1182 1229 -498 1179 -520 479 -1202 1211 -504 1179 -506 1203 -490 481 -1240 1179 -516 1209 -486 1177 -488 553 -1182 479 -1242 1183 -480 1211 -520 1173 -490 473 -1244 479 -1210 513 -2166 1239 -486 479 -1244 1167 -490 481 -1242 473 -1210 1209 -484 1205 -520 1195 -486 477 -1238 1167 -514 1207 -520 1177 -518 481 -1182 1235 -484 1181 -492 547 -1178 1197 -530 1153 -520 487 -1232 1177 -478 1217 -508 1165 -542 501 -1178 1227 -498 1177 -486 1211 -510 475 -1216 507 -1200 1183 -530 1191 -486 1211 -482 513 -1176 511 -1202 489 -2210 1173 -550 479 -1188 1225 -502 485 -1172 543 -1180 1171 -548 1171 -486 1223 -506 487 -1208 1217 -484 1171 -554 1179 -484 489 -1232 1159 -520 1211 -482 513 -1176 1209 -512 1207 -520 475 -1180 1231 -478 1183 -520 1209 -480 507 -1200 1205 -480 1237 -486 1171 -486 539 -1182 507 -1218 1179 -490 1209 -518 1177 -480 541 -1172 515 -1228 481 -2192 1187 -520 483 -1228 1169 -480 495 -1246 461 -1248 1183 -476 1237 -482 1175 -516 497 -1214 1187 -500 1215 -492 1173 -548 483 -1176 1207 -518 1171 -518 509 -1200 1157 -528 1189 -520 483 -1228 1177 -480 1219 -488 1195 -524 489 -1208 1195 -506 1181 -506 1199 -508 487 -1210 499 -1210 1203 -484 1181 -486 1229 -516 463 -1246 493 -1180 481 -2234 1173 -524 489 -1198 1211 -490 505 -1180 515 -1208 1177 -516 1175 -516 1225 -482 473 -1198 1235 -484 1183 -524 1181 -506 473 -1250 1181 -476 1241 -480 481 -1246 1177 -484 1215 -514 +RAW_Data: 495 -1180 1197 -494 1211 -506 1199 -502 485 -1210 1195 -488 1211 -520 1183 -514 477 -1214 477 -1220 1181 -506 1215 -490 1197 -498 515 -1182 503 -1218 489 -2198 1205 -482 475 -1246 1161 -520 515 -1174 485 -1246 1195 -484 1209 -482 1207 -484 537 -1196 1181 -520 1179 -504 1209 -482 513 -1174 1237 -478 1199 -518 479 -1190 1225 -504 1181 -488 481 -1224 1177 -542 1197 -492 1199 -494 515 -1172 1215 -516 1173 -486 1233 -484 489 -1216 493 -1212 1205 -488 1175 -522 1205 -482 519 -1216 485 -1176 511 -2198 1229 -498 483 -1174 1239 -486 479 -1240 477 -1202 1201 -486 1205 -490 1217 -490 481 -1238 1165 -530 1213 -462 1215 -480 475 -1244 1181 -482 1215 -516 501 -1216 1187 -498 1211 -494 473 -1202 1203 -520 1179 -516 1175 -516 499 -1214 1189 -498 1213 -492 1185 -512 481 -1208 479 -1232 1175 -514 1193 -520 1179 -522 487 -1206 473 -1214 515 -2198 1173 -486 505 -1218 1189 -554 487 -1174 489 -1232 1159 -520 1209 -482 1209 -482 533 -1196 1181 -518 1181 -488 1211 -514 483 -1182 1233 -484 1181 -538 501 -1180 1213 -488 1205 -474 545 -1180 1173 -544 1173 -490 1227 -502 485 -1176 1213 -516 1171 -552 1177 -482 473 -1250 463 -1212 1197 -508 1185 -520 1183 -514 485 -1214 479 -1192 553 -2178 1215 -496 483 -1176 1215 -516 479 -1210 509 -1172 1215 -516 1155 -520 1217 -482 479 -1244 1175 -514 1209 -486 1199 -494 519 -1176 1203 -518 1177 -486 501 -1214 1211 -476 1205 -512 475 -1246 1173 -482 1243 -482 1197 -494 505 -1216 1177 -518 1171 -510 1199 -518 479 -1184 501 -1212 1209 -520 1183 -484 1213 -516 465 -1212 477 -1240 479 -2198 1199 -492 539 -1180 1183 -520 481 -1202 489 -1216 1183 -520 1211 -480 1173 -550 479 -1186 1231 -478 1189 -520 1209 -482 479 -1246 1177 -482 1217 -516 497 -1178 1213 -490 1173 -540 503 -1164 1223 -488 1195 -506 1215 -490 505 -1180 1205 -518 1173 -522 1197 -478 493 -1238 473 -1182 1243 -492 1185 -514 1175 -520 481 -1220 489 -1182 547 -2172 1201 -508 475 -1246 1183 -484 483 -1218 515 -1172 1207 -516 1177 -518 1209 -484 493 -1216 1181 -486 1215 -514 1175 -514 495 -1192 1227 -504 1181 -486 483 -1222 1209 -476 1217 -488 505 -1232 1173 -490 1233 -476 1189 -520 517 -1168 1203 -510 1197 -474 1231 -480 495 -1210 507 -1206 1175 -518 1179 -520 1219 -478 491 -1206 503 -1184 515 -2196 1175 -554 485 -1178 1215 -506 473 -1216 483 -1208 1181 -514 1205 -486 1211 -516 483 -1182 1231 -482 1199 -478 1223 -486 519 -1212 1175 -518 1207 -482 489 -1184 1239 -474 1193 -524 491 -1210 1215 -486 1169 -520 1207 -482 485 -1248 1161 -500 1235 -486 1177 -520 509 -1196 473 -1228 1191 -474 1225 -506 1181 -486 549 -1172 489 -1216 491 -2172 1233 -484 485 -1248 +RAW_Data: 1157 -498 511 -1202 509 -1174 1237 -476 1201 -514 1205 -484 493 -1200 1187 -520 1213 -482 1173 -548 477 -1190 1215 -492 1211 -476 543 -1174 1183 -538 1163 -498 509 -1204 1205 -484 1231 -482 1169 -542 501 -1184 1215 -490 1173 -518 1207 -480 503 -1236 485 -1198 1213 -500 1175 -476 1233 -488 481 -1206 513 -1210 485 -2212 1181 -538 477 -1174 1213 -518 499 -1182 519 -1180 1205 -516 1169 -508 1215 -504 487 -1176 1241 -484 1173 -552 1181 -484 489 -1216 1183 -518 1213 -480 507 -1208 1169 -510 1203 -520 477 -1188 1215 -494 1205 -474 1215 -516 477 -1240 1165 -492 1237 -474 1195 -502 517 -1182 505 -1232 1153 -518 1215 -482 1175 -514 531 -1194 485 -1214 479 -2234 1175 -488 487 -1246 1175 -484 535 -1192 485 -1206 1205 -476 1239 -480 1175 -516 463 -1246 1187 -532 1157 -500 1237 -484 479 -1208 1211 -480 1203 -484 539 -1188 1165 -538 1191 -474 507 -1214 1187 -502 1213 -482 1207 -518 481 -1190 1217 -494 1177 -520 1209 -482 485 -1250 499 -1180 1199 -494 1205 -476 1209 -514 483 -1244 483 -1196 485 -2208 1221 -478 489 -1210 1213 -486 479 -1202 513 -1212 1177 -512 1199 -490 1201 -510 489 -1208 1199 -502 1181 -520 1203 -478 477 -1246 1181 -482 1211 -518 499 -1210 1167 -510 1217 -490 483 -1216 1209 -484 1203 -494 1199 -504 487 -1210 1189 -494 1239 -492 1165 -526 493 -1208 473 -1244 1177 -476 1235 -480 1201 -518 479 -1190 499 -1244 481 -2194 1207 -482 473 -1250 1155 -520 519 -1176 485 -1248 1161 -518 1209 -482 1203 -480 537 -1192 1167 -536 1197 -462 1229 -506 487 -1206 1201 -502 1181 -520 479 -1204 1219 -480 1187 -520 515 -1172 1181 -542 1199 -486 1205 -516 479 -1176 1243 -480 1173 -550 1173 -486 503 -1238 481 -1172 1217 -518 1171 -484 1235 -480 509 -1212 503 -1182 473 -2238 1213 -496 483 -1174 1239 -486 479 -1242 487 -1176 1215 -514 1159 -520 1213 -480 481 -1246 1177 -482 1243 -484 1165 -518 515 -1208 1179 -512 1199 -486 481 -1230 1201 -482 1227 -482 473 -1216 1211 -490 1231 -478 1185 -518 483 -1228 1175 -478 1217 -508 1183 -536 497 -1176 473 -1232 1199 -494 1197 -508 1181 -554 477 -1174 485 -1248 461 -2236 1175 -482 521 -1212 1171 -518 505 -1198 477 -1222 1181 -490 1237 -474 1199 -536 497 -1178 1219 -486 1177 -520 1205 -482 487 -1184 1239 -474 1191 -526 493 -1208 1195 -508 1181 -504 505 -1198 1201 -520 1197 -482 1203 -522 485 -1164 1249 -462 1207 -506 1197 -504 485 -1210 499 -1182 1241 -480 1205 -518 1173 -482 493 -1240 471 -1184 549 -2170 1203 -508 507 -1178 1207 -480 517 -1216 479 -1186 1229 -482 1187 -520 1213 -478 509 -1208 1185 -480 1229 -520 1173 -492 519 -1184 1207 -518 1175 -486 501 -1218 1209 -488 1205 -482 519 -1216 1193 -488 +RAW_Data: 1215 -480 1207 -484 535 -1156 1217 -516 1177 -486 1247 -476 509 -1176 511 -1204 1183 -534 1195 -486 1211 -478 509 -1172 515 -1192 517 -2210 1171 -488 541 -1196 1157 -528 493 -1208 475 -1240 1169 -488 1237 -482 1183 -538 499 -1178 1229 -480 1183 -486 1245 -480 479 -1232 1177 -480 1245 -484 501 -1184 1233 -464 1211 -520 483 -1200 1219 -478 1183 -522 1211 -480 477 -1232 1185 -480 1243 -482 1193 -474 497 -1252 483 -1174 1217 -516 1171 -514 1209 -484 485 -1220 515 -1170 513 -2204 1205 -490 511 -1202 1199 -498 481 -1210 503 -1198 1209 -490 1197 -528 1191 -486 483 -1226 1175 -482 1223 -486 1197 -538 501 -1180 1219 -488 1173 -520 479 -1218 1179 -506 1215 -490 485 -1244 1171 -486 1235 -480 1199 -478 525 -1186 1199 -534 1193 -486 1215 -480 515 -1176 511 -1204 1185 -532 1191 -486 1213 -480 513 -1180 513 -1204 489 -2212 1175 -512 507 -1182 1233 -478 493 -1176 521 -1210 1171 -548 1175 -488 1217 -492 515 -1172 1241 -484 1175 -482 1235 -482 485 -1252 1159 -522 1213 -480 479 -1214 1207 -480 1209 -512 505 -1180 1229 -478 1189 -486 1243 -478 479 -1234 1177 -514 1211 -486 1201 -490 505 -1192 493 -1242 1181 -486 1207 -520 1181 -482 525 -1184 503 -1212 503 -2156 1241 -486 483 -1232 1177 -480 495 -1240 471 -1198 1235 -484 1185 -534 1161 -500 511 -1202 1201 -486 1231 -484 1179 -524 491 -1208 1201 -490 1209 -480 545 -1170 1169 -540 1199 -486 487 -1236 1173 -480 1221 -486 1193 -524 493 -1206 1197 -508 1181 -486 1217 -514 481 -1218 515 -1160 1223 -506 1183 -474 1241 -480 513 -1210 485 -1204 485 -2196 1197 -510 507 -1184 1227 -500 483 -1178 521 -1212 1179 -512 1203 -460 1251 -486 479 -1208 1209 -482 1207 -518 1173 -486 499 -1238 1181 -488 1213 -514 483 -1220 1173 -484 1235 -514 495 -1178 1217 -488 1171 -538 1195 -500 483 -1208 1197 -502 1209 -474 1197 -506 521 -1180 505 -1218 1179 -506 1197 -506 1181 -504 505 -1184 517 -1204 505 -2212 1171 -516 495 -1214 1187 -464 549 -1180 487 -1216 1203 -486 1229 -484 1183 -502 519 -1182 1197 -532 1193 -486 1215 -480 481 -1208 1207 -478 1203 -548 477 -1182 1229 -480 1185 -504 503 -1216 1177 -486 1245 -476 1199 -516 479 -1190 1229 -502 1179 -474 1245 -480 479 -1246 481 -1198 1217 -478 1183 -522 1209 -478 507 -1210 485 -1208 489 -2210 1207 -486 509 -1182 1229 -478 491 -1208 521 -1184 1209 -482 1203 -490 1219 -490 513 -1172 1209 -482 1205 -520 1209 -480 471 -1234 1209 -486 1215 -500 485 -1208 1181 -514 1203 -506 471 -1216 1203 -506 1161 -528 1185 -522 487 -1196 1209 -478 1221 -488 1185 -548 483 -1172 505 -1204 1207 -488 1211 -514 1173 -514 509 -1180 505 -1216 487 -2204 1179 -484 529 -1216 1183 -488 515 -1202 +RAW_Data: 489 -1232 1171 -490 1233 -478 1187 -486 553 -1168 1201 -512 1197 -492 1199 -496 515 -1174 1209 -516 1167 -520 517 -1192 1187 -508 1185 -486 553 -1172 1187 -534 1187 -474 1231 -476 493 -1206 1197 -508 1181 -520 1205 -478 509 -1206 515 -1170 1217 -504 1177 -506 1205 -482 511 -1208 487 -1206 491 -2212 1209 -482 503 -1184 1227 -500 485 -1206 521 -1174 1203 -514 1167 -506 1231 -484 497 -1210 1199 -478 1221 -488 1197 -498 519 -1184 1201 -516 1177 -504 473 -1216 1207 -474 1249 -484 477 -1238 1169 -516 1201 -506 1161 -496 513 -1202 1199 -502 1211 -488 1229 -480 491 -1208 477 -1236 1167 -482 1237 -482 1183 -542 501 -1180 485 -1214 509 -2194 1197 -490 537 -1166 1197 -506 505 -1180 507 -1218 1183 -504 1211 -488 1185 -516 515 -1170 1237 -486 1201 -488 1207 -482 517 -1196 1215 -480 1229 -484 499 -1178 1235 -478 1181 -486 553 -1174 1183 -534 1191 -462 1247 -486 479 -1204 1207 -482 1205 -520 1209 -486 493 -1218 487 -1176 1217 -514 1175 -520 1213 -450 519 -1210 513 -1172 513 -2174 1249 -482 477 -1236 1197 -464 513 -1206 503 -1200 1205 -474 1195 -506 1213 -490 503 -1198 1201 -472 1229 -516 1193 -486 515 -1174 1209 -512 1201 -492 477 -1242 1173 -486 1237 -482 485 -1240 1175 -486 1241 -480 1175 -514 531 -1156 1189 -524 1185 -520 1209 -480 509 -1168 549 -1176 1183 -536 1195 -488 1211 -480 513 -1174 547 -1174 489 -2194 1201 -482 535 -1196 1187 -498 529 -1178 475 -1242 1173 -482 1235 -482 1183 -526 493 -1208 1195 -506 1179 -486 1243 -480 479 -1232 1181 -480 1229 -522 477 -1180 1231 -478 1185 -556 477 -1174 1243 -486 1195 -488 1217 -482 479 -1248 1181 -484 1215 -506 1183 -476 515 -1196 517 -1218 1169 -486 1229 -482 1191 -500 511 -1204 477 -1200 547 -2164 1209 -482 511 -1206 1185 -514 483 -1216 513 -1160 1249 -462 1209 -522 1175 -482 517 -1218 1199 -484 1211 -482 1207 -518 481 -1188 1215 -494 1207 -472 545 -1182 1175 -550 1173 -490 507 -1208 1175 -488 diff --git a/applications/debug/unit_tests/resources/unit_tests/subghz/hay21_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/hay21_raw.sub new file mode 100644 index 000000000..beab06462 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/subghz/hay21_raw.sub @@ -0,0 +1,10 @@ +Filetype: Flipper SubGhz RAW File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: RAW +RAW_Data: 99 -820 65 -230 65 -802 361 -852 131 -162 131 -298 133 -306 99 -198 129 -430 99 -732 197 -742 131 -302 197 -238 233 -132 99 -532 99 -168 101 -306 65 -832 133 -304 65 -300 65 -664 131 -198 133 -232 101 -234 99 -134 133 -404 65 -262 163 -654 131 -702 133 -864 131 -194 95 -390 131 -388 99 -1078 65 -260 97 -364 65 -194 161 -258 163 -228 97 -394 131 -336 133 -198 63 -358 97 -392 199 -164 65 -390 97 -426 99 -162 163 -326 193 -326 131 -456 131 -400 99 -700 133 -198 133 -400 131 -302 99 -202 65 -200 99 -1002 133 -236 169 -406 165 -372 99 -202 99 -334 165 -304 131 -332 133 -132 65 -464 133 -400 99 -298 67 -960 163 -400 133 -402 129 -166 101 -268 133 -432 133 -374 101 -366 101 -19558 129 -454 97 -23210 133 -688 129 -196 131 -162 65 -422 65 -132 163 -228 63 -194 65 -228 131 -292 129 -552 65 -986 65 -530 131 -164 99 -294 65 -796 65 -466 133 -200 131 -136 133 -1970 99 -1594 133 -966 65 -100 99 -300 99 -534 65 -664 99 -332 65 -198 97 -1086 131 -304 133 -402 167 -166 67 -996 99 -728 133 -268 131 -1684 163 -394 129 -690 65 -558 65 -132 131 -162 95 -456 97 -362 133 -168 67 -306 133 -202 67 -168 197 -338 167 -438 101 -362 97 -470 101 -402 65 -1058 131 -336 133 -1402 133 -402 165 -236 65 -432 99 -764 133 -470 65 -200 67 -166 131 -436 99 -398 99 -168 135 -902 65 -234 135 -200 199 -166 65 -628 197 -662 165 -694 65 -166 67 -564 65 -1072 99 -334 167 -168 131 -1058 131 -336 131 -370 99 -198 133 -462 131 -302 133 -438 67 -368 133 -1036 97 -634 165 -134 135 -670 99 -370 161 -690 65 -296 131 -1000 99 -436 133 -170 101 -404 99 -170 101 -270 67 -268 131 -302 131 -672 133 -234 65 -502 99 -604 65 -1260 99 -232 99 -964 131 -338 99 -200 65 -232 99 -168 135 -168 67 -204 67 -134 133 -370 99 -428 133 -234 135 -1128 163 -454 97 -362 163 -458 65 -164 99 -596 99 -268 131 -1562 65 -398 99 -402 163 -438 67 -368 65 -988 97 -622 131 -162 97 -390 133 -368 135 -402 133 -164 65 -438 131 -686 165 -334 99 -460 99 -368 167 -22538 65 -170 65 -766 99 -1098 165 -760 99 -364 65 -722 65 -2132 163 -326 163 -402 133 -266 161 -406 97 -168 101 -404 163 -168 67 -266 65 -334 65 -402 97 -494 65 -132 97 -462 99 -336 133 -334 131 -198 133 -672 131 -200 65 -1036 67 -1028 165 -702 99 -402 97 -1234 97 -196 63 -488 131 -658 65 -498 +RAW_Data: 97 -270 131 -566 65 -168 67 -336 67 -996 133 -300 67 -166 101 -702 99 -1202 131 -264 97 -590 163 -1680 99 -228 95 -162 97 -194 65 -886 99 -3586 65 -558 131 -1486 99 -634 65 -100 133 -132 67 -1134 97 -658 65 -1446 65 -564 133 -234 133 -372 65 -166 99 -1362 99 -332 97 -202 133 -1984 97 -198 65 -394 161 -23544 65 -334 99 -298 165 -570 167 -334 99 -198 67 -836 131 -290 65 -724 99 -298 165 -130 97 -264 127 -628 165 -266 131 -200 101 -234 135 -564 165 -234 99 -202 97 -368 131 -466 131 -1770 65 -326 165 -622 131 -296 133 -496 97 -1166 65 -1512 97 -1090 99 -534 165 -498 101 -336 99 -300 167 -302 65 -302 133 -1158 65 -166 99 -666 65 -736 233 -566 99 -502 99 -596 131 -168 135 -370 99 -1758 67 -3408 129 -1212 99 -690 65 -2674 67 -696 97 -200 65 -500 97 -338 65 -298 99 -234 165 -636 97 -724 65 -1424 97 -918 131 -198 65 -366 65 -562 99 -456 163 -1142 99 -394 165 -262 325 -230 97 -424 65 -460 131 -1150 129 -394 163 -356 197 -722 131 -920 97 -424 65 -1114 65 -262 97 -394 97 -888 97 -520 97 -21882 97 -2496 97 -164 97 -848 99 -47158 99 -234 99 -600 67 -1088 65 -918 63 -230 99 -162 127 -288 131 -164 97 -756 131 -392 99 -404 65 -400 67 -132 99 -302 131 -688 65 -982 131 -970 199 -372 101 -966 97 -130 129 -980 133 -1116 99 -162 95 -390 65 -428 163 -324 131 -266 99 -266 135 -1302 131 -470 199 -472 97 -358 97 -164 65 -230 99 -402 99 -398 99 -370 133 -372 99 -398 99 -166 129 -394 99 -266 67 -668 133 -1000 133 -734 131 -898 163 -324 131 -164 97 -424 99 -336 165 -328 163 -162 63 -946 129 -620 131 -130 129 -356 63 -296 131 -194 65 -390 97 -1692 129 -360 195 -390 127 -400 133 -200 65 -1034 133 -630 97 -162 129 -422 133 -1626 97 -368 133 -202 97 -1038 133 -498 133 -630 165 -730 97 -704 65 -134 97 -434 165 -462 99 -268 99 -628 165 -672 131 -1448 165 -604 167 -366 99 -398 131 -230 67 -438 65 -134 133 -432 67 -430 97 -232 65 -404 101 -426 131 -198 97 -756 131 -290 165 -298 99 -200 65 -1330 99 -702 131 -462 229 -294 161 -656 97 -196 97 -390 165 -1510 63 -2134 131 -328 129 -164 97 -726 99 -166 67 -134 97 -66 65 -730 99 -264 133 -266 165 -298 67 -1132 99 -1000 101 -200 67 -766 101 -368 165 -404 133 -832 99 -604 133 -640 197 -234 101 -134 99 -432 99 -100 133 -792 131 -168 101 -500 99 -298 101 -764 +RAW_Data: 133 -334 97 -196 97 -266 131 -202 167 -798 97 -966 99 -802 65 -396 65 -1062 101 -794 97 -770 131 -230 133 -728 99 -398 99 -268 133 -268 101 -1004 65 -964 97 -402 101 -438 131 -1168 97 -1494 65 -1446 99 -202 65 -566 131 -306 99 -198 65 -234 131 -470 63 -1092 165 -660 99 -266 131 -1006 67 -198 99 -3394 65 -696 199 -1100 65 -228 163 -230 131 -360 65 -228 97 -230 97 -164 97 -228 197 -164 135 -332 165 -166 133 -168 131 -1696 97 -1742 99 -556 131 -1180 65 -534 67 -794 65 -360 99 -2958 99 -532 65 -234 101 -166 67 -232 99 -734 99 -528 65 -432 65 -198 133 -66 197 -334 63 -924 131 -754 131 -1064 67 -298 99 -566 65 -330 65 -202 65 -998 65 -2024 163 -834 99 -366 65 -398 167 -5114 755 -4222 691 -310 657 -348 247 -724 257 -714 257 -726 283 -698 669 -302 683 -294 703 -312 285 -694 679 -290 679 -308 691 -318 657 -290 289 -702 677 -316 659 -324 297 -678 677 -316 691 -292 693 -4228 675 -318 667 -304 281 -692 289 -700 285 -694 281 -728 669 -302 683 -294 701 -312 285 -664 683 -324 673 -312 657 -316 691 -290 283 -702 677 -318 659 -322 295 -674 707 -284 697 -274 685 -4252 677 -318 663 -304 317 -688 259 -698 309 -696 283 -684 677 -316 661 -342 653 -294 309 -692 675 -318 659 -324 655 -344 657 -318 287 -698 683 -278 695 -310 285 -694 675 -318 663 -302 683 -4250 675 -320 659 -292 303 -682 291 -700 311 -662 317 -690 677 -318 661 -292 703 -310 269 -696 669 -316 691 -290 677 -310 689 -320 287 -682 685 -314 657 -314 285 -694 677 -316 663 -294 701 -4224 677 -318 661 -322 293 -676 289 -700 297 -696 281 -694 679 -294 701 -312 657 -316 285 -688 681 -314 695 -310 649 -294 701 -302 277 -700 681 -314 681 -290 285 -704 677 -318 659 -292 721 -4218 675 -318 693 -290 295 -680 291 -702 275 -698 313 -686 677 -318 663 -304 681 -294 277 -716 671 -318 659 -324 661 -344 655 -318 285 -698 685 -278 693 -312 285 -686 677 -316 661 -306 683 -4248 679 -318 663 -304 317 -656 289 -700 311 -662 317 -692 677 -318 663 -292 705 -310 271 -696 687 -312 657 -292 701 -312 657 -316 287 -686 677 -316 693 -310 287 -694 677 -318 665 -300 679 -4246 677 -318 665 -300 279 -692 289 -700 309 -662 317 -692 677 -318 667 -300 683 -294 277 -716 677 -320 659 -290 689 -310 689 -320 287 -682 687 -314 657 -314 287 -694 677 -318 663 -304 681 -4254 677 -286 701 -300 277 -684 289 -698 285 -726 277 -704 677 -294 699 -274 +RAW_Data: 693 -320 273 -696 681 -290 681 -308 691 -320 677 -290 295 -680 677 -318 695 -290 295 -678 675 -320 695 -258 721 -4222 677 -320 673 -294 301 -682 291 -696 311 -664 317 -692 673 -320 665 -300 681 -294 275 -718 677 -318 661 -290 687 -308 691 -320 285 -698 683 -280 691 -312 287 -694 677 -318 663 -144512 67 -362 131 -164 97 -456 99 -268 167 -674 133 -404 165 -334 165 -168 67 -404 133 -440 165 -304 165 -568 163 -298 97 -1054 65 -864 165 -302 101 -370 67 -764 97 -362 65 -690 99 -2320 99 -200 65 -1630 131 -564 99 -200 67 -302 65 -762 65 -366 99 -432 131 -134 131 -166 99 -366 133 -168 103 -668 99 -166 133 -304 133 -338 65 -200 99 -768 99 -368 131 -264 101 -402 65 -1062 131 -134 65 -434 133 -234 65 -396 99 -166 133 -366 65 -432 99 -766 167 -762 165 -1320 65 -490 97 -396 131 -464 97 -1346 99 -230 63 -420 97 -324 127 -822 131 -1558 65 -472 99 -500 65 -134 131 -434 65 -664 163 -470 133 -328 131 -1228 97 -164 97 -692 129 -936 133 -1882 99 -1620 63 -1118 97 -488 129 -728 99 -466 101 -468 65 -198 131 -202 65 -266 167 -630 65 -232 99 -1590 131 -666 97 -1294 99 -728 97 -564 67 -436 97 -22018 99 -1560 101 -200 65 -1896 65 -532 133 -202 97 -2158 133 -1600 65 -1016 65 -198 65 -260 67 -1154 65 -2234 97 -1188 133 -232 101 -336 99 -2400 99 -856 133 -884 97 -1312 129 -456 129 -746 65 -988 165 -324 99 -430 129 -164 99 -362 129 -360 131 -428 131 -598 65 -464 99 -468 131 -22500 65 -758 67 -2260 99 -164 99 -658 63 -228 163 -708 65 -730 99 -370 99 -728 97 -502 67 -298 165 -968 99 -198 65 -398 129 -882 131 -358 97 -22984 65 -688 97 -196 97 -718 131 -1210 129 -654 99 -170 135 -168 67 -338 135 -266 65 -500 65 -794 99 -100 99 -406 97 -234 65 -168 99 -406 63 -100 165 -200 99 -1298 131 -772 165 -704 65 -132 65 -1660 163 -360 133 -830 97 -724 99 -732 99 -426 63 -1356 165 -1180 131 -228 129 -720 167 -832 131 -262 163 -590 131 -400 99 -234 133 -166 67 -1598 99 -1428 65 -592 163 -286 131 -816 133 -266 167 -500 131 -698 133 -468 99 -402 99 -434 167 -200 165 -162 99 -304 101 -738 133 -336 99 -1800 165 -732 133 -722 67 -556 97 -326 97 -164 163 -458 99 -434 65 -166 65 -198 161 -1148 165 -914 99 -162 65 -130 165 -992 163 -952 65 -560 99 -826 65 -664 163 -620 197 -350 161 -292 161 -388 65 -494 65 -432 67 -202 101 -234 65 -722 +RAW_Data: 163 -196 63 -460 163 -468 65 -166 99 -334 129 -666 63 -230 63 -66 97 -328 129 -232 127 -494 129 -358 131 -260 197 -322 97 -628 99 -656 163 -358 99 -2128 133 -336 99 -1070 165 -368 99 -1004 65 -332 65 -330 163 -102 99 -2830 65 -1434 65 -800 65 -530 65 -1516 131 -400 97 -554 63 -300 99 -334 99 -1130 65 -896 99 -166 65 -1724 131 -270 99 -1068 131 -1168 97 -52402 99 -2098 99 -196 65 -454 65 -1644 97 -1754 65 -332 97 -794 131 -364 165 -330 99 -2220 129 -468 197 -1090 97 -360 65 -1746 63 -164 127 -784 131 -1284 65 -396 65 -390 97 -422 65 -428 97 -592 97 -1312 65 -694 131 -266 67 -732 97 -460 195 -360 65 -982 97 -2244 133 -472 99 -1096 97 -1762 65 -1664 131 -134 131 -1192 97 -1736 131 -204 99 -668 99 -602 165 -654 163 -324 161 -328 129 -960 65 -466 101 -768 131 -1588 99 -502 67 -988 65 -952 129 -424 65 -228 161 -162 129 -452 131 -954 65 -954 99 -328 65 -1564 133 -1300 131 -164 133 -728 135 -200 165 -298 131 -1572 131 -328 65 -164 133 -336 99 -1260 133 -370 97 -466 67 -830 197 -1260 131 -330 131 -1034 65 -198 97 -1378 133 diff --git a/applications/debug/unit_tests/resources/unit_tests/subghz/hollarm.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/hollarm.sub new file mode 100644 index 000000000..0b29739ee --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/subghz/hollarm.sub @@ -0,0 +1,7 @@ +Filetype: Flipper SubGhz Key File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: Hollarm +Bit: 42 +Key: 00 00 00 ED 24 3C 22 6F diff --git a/applications/debug/unit_tests/resources/unit_tests/subghz/hollarm_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/hollarm_raw.sub new file mode 100644 index 000000000..8bc83019e --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/subghz/hollarm_raw.sub @@ -0,0 +1,10 @@ +Filetype: Flipper SubGhz RAW File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: RAW +RAW_Data: 211 -978 199 -1602 209 -988 193 -990 217 -1592 209 -1616 205 -958 201 -1618 211 -1574 195 -1628 193 -1590 201 -992 209 -2406 199 -1604 195 -1592 195 -1616 193 -990 185 -1628 211 -1586 201 -986 201 -1608 211 -984 199 -996 233 -1576 195 -998 203 -990 195 -1632 193 -994 189 -998 205 -990 199 -1006 205 -1586 197 -1596 225 -1584 195 -1614 195 -992 211 -1020 195 -960 207 -984 195 -1618 201 -998 247 -952 199 -998 201 -1610 197 -1002 205 -1018 197 -1566 193 -1618 201 -1000 209 -1582 195 -1632 195 -1560 197 -1638 207 -956 199 -2416 199 -1626 177 -1614 201 -1580 201 -1032 213 -1548 197 -1626 193 -994 187 -1628 207 -950 225 -992 203 -1588 207 -1026 203 -968 205 -1622 197 -970 191 -1028 209 -982 199 -1028 205 -1552 201 -1628 201 -1556 207 -1632 197 -974 239 -978 201 -982 205 -1022 199 -1572 201 -1032 177 -984 197 -1024 185 -1594 209 -1016 201 -1000 211 -1594 197 -1572 201 -1034 177 -1582 197 -1626 195 -1622 193 -1556 199 -1030 207 -2384 199 -1594 195 -1618 201 -1562 241 -982 195 -1630 195 -1590 195 -1022 189 -1590 185 -1030 205 -954 227 -1590 191 -996 207 -1030 195 -1584 195 -1026 185 -1000 197 -974 205 -1020 199 -1572 227 -1584 193 -1616 195 -1586 201 -1028 207 -956 203 -1016 203 -986 203 -1610 211 -980 199 -976 235 -992 195 -1620 193 -996 211 -974 201 -1590 211 -1610 201 -980 193 -1620 191 -1592 209 -1618 205 -1586 197 -1000 191 -2416 205 -1586 207 -1564 225 -1586 227 -958 221 -1620 189 -1590 215 -994 207 -1588 229 -968 191 -996 205 -1588 229 -972 189 -1028 211 -1574 227 -1000 193 -962 243 -1002 201 -968 201 -1612 197 -1594 203 -1592 209 -1618 207 -958 203 -992 229 -1006 201 -980 199 -1602 195 -996 189 -1028 213 -976 201 -1592 209 -980 195 -1026 187 -1590 211 -1608 201 -978 205 -1610 211 -1606 199 -1574 201 -1596 201 -994 195 -2424 193 -1588 227 -1554 235 -1592 205 -988 227 -1562 201 -1620 195 -990 211 -1608 199 -978 205 -1014 199 -1574 201 -1032 211 -958 199 -1626 211 -958 199 -1038 211 -974 201 -962 229 -1600 195 -1622 193 -1588 193 -1618 195 -988 185 -1032 205 -960 203 -1014 237 -1546 201 -1028 187 -996 201 -978 223 -1582 225 -994 211 -986 199 -1624 213 -1550 195 -1036 201 -1556 207 -1630 197 -1602 195 -1590 193 -1022 189 -2382 215 -1590 211 -1586 203 -1580 195 -1020 225 -1558 185 -1628 209 -988 225 -1580 195 -1020 191 -996 205 -1620 197 -970 191 -996 207 -1594 197 -1004 239 -980 199 -980 205 -1016 199 -1576 201 -1628 203 -1606 213 -1550 195 -1034 203 -976 199 -976 225 -990 211 -1614 199 -978 205 -992 193 -1032 203 -1574 213 -1002 201 -968 +RAW_Data: 207 -1594 231 -1598 195 -1000 207 -1576 245 -1564 193 -1618 195 -1554 257 -990 207 -2372 207 -1596 193 -1590 225 -1586 225 -958 221 -1590 219 -1592 213 -982 193 -1586 199 -1040 211 -970 201 -1588 209 -986 201 -1006 209 -1596 193 -1024 193 -994 211 -1002 201 -968 207 -1596 229 -1566 201 -1596 201 -1618 211 -982 199 -978 199 -1018 201 -996 199 -1606 197 -998 207 -988 229 -974 203 -1612 211 -974 201 -1014 207 -1554 225 -1584 225 -960 219 -1590 219 -1590 215 -1590 185 -1624 185 -998 199 -2404 195 -1626 195 -1556 235 -1594 191 -996 207 -1584 229 -1600 193 -998 205 -1584 207 -1004 201 -992 203 -1580 201 -1030 211 -960 197 -1630 211 -958 199 -1008 241 -964 201 -996 197 -1602 195 -1628 195 -1560 235 -1592 211 -958 199 -1032 207 -964 203 -996 241 -1568 195 -1032 193 -992 211 -976 201 -1606 209 -960 235 -996 207 -1558 259 -1554 195 -1020 225 -1558 209 -1620 207 -1552 201 -1626 199 -996 233 -2368 195 -1588 225 -1586 225 -1552 227 -992 207 -1584 201 -1612 195 -1000 205 -1584 207 -1004 203 -992 201 -1582 227 -994 205 -982 195 -1624 195 -994 189 -1032 211 -970 201 -994 237 -1546 201 -1624 201 -1570 247 -1550 229 -1008 201 -994 197 -976 203 -1016 199 -1574 201 -1030 185 -998 199 -1010 201 -1566 211 -1022 201 -998 213 -1596 197 -1570 201 -1030 185 -1594 247 -1544 197 -1626 195 -1588 193 -1024 205 -2378 207 -1594 195 -1588 227 -1586 225 -960 219 -1594 207 -1584 201 -1010 193 -1588 205 -1014 201 -980 191 -1620 191 -996 207 -1028 195 -1582 195 -1028 213 -956 199 -976 237 -996 201 -1606 211 -1582 197 -1602 193 -1596 201 -1000 211 -982 201 -1030 205 -954 203 -1612 211 -978 195 -1024 185 -996 201 -1610 195 -1002 205 -984 225 -1560 225 -1588 227 -958 219 -1594 219 -1558 217 -1596 211 -1610 201 -982 205 -2402 211 -1608 199 -1572 203 -1592 225 -960 245 -1598 197 -1600 195 -998 205 -1582 209 -1004 201 -960 197 -1630 195 -1000 243 -946 231 -1596 195 -994 191 -996 205 -994 199 -998 199 -1610 195 -1598 239 -1550 193 -1620 191 -996 207 -1032 201 -964 201 -984 203 -1628 207 -968 199 -1014 201 -994 199 -1604 195 -996 189 -1030 211 -1570 193 -1622 193 -990 189 -1628 207 -1586 207 -1556 227 -1588 225 -958 219 -2388 209 -1618 205 -1592 195 -1598 201 -998 207 -1600 193 -1592 201 -992 211 -1608 199 -978 203 -1016 201 -1578 201 -1030 185 -996 197 -1602 197 -996 191 -1028 211 -972 195 -992 247 -1548 229 -1596 197 -1588 195 -1616 195 -990 185 -1028 207 -960 235 -994 201 -1578 201 -1030 185 -996 199 -1008 195 -1582 193 -1028 211 -980 201 -1604 209 -1582 197 -1008 195 -1588 +RAW_Data: 203 -1610 211 -1606 199 -1574 201 -1000 215 -2388 211 -1580 195 -1626 195 -1560 271 -960 211 -1574 229 -1566 201 -1026 209 -1588 197 -1008 201 -996 197 -1602 193 -996 189 -996 241 -1570 195 -1032 203 -976 199 -976 205 -1024 199 -1570 201 -1622 195 -1622 203 -1554 207 -1000 235 -962 201 -1012 195 -992 211 -1608 201 -982 205 -984 229 -970 237 -1556 243 -974 195 -990 209 -1608 201 -1608 197 -998 207 -1578 245 -1564 193 -1588 227 -1584 225 -994 205 -2380 207 -1556 259 -1556 193 -1616 225 -958 219 -1594 209 -1580 199 -1014 195 -1582 255 -960 211 -982 201 -1602 209 -986 195 -1018 193 -1592 211 -1020 193 -992 189 -1030 211 -970 201 -1608 211 -1554 197 -1630 195 -1622 195 -990 189 -996 205 -988 201 -996 201 -1606 195 -998 189 -1030 211 -976 203 -1588 211 -986 199 -1008 209 -1596 195 -1584 227 -990 187 -1624 187 -1594 243 -1548 201 -1610 195 -998 189 -2414 189 -1620 191 -1590 185 -1622 185 -998 235 -1584 195 -1622 193 -992 189 -1626 207 -950 195 -1022 225 -1556 209 -1020 195 -998 207 -1582 245 -968 203 -1012 205 -956 203 -994 195 -1630 193 -1590 227 -1552 225 -1590 199 -998 201 -1016 199 -990 213 -1006 195 -1586 193 -1026 185 -998 199 -974 225 -1584 203 -998 241 -968 195 -1618 195 -1584 191 -1028 211 -1574 193 -1592 225 -1558 271 -1554 191 -1028 211 -2366 227 -1562 237 -1586 199 -1568 211 -1016 193 -1580 197 -1614 195 -990 211 -1612 199 -982 239 -996 197 -1600 195 -994 191 -996 205 -1586 229 -968 191 -1032 211 -976 201 -996 239 -1548 201 -1624 201 -1590 209 -1582 197 -1028 195 -992 211 -972 223 -990 211 -1552 263 -978 203 -978 199 -1012 201 -1568 211 -1018 201 -1000 213 -1596 197 -1566 227 -992 205 -1620 211 -1566 193 -1592 227 -1554 235 -1000 213 -2398 199 -1570 201 -1598 203 -1610 211 -984 199 -1606 209 -1584 197 -1010 199 -1570 211 -1018 201 -1000 211 -1594 199 -970 193 -1028 211 -1572 195 -1028 195 -992 211 -1006 201 -966 201 -1614 197 -1594 193 -1618 201 -1564 245 -982 195 -1000 205 -984 229 -970 191 -1626 203 -984 195 -1028 193 -992 211 -1604 197 -974 203 -1026 199 -1574 203 -1594 203 -990 197 -1630 193 -1560 271 -1554 189 -1628 205 -988 193 -2382 237 -1588 199 -1592 211 -1596 199 -974 191 -1622 189 -1624 205 -968 235 -1576 197 -1000 191 -996 241 -1570 193 -1028 195 -992 211 -1604 199 -976 203 -990 195 -1028 201 -978 197 -1606 195 -1628 195 -1558 235 -1588 187 -996 201 -1016 199 -1006 205 -952 255 -1584 193 -962 279 -948 199 -1006 205 -1582 229 -968 191 -1028 211 -1574 193 -1594 227 -960 243 -1590 205 -1588 197 -1600 201 -1596 203 -990 +RAW_Data: 195 -2422 193 -1584 227 -1618 191 -1558 235 -994 209 -1558 225 -1588 237 -956 211 -1612 197 -980 205 -1000 245 -1560 193 -1026 193 -994 211 -1598 197 -974 205 -1022 199 -980 203 -1022 199 -1570 201 -1598 201 -1588 207 -1620 199 -972 237 -992 211 -974 201 -1012 207 -1550 227 -992 237 -958 195 -1034 193 -1588 189 -1032 211 -974 201 -1608 211 -1556 195 -1034 199 -1558 243 -1596 197 -1602 195 -1594 195 -1026 205 -2388 185 -1592 211 -1602 199 -1574 227 -992 203 -1614 211 -1570 193 -1030 203 -1554 207 -1032 201 -970 207 -1590 229 -968 191 -1030 211 -1572 195 -1032 203 -958 195 -1004 205 -1022 197 -1568 227 -1590 201 -1600 203 -1586 207 -990 225 -958 217 -1000 239 -950 225 -1588 225 -962 209 -996 225 -990 185 -1624 209 -956 225 -962 211 -1616 199 -1608 195 -1002 205 -1608 211 -1568 195 -1588 227 -1588 237 -956 211 -2406 199 -1604 195 -1594 201 -1596 209 -990 201 -1592 209 -1584 233 -986 201 -1590 211 -984 201 -976 203 -1614 197 -1000 207 -988 229 -1566 237 -990 185 -996 197 -1012 201 -996 197 -1602 195 -1592 193 -1616 201 -1598 203 -982 195 -998 223 -990 211 -984 201 -1590 213 -984 195 -1024 187 -994 201 -1610 195 -1004 203 -1000 209 -1562 225 -1586 227 -956 243 -1582 207 -1596 229 -1566 203 -1598 201 -982 197 -2426 193 -1588 227 -1554 271 -1550 193 -1028 211 -1578 195 -1622 193 -992 187 -1622 189 -996 205 -988 225 -1556 203 -1014 237 -952 203 -1618 211 -976 201 -1010 205 -966 225 -964 245 -1564 231 -1598 195 -1590 193 -1618 193 -990 185 -1030 207 -964 201 -1016 237 -1548 201 -1028 211 -960 199 -1032 207 -1556 235 -990 187 -996 203 -1612 197 -1596 201 -998 209 -1560 227 -1590 201 -1624 201 -1572 211 -1012 201 -2360 243 -1580 207 -1586 229 -1566 203 -1002 209 -1580 227 -1596 195 -958 251 -1592 207 -988 195 -1000 191 -1624 205 -982 193 -994 189 -1626 205 -952 257 -964 235 -960 195 -1036 201 -1556 207 -1630 199 -1600 195 -1594 201 -998 207 -1002 201 -958 231 -1006 193 -1622 203 -976 199 -974 191 -1028 211 -1576 227 -968 227 -960 245 -1596 197 -1602 195 -1000 205 -1584 207 -1598 193 -1586 225 -1584 225 -986 189 -2420 207 -1550 201 -1614 195 -1624 195 -994 189 -1626 207 -1550 203 -1016 201 -1592 211 -1008 201 -966 209 -1628 197 -972 191 -1028 211 -1576 195 -1030 201 -978 199 -1006 193 -992 211 -1610 199 -1576 195 -1620 195 -1614 195 -990 211 -956 235 -996 207 -998 201 -1586 213 -978 195 -1024 187 -998 199 -1606 195 -1004 205 -1000 207 -1562 225 -1588 225 -960 219 -1592 219 -1594 207 -1586 199 -1612 195 -998 207 -2372 209 -1598 231 -1562 195 -1614 +RAW_Data: 195 -986 219 -1592 209 -1618 207 -960 203 -1624 211 -974 193 -1026 211 -1566 207 -996 235 -962 diff --git a/applications/debug/unit_tests/resources/unit_tests/subghz/legrand.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/legrand.sub new file mode 100644 index 000000000..7041324cb --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/subghz/legrand.sub @@ -0,0 +1,8 @@ +Filetype: Flipper SubGhz Key File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: Legrand +Bit: 18 +Key: 00 00 00 00 00 02 E3 7F +TE: 358 diff --git a/applications/debug/unit_tests/resources/unit_tests/subghz/legrand_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/legrand_raw.sub new file mode 100644 index 000000000..ba703925c --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/subghz/legrand_raw.sub @@ -0,0 +1,22 @@ +Filetype: Flipper SubGhz RAW File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: RAW +RAW_Data: 10133 -4946 65 -5114 65 -1750 67 -232 165 -536 165 -166 533 -694 395 -198 263 -66 395 -12664 297 -662 133 -264 99 -102 131 -200 131 -234 263 -66 265 -100 661 -168 197 -134 297 -66 431 -132 45005 -12358 131 -100 129 -66 65 -198 65 -66 197 -1018 131 -266 97 -1052 97 -228 459 -100 1517 -100 4361 -10350 165 -1264 65 -232 131 -200 65 -166 133 -232 299 -132 65 -166 299 -134 231 -198 99 -68 429 -12270 231 -628 163 -594 97 -496 227 -1220 163 -166 129 -66 655 -98 163 -66 393 -66 195 -198 293 -12130 67 -68 331 -134 99 -98 99 -332 99 -562 163 -164 65 -130 131 -920 131 -98 65 -198 133 -98 427 -130 359 -132 627 -66 2207 -16066 163 -1316 331 -832 131 -370 131 -132 661 -166 265 -134 833 -168 34477 -10228 65 -2266 261 -230 131 -198 129 -1282 65 -66 463 -100 365 -232 233 -66 297 -166 2151 -12330 99 -400 97 -436 131 -896 165 -400 363 -298 199 -166 97 -430 165 -168 365 -132 725 -130 3253 -12440 131 -796 297 -332 65 -830 65 -828 429 -198 335 -66 665 -266 29175 -12140 163 -230 131 -992 165 -1230 397 -132 199 -132 299 -66 659 -100 9675 -11036 99 -570 99 -830 165 -662 331 -266 429 -132 391 -396 431 -132 233 -166 231 -100 3435 -11728 99 -332 165 -166 63 -128 161 -326 129 -690 231 -130 265 -98 167 -232 199 -66 201 -264 165 -166 365 -66 727 -100 363 -12688 229 -228 95 -98 391 -198 129 -196 97 -364 97 -100 65 -266 99 -64 293 -98 327 -262 761 -66 7681 -5384 99 -762 195 -134 525 -992 99 -166 67 -98 329 -100 659 -100 293 -100 97 -98 163 -264 1741 -100 57331 -9834 67 -664 67 -2158 99 -862 133 -368 331 -864 65 -330 65 -794 99 -66 431 -100 3017 -66 7061 -6558 99 -6316 99 -266 131 -166 97 -198 65 -294 99 -66 661 -132 2661 -130 19413 -12380 65 -262 133 -66 67 -232 133 -798 197 -264 165 -498 97 -166 199 -134 559 -98 165 -13118 99 -562 65 -1256 231 -232 99 -132 131 -66 199 -66 429 -296 131 -200 201 -266 65 -66 895 -132 829 -66 12455 -11974 199 -334 99 -2160 165 -1232 929 -100 327 -166 295 -134 2191 -100 20415 -11032 65 -1170 131 -2326 163 -398 263 -596 99 -400 165 -68 499 -66 661 -11724 97 -1226 99 -166 233 -400 131 -662 165 -400 165 -166 299 -296 1159 -100 131 -98 427 -68 1643 -13934 101 -1294 65 -394 63 -1458 265 -166 265 -364 133 -434 101 -398 195 -264 1097 -166 265 -166 58013 -11978 131 -100 165 -666 199 -66 163 -166 65 -198 195 -592 1295 -134 597 -136 567 -100 1615 -100 17759 -12402 261 -692 +RAW_Data: 265 -730 327 -398 131 -300 199 -432 7947 -4772 233 -366 365 -166 133 -522 99 -164 65 -366 99 -100 395 -66 4423 -15068 197 -430 163 -398 65 -230 165 -228 97 -494 165 -134 197 -168 131 -168 65 -298 267 -132 1657 -98 99 -66 165 -100 7687 -12010 197 -132 163 -198 527 -130 293 -396 297 -360 163 -264 95 -164 161 -694 427 -262 129 -100 885 -100 131 -66 461 -9862 97 -5002 497 -594 197 -66 229 -596 65 -132 195 -262 99 -132 63 -166 65 -230 431 -100 165 -168 461 -164 13159 -12060 133 -64 129 -1316 131 -758 165 -430 165 -270 133 -564 995 -7536 99 -4746 165 -358 457 -132 165 -166 99 -100 397 -132 299 -66 197 -66 131 -392 129 -196 131 -328 459 -100 565 -66 165 -66 265 -100 1131 -100 9533 -12070 65 -400 163 -366 165 -530 197 -196 133 -68 233 -398 165 -66 99 -100 163 -132 791 -66 17359 -12434 131 -328 229 -558 195 -564 167 -132 133 -234 131 -100 65 -364 65 -496 65 -200 297 -66 1725 -12958 63 -68 65 -164 67 -132 63 -200 131 -166 165 -696 131 -166 67 -200 65 -168 1061 -12848 199 -166 2039 -1660 201 -5822 1017 -1126 299 -414 1015 -388 1061 -380 1055 -1086 327 -1118 339 -1084 351 -360 1051 -376 1083 -1070 343 -346 1105 -350 1069 -354 1087 -346 1063 -362 1075 -372 1067 -334 1097 -5694 1083 -1074 351 -378 1057 -332 1101 -370 1065 -1050 379 -1050 379 -1066 363 -348 1073 -388 1047 -1076 351 -380 1053 -362 1071 -372 1067 -352 1081 -352 1069 -350 1091 -346 1087 -5682 1095 -1044 381 -342 1097 -326 1111 -346 1061 -1064 379 -1066 381 -1032 379 -360 1065 -364 1067 -1066 375 -352 1081 -350 1069 -350 1089 -352 1081 -350 1069 -350 1097 -326 1081 -5698 1097 -1074 353 -354 1083 -326 1111 -344 1083 -1054 387 -1046 349 -1094 345 -350 1081 -354 1069 -1076 385 -350 1051 -354 1077 -386 1057 -334 1099 -370 1051 -354 1083 -356 1087 -5670 1107 -1072 353 -354 1081 -326 1111 -346 1085 -1054 389 -1050 353 -1056 377 -352 1083 -350 1071 -1080 381 -312 1099 -334 1107 -358 1079 -320 1091 -350 1095 -348 1081 -318 1113 -5688 1075 -1076 353 -354 1085 -324 1111 -346 1067 -1056 387 -1048 381 -1070 383 -314 1085 -352 1073 -1078 385 -348 1049 -354 1103 -356 1055 -334 1097 -372 1049 -354 1081 -354 1087 -5680 1109 -1038 383 -356 1085 -326 1107 -344 1065 -1066 361 -1076 371 -1054 389 -330 1069 -376 1069 -1054 389 -332 1073 -374 1069 -354 1079 -320 1097 -350 1093 -348 1083 -322 1111 -5680 1105 -1044 359 -356 1083 -354 1085 -348 1097 -1050 387 -1042 349 -1074 385 -350 1053 -352 1077 -1082 379 -314 1099 -336 1095 -346 1075 -374 1071 -354 1081 -320 1095 -348 1097 -5686 +RAW_Data: 1085 -1062 369 -346 1093 -322 1105 -350 1063 -1076 351 -1070 381 -1068 355 -358 1087 -354 1049 -1070 351 -384 1081 -318 1113 -344 1065 -348 1083 -352 1071 -388 1051 -352 1081 -5706 1065 -1064 361 -348 1087 -356 1079 -348 1091 -1072 381 -1020 375 -1068 361 -356 1081 -354 1085 -1042 381 -352 1079 -352 1101 -320 1093 -346 1087 -322 1117 -354 1057 -334 1099 -5702 1085 -1044 381 -340 1087 -352 1081 -352 1069 -1080 379 -1032 379 -1050 377 -346 1099 -332 1077 -1084 355 -334 1101 -334 1089 -350 1097 -320 1095 -348 1097 -326 1077 -376 1053 -5706 1073 -1088 367 -346 1063 -354 1071 -382 1067 -1076 347 -1068 381 -1064 347 -360 1085 -354 1047 -1072 385 -352 1087 -328 1073 -374 1051 -388 1045 -354 1093 -348 1085 -354 1079 -5668 1085 -1082 349 -348 1079 -374 1051 -382 1063 -1076 355 -1082 349 -1068 353 -364 1065 -362 1075 -1064 373 -346 1067 -362 1073 -382 1063 -352 1067 -380 1065 -326 1081 -376 1065 -5714 1075 -1072 345 -346 1099 -332 1097 -346 1075 -1062 373 -1066 347 -1076 373 -344 1069 -362 1073 -1072 383 -314 1113 -344 1063 -350 1079 -374 1065 -332 1099 -370 1065 -336 1099 -5694 1087 -1046 351 -368 1097 -348 1051 -354 1109 -1046 383 -1060 365 -1072 365 -352 1049 -352 1091 -1074 347 -384 1051 -352 1073 -384 1051 -350 1079 -354 1073 -380 1067 -326 1081 -5722 1065 -1078 355 -352 1081 -356 1085 -344 1083 -1050 385 -1050 353 -1092 345 -384 1051 -352 1069 -1078 381 -350 1051 -354 1069 -386 1057 -364 1073 -368 1071 -352 1049 -354 1093 -5710 1077 -1072 353 -354 1085 -326 1079 -376 1069 -1052 381 -1064 349 -1074 381 -346 1073 -368 1067 -1056 359 -350 1107 -348 1063 -352 1069 -382 1065 -326 1111 -346 1063 -364 1071 -5686 1099 -1072 355 -352 1083 -326 1111 -346 1065 -1086 353 -1050 383 -1068 383 -350 1047 -354 1071 -1076 381 -346 1069 -370 1065 -336 1095 -334 1099 -336 1099 -330 1073 -354 1081 -5710 1077 -1050 385 -348 1085 -322 1109 -356 1055 -1084 355 -1054 377 -1052 375 -346 1103 -350 1065 -1074 351 -342 1093 -346 1091 -322 1107 -356 1055 -364 1071 -372 1053 -354 1085 -5704 1077 -1050 383 -346 1073 -370 1065 -334 1097 -1060 371 -1062 347 -1080 373 -344 1069 -360 1075 -1076 351 -346 1109 -344 1063 -364 1069 -370 1065 -334 1099 -332 1097 -336 1097 -5690 1087 -1044 383 -338 1095 -348 1083 -320 1109 -1048 385 -1056 365 -1072 365 -356 1081 -320 1095 -1078 343 -346 1103 -330 1075 -352 1095 -350 1069 -350 1085 -352 1081 -350 1071 -5712 1075 -1042 385 -354 1085 -326 1079 -374 1051 -1084 351 -1064 383 -1068 347 -346 1099 -334 1103 -1054 347 -354 1099 -346 1083 -354 1081 -354 1053 -362 1071 -374 1065 -346 1067 -5726 1053 -1082 351 -360 1091 -344 1083 -350 +RAW_Data: 1081 -1074 353 -1074 351 -1088 325 -386 1051 -386 1047 -1074 353 -378 1051 -384 1047 -354 1073 -384 1049 -376 1065 -356 1073 -352 1087 -5680 1079 -1074 381 -350 1051 -354 1071 -386 1051 -1086 351 -1080 351 -1070 381 -350 1051 -352 1071 -1080 383 -348 1049 -354 1075 -384 1065 -324 1113 -346 1061 -350 1079 -374 1051 -5706 1077 -1086 333 -384 1047 -354 1093 -346 1065 -1078 353 -1094 345 -1082 351 -360 1089 -346 1063 -1074 353 -366 1065 -360 1075 -372 1067 -334 1101 -368 1051 -352 1085 -356 1051 -5734 1073 -1072 353 -354 1083 -328 1073 -376 1083 -1040 375 -1066 359 -1082 353 -334 1099 -368 1069 -1070 343 -346 1105 -354 1055 -362 1071 -370 1069 -352 1077 -352 1069 -350 1087 -5680 1079 -1086 367 -352 1049 -354 1097 -346 1063 -1078 349 -1092 345 -1098 347 -358 1065 -350 1079 -1064 373 -346 1067 -362 1081 -350 1065 -350 1095 -346 1067 -362 1075 -372 1049 -5706 1071 -1086 367 -352 1083 -320 1095 -348 1087 -1050 387 -1050 353 -1094 345 -346 1101 -332 1073 -1072 383 -346 1083 -346 1087 -352 1051 -354 1093 -346 1085 -346 1061 -354 1103 -5700 1069 -1050 393 -352 1063 -352 1069 -350 1097 -1044 383 -1068 379 -1030 379 -358 1065 -348 1073 -1068 377 -346 1099 -330 1073 -352 1093 -350 1073 -348 1087 -352 1081 -350 1069 -5710 1073 -1076 353 -354 1085 -324 1111 -344 1083 -1040 389 -1044 381 -1070 347 -350 1085 -352 1071 -1078 383 -352 1047 -352 1075 -352 1089 -350 1083 -352 1069 -350 1097 -326 1109 -5664 1113 -1040 383 -354 1085 -328 1075 -374 1051 -1084 351 -1066 351 -1096 379 -314 1099 -334 1097 -1052 379 -356 1069 -354 1079 -320 1097 -348 1093 -346 1083 -354 1077 -354 1055 -5714 1073 -1076 345 -350 1081 -354 1069 -382 1065 -1074 349 -1068 379 -1064 347 -360 1065 -364 1071 -1066 371 -344 1067 -360 1077 -354 1083 -354 1087 -346 1065 -364 1071 -368 1071 -5682 1075 -1084 367 -342 1067 -360 1075 -352 1097 -1040 353 -1074 385 -1058 363 -346 1075 -354 1081 -1076 351 -378 1057 -334 1101 -334 1103 -356 1079 -320 1091 -348 1093 -348 1085 -5678 1095 -1050 379 -356 1065 -334 1097 -370 1049 -1086 353 -1086 325 -1080 375 -352 1081 -350 1067 -1078 383 -350 1051 -352 1071 -386 1053 -334 1103 -336 1085 -354 1087 -324 1111 -5676 1079 -1078 351 -380 1055 -364 1073 -368 1051 -1082 349 -1064 351 -1092 347 -378 1073 -334 1099 -1050 381 -328 1095 -346 1083 -350 1081 -352 1069 -382 1049 -344 1095 -354 1077 -5682 1071 -1078 359 -352 1081 -356 1085 -346 1065 -1082 353 -1080 351 -1070 385 -348 1051 -354 1077 -1076 381 -314 1103 -334 1105 -354 1081 -318 1095 -348 1087 -354 1081 -320 1097 -5712 1077 -1074 319 -380 1067 -356 1087 -346 1065 -1080 349 -1092 345 -1082 351 -360 +RAW_Data: 1053 -378 1083 -1050 381 -346 1053 -376 1067 -364 1069 -370 1047 -386 1047 -356 1093 -344 1083 -5690 1079 -1060 371 -344 1067 -362 1073 -348 1095 -1076 353 -1074 353 -1054 359 -352 1101 -348 1085 -1046 361 -388 1043 -350 1093 -346 1081 -352 1081 -352 1067 -384 1055 -346 1085 -5678 1083 -1084 347 -344 1093 -348 1097 -326 1075 -1070 375 -1062 347 -1080 373 -344 1067 -362 1073 -1074 381 -314 1115 -344 1085 -356 1047 -354 1091 -348 1085 -354 1081 -320 1095 -5708 1075 -1074 353 -354 1083 -326 1109 -346 1065 -1054 387 -1050 351 -1096 381 -316 1083 -352 1073 -1080 381 -312 1101 -336 1105 -350 1063 -352 1067 -384 1065 -324 1111 -346 1065 -5704 1073 -1080 347 -350 1083 -352 1073 -350 1085 -1064 375 -1064 357 -1072 383 -314 1111 -344 1087 -1042 387 -330 1071 -374 1069 -352 1081 -350 1067 -350 1097 -348 1083 -322 1113 -5682 1075 -1076 353 -354 1083 -326 1117 -342 1085 -1054 351 -1062 383 -1070 377 -314 1103 -332 1105 -1042 381 -346 1083 -346 1065 -360 1071 -374 1069 -352 1079 -352 1067 -348 1089 -5682 1077 -1088 367 -354 1047 -354 1095 -346 1083 -1068 345 -1096 351 -1074 351 -346 1077 -372 1065 -1066 349 -386 1069 -346 1085 -356 1077 -354 1057 -364 1071 -370 1071 -352 1079 -5676 1073 -1074 381 -314 1117 -344 1063 -352 1081 -1076 357 -1080 355 -1052 359 -352 1095 -334 1097 -1064 371 -352 1083 -320 1095 -348 1085 -352 1081 -350 1069 -350 1093 -348 1085 -5676 1095 -1052 379 -356 1065 -346 1087 -356 1075 -1048 387 -1058 363 -1074 367 -356 1049 -354 1091 -1072 347 -346 1101 -332 1073 -384 1063 -350 1067 -350 1101 -326 1079 -376 1065 -5680 1107 -1074 343 -346 1067 -362 1083 -352 1085 -1078 349 -1070 345 -1066 379 -360 1065 -348 1079 -1062 375 -344 1069 -362 1075 -352 1081 -356 1085 -344 1063 -384 1047 -354 1073 -5708 1073 -1074 357 -354 1083 -356 1087 -344 1087 -1052 351 -1064 379 -1068 379 -314 1103 -334 1099 -1052 347 -386 1061 -348 1073 -374 1067 -352 1083 -350 1067 -350 1095 -348 1081 -5682 1085 -1054 383 -344 1087 -346 1067 -362 1069 -1064 373 -1062 347 -1078 373 -352 1081 -350 1065 -1078 383 -350 1051 -352 1071 -386 1051 -352 1081 -352 1069 -350 1095 -346 1087 -5678 1089 -1052 387 -330 1073 -374 1083 -354 1045 -1072 353 -1074 385 -1056 361 -350 1081 -352 1083 -1080 349 -376 1059 -346 1087 -356 1075 -354 1055 -362 1075 -370 1067 -352 1081 -5676 1075 -1076 387 -330 1071 -374 1065 -352 1079 -1072 355 -1082 353 -1054 361 -350 1095 -348 1073 -1068 373 -352 1081 -322 1097 -348 1085 -354 1079 -352 1069 -348 1093 -348 1083 -5676 1097 -1056 361 -348 1071 -354 1081 -354 1089 -1074 345 -1062 383 -1050 353 -380 1057 -364 1071 -1066 371 -354 1081 -318 +RAW_Data: 1099 -348 1085 -352 1079 -352 1071 -348 1087 -346 1095 -5678 1095 -1050 383 -328 1091 -348 1061 -386 1047 -1078 357 -1084 351 -1066 351 -366 1087 -348 1061 -1076 357 -354 1081 -354 1089 -346 1065 -352 1079 -354 1069 -386 1055 -334 1099 -5694 1085 -1044 383 -340 1087 -352 1081 -350 1065 -1080 383 -1052 351 -1082 349 -376 1059 -348 1083 -1046 395 -356 1049 -352 1091 -348 1085 -354 1083 -320 1093 -348 1087 -354 1081 -5670 1107 -1070 343 -348 1067 -384 1067 -354 1083 -1068 345 -1064 349 -1080 373 -350 1081 -352 1067 -1076 383 -348 1049 -354 1071 -386 1053 -364 1071 -368 1069 -352 1079 -350 1069 -5708 1075 -1074 351 -354 1083 -326 1111 -344 1067 -1052 385 -1050 381 -1070 347 -346 1101 -332 1073 -1072 383 -344 1085 -346 1067 -362 1069 -370 1053 -352 1085 -356 1085 -346 1067 -5682 1111 -1060 369 -344 1071 -360 1081 -354 1053 -1080 353 -1092 345 -1084 351 -360 1053 -376 1067 -1086 351 -330 1081 -376 1051 -382 1067 -352 1067 -380 1067 -326 1081 -374 1051 -5696 1109 -1058 369 -354 1079 -320 1097 -350 1085 -1054 387 -1048 353 -1060 377 -352 1085 -350 1069 -1078 383 -350 1049 -352 1071 -384 1065 -326 1081 -376 1067 -334 1101 -334 1101 -5692 1063 -1064 375 -346 1067 -362 1073 -380 1065 -1074 355 -1072 349 -1066 349 -366 1085 -350 1081 -1076 355 -350 1085 -326 1077 -376 1083 -352 1049 -354 1095 -346 1065 -380 1051 -5712 1083 -1052 389 -330 1073 -374 1053 -354 1083 -1074 351 -1074 385 -1052 343 -346 1107 -352 1065 -1072 351 -376 1065 -356 1081 -346 1065 -362 1069 -372 1067 -336 1097 -332 1099 -5678 1101 -1068 343 -350 1081 -352 1069 -384 1055 -1082 349 -1056 377 -1082 353 -330 1081 -374 1067 -1082 351 -330 1081 -376 1081 -352 1047 -354 1097 -348 1081 -352 1081 -352 1069 -5712 1077 -1038 383 -354 1083 -326 1079 -374 1055 -1074 383 -1064 347 -1066 381 -316 1099 -370 1051 -1082 353 -336 1097 -368 1053 -352 1081 -354 1089 -346 1063 -350 1081 -352 1071 -5714 1075 -1074 355 -350 1083 -358 1053 -376 1065 -1084 347 -1064 353 -1070 385 -346 1085 -352 1077 -1042 387 -346 1053 -354 1077 -386 1055 -364 1069 -370 1051 -388 1051 -354 1087 -5672 1081 -1074 351 -378 1065 -356 1083 -346 1065 -1078 347 -1092 345 -1066 359 -350 1107 -356 1045 -1068 383 -354 1083 -328 1075 -372 1053 -388 1051 -356 1087 -346 1065 -362 1071 -5702 1083 -1076 349 -376 1055 -346 1083 -356 1073 -1078 353 -1054 361 -1078 369 -352 1083 -352 1065 -1072 347 -346 1103 -332 1101 -346 1081 -354 1045 -386 1051 -362 1073 -370 1049 -5718 1075 -1048 365 -388 1047 -354 1091 -346 1085 -1040 387 -1050 351 -1096 349 -348 1083 -352 1073 -1076 379 -316 1101 -334 1097 -336 1099 -330 1071 -388 1047 -354 +RAW_Data: 1087 -348 1085 -5684 1075 -1084 367 -344 1067 -358 1073 -350 1063 -1074 353 -1108 349 -1068 353 -362 1067 -344 1097 -1050 363 -386 1047 -356 1087 -346 1085 -352 1079 -320 1097 -346 1083 -352 1081 -5676 1103 -1054 387 -330 1071 -372 1053 -354 1085 -1074 351 -1070 385 -1058 347 -362 1069 -350 1087 -1054 381 -346 1085 -346 1065 -364 1069 -370 1071 -354 1081 -320 1097 -348 1083 -5676 1079 -1090 367 -344 1067 -360 1073 -350 1095 -1074 351 -1076 349 -1068 353 -364 1081 -352 1081 -1074 321 -382 1051 -360 1075 -374 1051 -386 1049 -354 1091 -346 1085 -346 1067 -5706 1065 -1084 351 -360 1087 -346 1085 -356 1047 -1074 353 -1072 383 -1064 347 -362 1067 -364 1069 -1068 371 -352 1083 -320 1095 -350 1097 -346 1083 -322 1077 -388 1051 -350 1081 -5706 1051 -1084 387 -328 1073 -372 1049 -386 1049 -1076 353 -1072 383 -1056 363 -348 1071 -354 1085 -1076 351 -376 1057 -344 1087 -356 1073 -352 1081 -350 1053 -376 1051 -384 1049 -5710 1083 -1076 351 -348 1073 -372 1063 -334 1099 -1062 371 -1052 389 -1050 353 -364 1087 -354 1049 -1072 353 -382 1055 -362 1075 -370 1051 -352 1095 -350 1067 -348 1099 -326 1077 -5702 1077 -1076 353 -378 1053 -362 1071 -370 1051 -1084 349 -1066 353 -1096 345 -382 1049 -354 1069 -1080 379 -346 1069 -368 1041 -386 1047 -354 1093 -346 1085 -350 1083 -350 1067 -5708 1075 -1074 353 -352 1083 -326 1109 -344 1083 -1050 387 -1052 355 -1054 377 -352 1085 -350 1069 -1076 377 -314 1103 -332 1101 -336 1097 -330 1073 -354 1085 -354 1089 -346 1083 -5688 1073 -1084 331 -386 1047 -356 1087 -346 1061 -1082 383 -1050 353 -1070 385 -346 1051 -354 1105 -1046 385 -346 1085 -322 1073 -386 1049 -382 1047 -354 1069 -386 1053 -362 1071 -5692 1079 -1076 351 -378 1055 -364 1071 -370 1073 -1040 373 -1070 357 -1084 353 -334 1097 -334 1105 -1056 387 -330 1071 -372 1069 -354 1081 -318 1095 -348 1085 -352 1077 -352 1069 -5708 1075 -1076 353 -352 1087 -326 1081 -372 1067 -1084 351 -1050 353 -1098 345 -344 1101 -332 1101 -1054 365 -348 1079 -354 1087 -326 1081 -374 1049 -382 1063 -352 1067 -384 1055 -5708 1071 -1076 345 -350 1081 -352 1069 -386 1059 -1054 361 -1076 371 -1054 387 -330 1073 -372 1069 -1054 383 -344 1085 -346 1065 -364 1073 -368 1065 -334 1099 -334 1073 -382 1061 -5700 1067 -1064 383 -328 1093 -348 1083 -354 1083 -1040 383 -1046 387 -1056 361 -348 1073 -354 1085 -1074 351 -378 1055 -332 1099 -338 1089 -354 1085 -324 1115 -344 1085 -350 1063 -5704 1069 -1068 349 -360 1091 -346 1067 -362 1075 -1062 373 -1068 333 -1102 365 -354 1049 -356 1087 -1074 345 -384 1047 -352 1069 -386 1053 -364 1071 -370 1051 -352 1085 -354 1085 -5678 1081 -1072 351 -378 +RAW_Data: 1055 -362 1069 -372 1069 -1054 387 -1048 353 -1092 345 -346 1101 -332 1073 -1074 383 -312 1113 -346 1063 -364 1071 -370 1073 -354 1081 -320 1091 -348 1085 -5680 1075 -1088 367 -344 1069 -360 1075 -350 1063 -1074 353 -1076 385 -1054 365 -348 1071 -354 1083 -1074 351 -374 1067 -326 1077 -376 1085 -352 1049 -354 1091 -346 1063 -380 1051 -5714 1085 -1038 387 -328 1081 -376 1053 -382 1063 -1072 355 -1076 349 -1064 349 -368 1097 -326 1073 -1070 377 -344 1099 -330 1085 -354 1087 -326 1079 -374 1069 -350 1081 -352 1071 -5700 1067 -1076 357 -350 1095 -320 1095 -348 1065 -1078 349 -1092 347 -1066 379 -358 1069 -332 1101 -1062 371 -350 1085 -320 1095 -348 1097 -346 1049 -356 1075 -386 1057 -364 1069 -5692 1077 -1070 351 -380 1051 -350 1075 -376 1065 -1052 385 -1048 383 -1068 381 -316 1083 -352 1073 -1080 377 -314 1103 -334 1099 -336 1099 -328 1075 -350 1095 -352 1065 -350 1095 -5674 1079 -1076 379 -346 1071 -368 1067 -336 1065 -1088 369 -1050 387 -1048 353 -366 1063 -382 1045 -1078 361 -386 1047 -354 1089 -346 1063 -384 1045 -354 1071 -386 1057 -332 1101 -5698 1081 -1044 383 -340 1085 -352 1079 -352 1067 -1080 379 -1030 379 -1050 377 -344 1099 -330 1073 -1074 381 -346 1085 -346 1085 -356 1047 -354 1091 -348 1063 -362 1075 -374 1063 -5678 1103 -1042 377 -352 1081 -352 1073 -348 1095 -1056 347 -1080 373 -1054 383 -314 1113 -346 1065 -1082 355 -358 1067 -352 1079 -352 1067 -350 1093 -346 1083 -354 1075 -354 1087 -5678 1079 -1074 345 -348 1099 -332 1101 -336 1067 -1090 365 -1052 387 -1048 355 -362 1067 -364 1073 -1066 371 -350 1081 -352 1065 -348 1069 -362 1075 -372 1067 -334 1099 -332 1099 -848 153 -153886 131 -896 231 -464 231 -428 97 -166 99 -966 233 -200 101 -66 199 -102 265 -266 299 -98 131 -100 65 -232 131 -100 44699 -12320 65 -132 131 -300 231 -100 167 -266 197 -68 297 -330 131 -496 131 -400 131 -166 633 -66 431 -230 589 -166 10885 -11354 329 -792 99 -296 129 -166 97 -98 65 -132 229 -294 297 -98 561 -166 231 -98 825 -166 57693 -12116 131 -98 295 -424 163 -398 131 -232 97 -130 65 -66 97 -66 165 -460 529 -66 197 -132 393 -198 267 -168 43035 -10678 99 -1716 97 -498 65 -298 67 -2020 331 -166 957 -66 199 -100 529 -12206 199 -796 67 -66 131 -132 129 -398 197 -230 131 -66 227 -196 65 -526 197 -132 589 -132 597 -100 327 -66 8489 -11212 133 -630 131 -100 133 -166 99 -994 99 -198 333 -1626 329 -130 6045 -13890 65 -434 463 -264 295 -266 1385 -132 461 -134 163951 -12284 131 -858 233 -532 199 -402 97 -266 133 -496 431 -66 1161 -66 493 -68 21495 -5856 +RAW_Data: 95 -560 65 -2896 199 -762 97 -100 65 -430 131 -896 199 -234 101 -800 231 -332 99 -334 133 -132 265 -268 1425 -66 33043 -12400 99 -666 131 -234 67 -232 65 -498 65 -266 65 -498 165 -102 165 -200 2189 -11534 133 -266 197 -232 97 -166 165 -100 99 -98 99 -500 97 -332 99 -200 12547 -12014 65 -232 99 -600 131 -330 299 -332 625 -66 1243 -134 7929 -12344 97 -762 131 -860 165 -298 295 -298 231 -266 97 -134 397 -132 2891 -68 299 -16210 67 -266 133 -234 101 -268 163 -466 97 -232 65 -198 67 -132 67 -100 263 -98 437 -302 133 -66 165 -168 24439 -10722 131 -1534 229 -1318 99 -98 195 -2100 597 -100 733 -134 28455 -10484 231 -2030 67 -100 101 -132 163 -166 265 -854 361 -166 531 -134 233 -100 495 -164 25733 -12966 65 -1880 65 -132 163 -1086 799 -68 16865 -12956 295 -330 163 -232 233 -166 97 -1860 99 -68 333 -66 1613 -98 55627 -12998 97 -266 263 -1594 131 -894 167 -134 231 -332 131 -66 399 -98 99 -66 697 -4972 65 -3944 65 -132 459 -328 197 -100 1557 -98 23673 -12982 333 -928 133 -232 433 -198 131 -360 99 -64 497 -100 463 -98 397 -132 197 -68 765 -13710 65 -166 99 -1684 131 -632 691 -230 263 -66 359 -132 2073 -64 6059 -12430 97 -462 65 -328 65 -792 229 -824 97 -892 529 -166 163 -168 165 -698 165 -166 431 -130 20385 -12774 97 -98 65 -1384 131 -198 131 -622 131 -598 99 -164 129 -98 193 -98 195 -98 263 -98 459 -132 131 -66 73855 -13048 65 -2554 97 -166 761 -68 761 -366 15305 -12094 165 -764 963 -68 65 -66 163 -66 233 -102 333 -100 965 -66 40087 -10292 165 -1724 65 -1754 165 -266 463 -396 267 -432 795 -132 297 -66 267 -68 261 -134 36583 -10588 65 -2738 97 -1184 259 -298 131 -294 395 -98 819 -132 689 -166 7999 -11970 65 -468 63 -1892 429 -262 263 -200 199 -632 265 -66 229 -66 1149 -68 2355 -15782 233 -198 67 -166 295 -818 229 -164 1059 -68 265 -68 1789 -10374 131 -1904 65 -196 229 -230 197 -360 229 -164 129 -1120 963 -166 231 -200 199 -230 333 -10180 67 -966 133 -1264 65 -798 99 -432 65 -898 65 -134 7681 -12052 131 -928 129 -624 99 -526 65 -432 65 -532 65 -596 199 -100 265 -296 895 -100 229 -98 589 -15506 97 -266 65 -434 65 -1520 199 -98 265 -894 293 -198 427 -198 97 -132 459 -64 2145 -66 9075 -7224 99 -66 65 -1060 65 -200 99 -932 167 -894 65 -528 165 -102 927 -100 597 -232 99 -66 97 -98 461 -196 261 -98 197 -264 2909 -134 11635 -804 99 -240 143 -244 369 -5700 1077 -1074 353 -354 1083 -324 +RAW_Data: 1117 -346 1063 -1086 353 -1050 381 -1070 383 -348 1049 -354 1073 -1082 383 -350 1047 -354 1077 -386 1057 -334 1101 -336 1105 -354 1081 -320 1093 -5710 1073 -1076 351 -354 1085 -324 1111 -344 1065 -1084 351 -1082 349 -1068 385 -348 1053 -354 1075 -1078 385 -348 1051 -354 1075 -382 1065 -354 1085 -346 1065 -364 1069 -370 1069 -5686 1077 -1086 367 -354 1047 -354 1095 -346 1095 -1054 363 -1072 369 -1050 387 -328 1077 -374 1051 -1084 349 -348 1081 -376 1081 -348 1063 -352 1073 -380 1067 -356 1053 -376 1083 -5684 1073 -1086 365 -356 1049 -354 1089 -348 1085 -1066 385 -1046 351 -1074 385 -348 1083 -320 1109 -1048 381 -346 1071 -336 1097 -336 1097 -334 1085 -352 1093 -320 1097 -348 1095 -5680 1089 -1064 371 -352 1081 -352 1067 -348 1067 -1078 349 -1094 347 -1096 349 -358 1067 -350 1081 -1076 355 -352 1081 -356 1055 -378 1065 -350 1079 -352 1069 -384 1053 -348 1079 -5702 1099 -1072 353 -350 1065 -354 1087 -348 1097 -1046 347 -1096 377 -1034 377 -360 1085 -356 1049 -1074 351 -382 1051 -362 1071 -374 1051 -388 1049 -352 1093 -346 1061 -384 1049 -5708 1087 -1068 349 -362 1089 -344 1061 -382 1051 -1076 359 -1080 351 -1088 355 -358 1069 -352 1081 -1078 353 -350 1087 -326 1079 -376 1065 -350 1079 -354 1069 -384 1059 -332 1101 -5700 1087 -1044 353 -368 1095 -348 1085 -320 1111 -1044 381 -1064 347 -1090 345 -344 1101 -332 1073 -1074 383 -344 1087 -344 1067 -352 1083 -352 1071 -384 1051 -352 1083 -352 1071 -5712 1077 -1076 355 -350 1087 -326 1079 -374 1053 -1070 375 -1072 357 -1080 353 -334 1103 -334 1073 -1084 349 -378 1051 -376 1067 -364 1073 -368 1065 -336 1101 -332 1083 -352 1083 -5712 1075 -1044 383 -348 1069 -372 1051 -354 1083 -1076 351 -1074 383 -1052 385 -326 1089 -346 1065 -1066 381 -340 1099 -326 1077 -374 1067 -352 1083 -350 1069 -350 1087 -352 1081 -5676 1085 -1074 387 -330 1071 -372 1069 -352 1079 -1076 355 -1078 351 -1084 347 -360 1067 -348 1081 -1074 357 -352 1081 -356 1085 -344 1065 -384 1049 -354 1071 -384 1059 -346 1085 -5680 1087 -1084 343 -348 1103 -352 1065 -354 1087 -1070 345 -1086 351 -1082 351 -340 1097 -346 1087 -1046 361 -352 1083 -354 1089 -346 1067 -350 1081 -354 1071 -386 1053 -346 1097 -5676 1085 -1084 349 -360 1085 -344 1081 -348 1065 -1076 357 -1080 351 -1086 325 -388 1065 -366 1067 -1064 371 -352 1081 -352 1065 -382 1049 -348 1083 -352 1071 -382 1049 -384 1047 -5708 1065 -1062 377 -350 1083 -354 1085 -326 1077 -1070 377 -1052 381 -1066 351 -378 1057 -332 1103 -1060 373 -344 1069 -362 1073 -350 1095 -350 1065 -348 1095 -350 1085 -344 1065 -5704 1079 -1072 347 -382 1049 -354 1075 -382 1051 -1082 351 -1082 351 -1072 +RAW_Data: 381 -348 1053 -354 1071 -1074 381 -348 1071 -370 1049 -386 1047 -356 1091 -346 1083 -346 1067 -354 1071 -5700 1103 -1046 361 -386 1047 -354 1087 -346 1085 -1050 387 -1050 351 -1098 345 -350 1083 -352 1073 -1078 379 -316 1103 -332 1101 -336 1097 -330 1073 -380 1065 -352 1069 -384 1061 -5684 1089 -1064 369 -346 1093 -320 1109 -352 1067 -1076 349 -1070 381 -1032 379 -360 1065 -362 1071 -1064 373 -342 1099 -330 1073 -350 1093 -352 1067 -350 1087 -350 1081 -352 1071 -5710 1077 -1074 355 -354 1081 -326 1113 -346 1083 -1054 381 -1034 383 -1066 379 -316 1099 -334 1097 -1052 379 -358 1067 -350 1081 -352 1067 -386 1055 -334 1101 -334 1107 -322 1085 -5708 1077 -1050 387 -348 1087 -320 1111 -356 1055 -1086 355 -1050 375 -1054 387 -330 1075 -376 1067 -1068 385 -326 1087 -346 1063 -382 1047 -354 1105 -354 1057 -332 1097 -372 1049 -5716 1071 -1080 365 -356 1047 -354 1095 -348 1085 -1070 353 -1050 383 -1070 379 -314 1101 -334 1099 -1052 377 -356 1071 -354 1081 -320 1097 -348 1087 -346 1095 -322 1105 -352 1063 -5702 1075 -1076 351 -346 1109 -342 1087 -322 1081 -1074 351 -1076 383 -1062 347 -362 1065 -364 1071 -1064 373 -344 1099 -330 1077 -350 1097 -320 1097 -348 1095 -326 1079 -376 1063 -5712 1071 -1074 347 -346 1101 -332 1083 -350 1097 -1040 385 -1044 387 -1054 363 -348 1085 -354 1087 -1046 351 -370 1085 -346 1097 -320 1107 -350 1063 -350 1087 -346 1065 -360 1075 -5704 1077 -1076 351 -378 1055 -362 1071 -370 1049 -1084 351 -1084 355 -1054 375 -350 1083 -352 1067 -1076 379 -346 1075 -332 1101 -336 1065 -362 1073 -382 1065 -352 1069 -384 1049 -5682 1077 -1090 369 -354 1049 -354 1093 -348 1087 -1070 347 -1062 383 -1042 385 -346 1073 -372 1051 -1084 351 -346 1079 -374 1083 -354 1051 -354 1091 -348 1089 -354 1047 -354 1097 -5702 1075 -1076 353 -352 1085 -326 1077 -374 1053 -1082 385 -1050 353 -1092 345 -348 1083 -352 1069 -1076 383 -346 1051 -354 1073 -384 1051 -374 1067 -358 1073 -350 1069 -354 1087 -5706 1075 -1042 385 -352 1085 -326 1077 -378 1067 -1050 387 -1048 383 -1064 381 -350 1049 -354 1069 -1080 383 -350 1049 -354 1073 -388 1055 -334 1101 -336 1107 -354 1047 -354 1093 -5674 1109 -1072 353 -350 1083 -348 1051 -376 1065 -1082 351 -1082 351 -1072 379 -348 1073 -332 1073 -1072 381 -346 1085 -344 1065 -362 1073 -372 1047 -386 1047 -354 1089 -346 1083 -5686 1075 -1092 367 -354 1049 -354 1093 -348 1065 -1066 383 -1070 347 -1062 379 -358 1063 -348 1079 -1066 375 -344 1067 -362 1073 -350 1097 -350 1071 -348 1095 -346 1083 -320 1107 -5668 1107 -1078 329 -388 1047 -354 1093 -344 1065 -1082 343 -1098 353 -1080 349 -346 1077 -370 1067 -1052 347 -390 +RAW_Data: 1067 -364 1069 -366 1051 -386 1051 -356 1053 -378 1085 -346 1067 -5712 1053 -1074 381 -346 1083 -344 1063 -350 1077 -1068 377 -1066 383 -1050 351 -378 1057 -364 1073 -1066 371 -346 1063 -354 1071 -386 1053 -346 1103 -332 1071 -382 1063 -352 1065 -5716 1077 -1074 353 -352 1083 -326 1077 -376 1051 -1084 381 -1066 351 -1066 379 -348 1069 -368 1067 -1066 349 -362 1067 -362 1071 -370 1067 -334 1099 -366 1065 -336 1067 -364 1073 -5714 1075 -1050 361 -388 1045 -350 1091 -348 1085 -1054 381 -1066 349 -1070 381 -346 1071 -368 1065 -1052 379 -358 1067 -334 1097 -334 1085 -352 1085 -354 1087 -346 1083 -354 1051 -5706 1087 -1078 349 -348 1075 -372 1051 -382 1067 -1072 353 -1076 353 -1056 361 -350 1083 -352 1083 -1076 351 -376 1055 -364 1071 -370 1049 -352 1083 -356 1087 -346 1083 -352 1049 -5702 1087 -1052 389 -346 1087 -322 1111 -354 1057 -1082 355 -1054 375 -1064 347 -354 1105 -354 1081 -1040 383 -352 1085 -326 1075 -374 1051 -354 1083 -354 1089 -346 1085 -348 1065 -5710 1087 -1062 345 -352 1071 -382 1061 -352 1069 -1078 383 -1052 351 -1082 349 -380 1055 -334 1099 -1062 371 -354 1083 -318 1097 -348 1083 -352 1081 -352 1065 -350 1087 -352 1077 -5710 1069 -1054 389 -328 1075 -376 1049 -356 1079 -1076 351 -1074 385 -1058 365 -344 1073 -352 1097 -1036 383 -350 1063 -354 1087 -348 1097 -328 1071 -376 1063 -332 1101 -336 1101 -5676 1103 -1038 379 -344 1099 -332 1077 -354 1085 -1076 349 -1070 385 -1052 353 -360 1087 -344 1085 -1040 387 -328 1081 -374 1051 -386 1047 -354 1089 -346 1065 -362 1077 -372 1065 -5710 1075 -1042 377 -350 1083 -352 1069 -350 1095 -1050 373 -1064 357 -1074 353 -364 1073 -372 1053 -1074 351 -346 1081 -376 1069 -332 1099 -334 1087 -352 1085 -354 1085 -346 1085 -5682 1075 -1088 329 -388 1049 -354 1089 -346 1065 -1070 353 -1096 345 -1084 351 -362 1085 -344 1067 -1082 355 -358 1083 -354 1047 -354 1093 -346 1065 -360 1073 -372 1081 -354 1045 -5708 1073 -1086 353 -346 1075 -374 1055 -352 1095 -1074 353 -1074 351 -1062 379 -340 1095 -326 1079 -1070 373 -344 1099 -330 1083 -356 1083 -324 1113 -344 1067 -352 1081 -352 1073 -5706 1073 -1076 355 -354 1085 -326 1081 -374 1067 -1054 389 -1048 353 -1096 345 -350 1081 -352 1071 -1080 383 -348 1047 -354 1073 -386 1057 -334 1099 -334 1077 -384 1063 -350 1067 -5704 1071 -1076 357 -354 1081 -356 1083 -346 1067 -1086 353 -1048 383 -1070 383 -314 1081 -354 1071 -1078 385 -348 1047 -354 1107 -354 1057 -334 1099 -334 1085 -354 1083 -354 1085 -5670 1111 -1038 383 -354 1085 -328 1071 -376 1053 -1088 353 -1086 355 -1048 375 -352 1081 -350 1069 -1078 381 -350 1049 -352 1071 -386 1055 -334 1097 -336 +RAW_Data: 1087 -356 1081 -356 1085 -5672 1077 -1074 353 -382 1053 -364 1073 -372 1073 -1054 389 -1050 355 -1054 377 -344 1099 -330 1073 -1076 387 -330 1069 -372 1067 -344 1099 -330 1073 -354 1081 -354 1087 -348 1063 -5718 1081 -1060 369 -342 1067 -360 1073 -354 1083 -1076 349 -1068 385 -1052 353 -360 1085 -346 1087 -1052 389 -330 1069 -372 1051 -356 1083 -354 1087 -346 1089 -348 1065 -352 1069 -5700 1101 -1046 359 -354 1083 -354 1087 -348 1063 -1082 353 -1054 377 -1062 379 -356 1067 -334 1101 -1060 369 -344 1067 -360 1083 -356 1087 -328 1075 -374 1067 -352 1081 -350 1071 -5706 1075 -1076 351 -354 1085 -324 1111 -344 1085 -1040 389 -1048 353 -1094 345 -350 1081 -352 1071 -1076 379 -316 1097 -336 1097 -336 1097 -334 1095 -346 1073 -372 1051 -354 1079 -5692 1085 -1084 351 -346 1075 -374 1051 -354 1083 -1074 351 -1072 385 -1058 381 -328 1073 -348 1085 -1054 389 -328 1075 -376 1067 -350 1081 -352 1069 -350 1085 -352 1083 -350 1071 -5710 1077 -1042 383 -352 1089 -326 1073 -376 1065 -1064 375 -1072 365 -1038 373 -348 1071 -386 1059 -1050 379 -358 1087 -354 1047 -354 1089 -348 1085 -354 1081 -320 1095 -348 1087 -5686 1079 -1084 365 -356 1049 -354 1093 -346 1085 -1052 387 -1048 355 -1090 347 -350 1083 -352 1071 -1076 379 -314 1099 -334 1097 -336 1097 -332 1099 -336 1095 -332 1075 -354 1083 -5708 1051 -1080 383 -348 1049 -354 1105 -354 1055 -1084 355 -1052 375 -1084 351 -330 1079 -376 1051 -1074 387 -328 1075 -376 1067 -350 1079 -352 1069 -380 1067 -326 1081 -374 1051 -5714 1075 -1050 395 -356 1047 -354 1091 -348 1095 -1052 375 -1064 353 -1076 351 -346 1077 -372 1065 -1050 379 -360 1065 -362 1069 -370 1065 -334 1099 -332 1097 -336 1097 -332 1083 -5682 1101 -1046 395 -356 1047 -354 1089 -346 1097 -1050 385 -1046 351 -1074 385 -348 1051 -354 1105 -1042 385 -350 1051 -354 1073 -384 1067 -326 1113 -344 1063 -364 1069 -370 1065 -5690 1077 -1074 383 -348 1047 -354 1075 -386 1057 -1054 361 -1076 373 -1052 387 -330 1073 -374 1069 -1054 387 -330 1075 -374 1067 -352 1081 -352 1067 -350 1085 -352 1079 -352 1069 -5712 1075 -1076 351 -354 1085 -326 1109 -344 1083 -1054 387 -1052 355 -1054 375 -352 1085 -318 1097 -1076 379 -314 1099 -334 1103 -356 1079 -320 1091 -348 1085 -352 1081 -350 1067 -5708 1075 -1076 355 -350 1085 -326 1079 -374 1053 -1084 351 -1084 353 -1090 343 -350 1079 -354 1069 -1080 379 -346 1073 -332 1073 -382 1065 -352 1067 -380 1067 -326 1081 -374 1069 -5680 1103 -1070 345 -346 1099 -330 1073 -388 1047 -1070 351 -1078 385 -1058 363 -346 1081 -354 1087 -1046 351 -368 1095 -348 1051 -354 1077 -384 1065 -326 1113 -344 1085 -350 1061 -5708 1053 -1084 +RAW_Data: 351 -364 1073 -372 1069 -352 1083 -1040 353 -1110 353 -1056 361 -348 1081 -354 1083 -1078 351 -376 1059 -346 1055 -354 1079 -386 1057 -364 1071 -368 1037 -386 1047 -5706 1073 -1084 355 -334 1101 -336 1083 -354 1083 -1074 351 -1072 383 -1050 351 -360 1087 -346 1087 -1038 383 -344 1087 -346 1063 -362 1077 -370 1051 -354 1085 -356 1087 -344 1065 -5704 1081 -1074 345 -384 1047 -354 1069 -384 1057 -1086 325 -1078 373 -1052 385 -330 1079 -374 1049 -1072 381 -346 1085 -344 1067 -362 1073 -370 1067 -334 1101 -332 1071 -386 1049 -5712 1077 -1044 383 -346 1075 -370 1065 -348 1085 -1048 361 -1072 389 -1048 353 -364 1085 -354 1079 -1042 385 -354 1087 -328 1073 -374 1071 -352 1081 -320 1099 -348 1085 -352 1083 -5668 1087 -1084 353 -364 1069 -368 1053 -380 1067 -1072 353 -1078 349 -1068 355 -362 1067 -348 1083 -1074 357 -350 1085 -356 1081 -344 1083 -354 1047 -354 1093 -348 1083 -352 1079 -5704 1073 -1052 387 -330 1077 -372 1051 -352 1085 -1076 349 -1072 379 -1070 323 -388 1063 -348 1077 -1064 375 -352 1079 -352 1065 -384 1049 -352 1083 -352 1069 -350 1085 -352 1083 -5676 1085 -1080 351 -366 1071 -366 1053 -352 1085 -1076 351 -1070 379 -1064 349 -360 1065 -364 1073 -1064 369 -346 1071 -358 1073 -350 1065 -354 1091 -380 1051 -348 1083 -352 1069 -5712 1077 -1074 353 -352 1085 -326 1075 -374 1065 -1066 361 -1074 371 -1056 387 -330 1071 -372 1051 -1086 353 -336 1095 -370 1069 -354 1079 -320 1095 -348 1097 -350 1051 -354 1077 -5708 1075 -1078 355 -352 1085 -326 1081 -374 1051 -1082 349 -1100 321 -1098 345 -348 1101 -332 1099 -1072 325 -388 1065 -334 1099 -368 1051 -352 1083 -356 1053 -378 1083 -352 1047 -5710 1073 -1072 383 -314 1113 -344 1067 -364 1067 -1068 371 -1054 389 -1050 355 -362 1087 -354 1049 -1074 351 -382 1053 -362 1077 -370 1051 -352 1095 -352 1065 -348 1097 -324 1079 -5690 1101 -1074 355 -350 1081 -350 1085 -346 1081 -1050 387 -1048 355 -1094 345 -350 1083 -352 1069 -1074 379 -346 1073 -366 1067 -346 1085 -354 1073 -352 1055 -360 1075 -374 1051 -5718 1071 -1048 395 -356 1047 -354 1091 -346 1085 -1052 383 -1066 349 -1070 381 -314 1101 -334 1085 -1074 351 -346 1081 -376 1067 -332 1099 -334 1099 -336 1101 -330 1085 -350 1065 -5702 1075 -1072 381 -348 1051 -374 1069 -366 1067 -1064 371 -1070 351 -1080 351 -376 1047 -382 1047 -1078 357 -352 1083 -358 1053 -374 1051 -386 1049 -354 1093 -344 1065 -350 1079 -5720 1067 -1076 355 -352 1085 -326 1077 -376 1053 -1086 353 -1084 355 -1056 375 -344 1093 -322 1105 -1074 351 -348 1073 -372 1065 -334 1099 -332 1101 -346 1083 -352 1079 -354 1057 -5706 1077 -1074 347 -348 1081 -354 1071 -384 1057 -1052 +RAW_Data: 361 -1076 373 -1050 387 -330 1075 -374 1049 -1086 387 -330 1073 -370 1053 -352 1083 -354 1085 -346 1065 -362 1071 -370 1065 -5682 1105 -1036 377 -346 1099 -330 1085 -354 1085 -1046 353 -1096 347 -1086 351 -360 1085 -344 1085 -1052 381 -314 1113 -344 1083 -354 1047 -354 1093 -348 1067 -362 1073 -374 1049 -5716 1071 -1052 363 -388 1047 -354 1091 -346 1065 -1080 347 -1090 345 -1064 379 -358 1069 -334 1097 -1062 369 -352 1083 -320 1099 -348 1065 -362 1075 -374 1067 -334 1099 -332 1073 -5720 1073 -1048 395 -356 1049 -356 1089 -346 1065 -1084 343 -1072 361 -1084 355 -334 1101 -336 1099 -1058 363 -344 1085 -354 1089 -326 1075 -376 1051 -356 1081 -356 1085 -346 1065 -5688 1111 -1058 367 -348 1065 -352 1069 -384 1067 -1042 379 -1070 383 -1052 351 -330 1115 -346 1087 -1056 351 -346 1077 -376 1083 -352 1061 -352 1067 -350 1097 -326 1109 -346 1083 -5686 1075 -1048 397 -356 1047 -354 1091 -346 1085 -1054 389 -1048 353 -1058 377 -352 1085 -352 1069 -1074 381 -350 1051 -352 1069 -386 1055 -366 1069 -368 1051 -354 1083 -356 1083 -5672 1081 -1074 351 -382 1055 -364 1069 -372 1053 -1086 355 -1054 359 -1076 373 -352 1079 -350 1065 -1078 383 -316 1083 -354 1073 -352 1085 -352 1083 -350 1071 -348 1099 -328 1075 -5694 1099 -1072 351 -354 1083 -326 1111 -342 1085 -1054 353 -1062 381 -1064 379 -314 1101 -334 1099 -1058 361 -348 1081 -354 1081 -348 1087 -346 1085 -356 1049 -354 1091 -348 1065 -5710 1073 -1078 347 -346 1101 -332 1073 -350 1093 -1074 355 -1050 385 -1062 347 -366 1099 -326 1077 -1066 373 -352 1083 -320 1097 -350 1097 -326 1075 -376 1065 -332 1097 -370 1065 -5684 1101 -1036 377 -344 1101 -330 1073 -356 1079 -1074 349 -1074 385 -1056 365 -344 1081 -354 1085 -1042 379 -340 1095 -348 1085 -320 1109 -356 1055 -332 1101 -334 1107 -354 1049 -5704 1087 -1048 389 -350 1081 -320 1111 -356 1055 -1052 389 -1048 377 -1056 391 -328 1073 -374 1057 -1084 353 -344 1077 -372 1053 -356 1083 -356 1085 -346 1063 -352 1081 -352 1073 -5712 1073 -1074 353 -356 1079 -318 1117 -346 1065 -1056 389 -1048 381 -1066 383 -314 1081 -352 1073 -1080 385 -316 1085 -352 1071 -352 1097 -326 1109 -344 1071 -354 1081 -320 1099 -5696 1069 -1076 355 -356 1083 -324 1113 -346 1063 -1078 347 -1090 379 -1032 377 -356 1069 -354 1081 -1040 387 -354 1087 -326 1075 -376 1069 -342 1097 -330 1077 -356 1083 -324 1115 -5678 1081 -1076 349 -346 1089 -334 1095 -336 1107 -1058 355 -1054 389 -1046 377 -354 1081 -320 1097 -1078 379 -314 1097 -332 1099 -338 1093 -332 1073 -354 1091 -350 1069 -350 1095 -5674 1111 -1044 381 -314 1099 -336 1097 -336 1095 -1064 369 -1038 389 -1050 381 -338 1093 -350 +RAW_Data: 1081 -1044 395 -322 1081 -354 1087 -348 1085 -356 1083 -320 1093 -348 1089 -356 1049 -5706 1077 -1052 387 -350 1087 -320 1113 -318 1097 -1042 379 -1066 381 -1030 379 -362 1063 -364 1069 -1068 373 -356 1081 -320 1095 -350 1087 -352 1063 -352 1071 -350 1099 -326 1111 -5670 1081 -1076 349 -380 1057 -334 1097 -334 1089 -1054 389 -1056 361 -1072 373 -354 1083 -320 1091 -1074 381 -318 1079 -352 1071 -352 1091 -348 1083 -322 1113 -358 1053 -332 1133 -810 65 -262 181 -142572 65 -98 199 -100 133 -300 99 -166 99 -232 99 -134 333 -166 231 -330 99 -230 493 -100 361 -66 525 -12248 131 -268 133 -268 131 -400 229 -726 97 -494 265 -402 65 -664 363 -134 365 -166 131 -262 395 -98 795 -98 68827 -6766 99 -1626 99 -66 65 -700 97 -864 565 -464 99 -98 133 -230 197 -166 591 -98 5135 -12244 97 -2452 65 -734 165 -198 233 -266 631 -198 12423 -12322 165 -796 67 -100 99 -98 67 -862 133 -728 263 -66 361 -166 129 -100 395 -166 625 -232 131 -66 8113 -12068 229 -132 65 -1394 99 -134 65 -300 165 -134 65 -102 99 -166 133 -766 297 -98 199 -102 1067 -166 859 -98 25307 -13210 65 -1764 97 -264 163 -496 133 -132 65 -66 427 -98 721 -68 531 -134 1591 -100 54955 -11122 65 -726 65 -368 65 -492 99 -428 233 -164 99 -266 131 -498 197 -430 295 -66 2793 -130 11307 -12324 97 -132 193 -1216 65 -1286 131 -594 399 -100 431 -266 495 -100 165 -132 63531 -12252 99 -166 165 -596 99 -496 99 -298 97 -368 65 -134 131 -198 165 -434 99 -266 301 -364 37153 -11928 65 -332 195 -528 131 -230 97 -1126 131 -530 99 -790 361 -100 895 -98 597 -66 699 -100 15803 -10136 99 -2044 65 -400 165 -534 165 -134 99 -434 65 -966 97 -100 133 -134 1125 -66 589 -100 26555 -12218 165 -334 65 -100 99 -2586 65 -732 531 -100 461 -132 3347 -10206 97 -2084 63 -296 99 -164 65 -1052 97 -66 231 -330 65 -232 331 -596 1119 -424 361 -66 28413 -12166 67 -264 161 -228 97 -390 65 -856 131 -66 763 -132 201 -132 9717 -13508 129 -990 65 -100 97 -296 165 -724 65 -368 231 -98 401 -66 527 -66 767 -100 637 -66 109351 -13036 165 -796 65 -200 133 -100 199 -600 133 -134 99 -932 461 -132 399 -264 47323 -12184 97 -566 231 -400 67 -1456 229 -132 97 -394 163 -230 1579 -66 231 -164 10685 -12812 99 -132 67 -464 133 -1162 331 -132 99 -266 167 -200 99 -268 791 -164 823 -132 329 -134 63603 -10646 65 -662 165 -234 163 -630 463 -98 233 -100 195 -262 131 -132 491 -12204 65 -1156 131 -954 295 -132 99 -332 65 -664 789 -328 +RAW_Data: 1351 -8346 67 -4708 133 -400 99 -298 65 -556 65 -396 295 -66 263 -64 163 -100 131 -100 97 -68 131 -166 65 -166 497 -132 999 -132 5609 -4806 131 -4842 131 -298 133 -332 67 -596 131 -1186 263 -268 495 -100 461 -462 165 -66 525 -100 1579 -100 4557 -10356 165 -1614 165 -98 97 -630 65 -164 461 -164 65 -558 231 -130 131 -164 591 -66 557 -9122 65 -166 101 -6060 295 -68 65 -594 163 -368 263 -98 65 -98 99 -294 99 -66 363 -432 329 -66 1987 -100 25465 -9810 197 -100 131 -262 97 -530 99 -168 65 -166 431 -530 99 -232 167 -266 97 -234 1065 -66 131 -198 17549 -9930 899 -100 367 -994 67 -132 167 -794 231 -98 99 -464 299 -134 131 -100 199 -732 97 -296 131 -66 1157 -132 359 -130 47799 -12132 99 -630 197 -132 165 -502 195 -164 261 -98 261 -98 263 -66 195 -296 195 -198 65 -66 393 -66 955 -100 12501 -12452 97 -2942 165 -262 97 -228 65 -98 359 -130 625 -100 723 -66 365 -13612 99 -832 233 -66 99 -298 65 -464 233 -164 65 -164 1053 -362 10409 -11212 197 -794 131 -970 67 -132 99 -366 167 -366 165 -134 167 -664 99 -302 1253 -200 131 -15330 97 -132 259 -100 63 -558 165 -398 65 -860 231 -66 163 -98 263 -364 559 -100 1313 -66 89369 -10410 65 -1788 233 -132 133 -1062 165 -532 65 -632 63 -794 65 -166 97 -132 133 -66 431 -100 467 -132 199 -364 15615 -12276 99 -662 165 -66 133 -564 197 -1460 65 -66 65 -364 231 -396 165 -134 5835 -12662 165 -436 163 -468 65 -100 299 -728 99 -534 265 -166 263 -100 627 -100 197 -66 43301 -12090 163 -96 197 -692 97 -862 129 -368 165 -232 65 -998 197 -66 393 -196 683 -98 6471 -12036 165 -662 133 -302 99 -566 727 -66 2289 -66 7119 -12372 99 -662 97 -300 99 -66 131 -962 165 -362 131 -168 65 -428 133 -134 1797 -66 63965 -10524 263 -1458 265 -298 99 -166 199 -130 789 -68 23871 -12668 65 -990 99 -196 331 -990 755 -100 233 -132 267 -13192 65 -932 233 -134 199 -164 133 -568 99 -898 331 -134 99 -66 961 -66 7013 -10302 65 -664 131 -432 99 -532 199 -466 329 -364 65 -134 429 -198 759 -132 617 -198 38455 -12426 199 -134 99 -1252 99 -396 229 -660 99 -362 197 -164 197 -132 689 -66 161 -98 357 -330 265 -100 15001 -11544 133 -636 131 -132 99 -232 65 -1488 165 -636 131 -266 133 -134 367 -66 197 -100 267 -6574 131 -260 165 -166 65 -132 65 -828 295 -66 589 -100 diff --git a/applications/debug/unit_tests/resources/unit_tests/subghz/marantec24.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/marantec24.sub new file mode 100644 index 000000000..b15d1cdb3 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/subghz/marantec24.sub @@ -0,0 +1,7 @@ +Filetype: Flipper SubGhz Key File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: Marantec24 +Bit: 24 +Key: 00 00 00 00 00 AC 05 C4 diff --git a/applications/debug/unit_tests/resources/unit_tests/subghz/marantec24_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/marantec24_raw.sub new file mode 100644 index 000000000..4a8c34b21 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/subghz/marantec24_raw.sub @@ -0,0 +1,14 @@ +Filetype: Flipper SubGhz RAW File +Version: 1 +Frequency: 868350000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: RAW +RAW_Data: 1224 -1300 1801 -3200 1615 -2374 821 -3198 1603 -2410 785 -3196 815 -3188 1605 -2398 1605 -2398 1607 -2400 1605 -2398 1605 -2396 1601 -2400 1605 -2398 809 -3184 1603 -2396 815 -3194 811 -3190 811 -3194 1603 -2398 1603 -2398 1601 -2392 811 -3194 1605 -2394 1607 -16170 131 -196 217 -493440 135 -1296 1807 -3210 1591 -2396 811 -3198 1591 -2394 807 -3218 807 -3188 1607 -2400 1605 -2394 1599 -2402 1601 -2392 1609 -2400 1605 -2392 1603 -2392 815 -3190 1609 -2402 809 -3188 813 -3192 813 -3190 1605 -2400 1603 -2396 1605 -2396 813 -3194 1601 -2402 1601 -16170 133 -200 219 -382462 101 -1314 1813 -3194 1581 -2414 801 -3214 1605 -2398 807 -3190 811 -3190 1601 -2394 1603 -2398 1607 -2398 1605 -2396 1605 -2394 1601 -2394 1609 -2396 815 -3194 1603 -2400 809 -3194 811 -3190 811 -3190 1603 -2396 1603 -2396 1607 -2398 811 -3192 1607 -2396 1601 -16176 131 -198 217 -348436 303 -1106 1817 -3194 1609 -2386 803 -3214 1597 -2392 811 -3186 815 -3192 1611 -2396 1601 -2394 1609 -2404 1605 -2396 1605 -2396 1607 -2392 1601 -2396 813 -3192 1603 -2394 813 -3190 815 -3188 811 -3194 1601 -2396 1607 -2394 1601 -2396 815 -3186 1611 -2396 1609 -16168 131 -198 217 -349492 347 -1080 1799 -3192 1617 -2388 801 -3218 1601 -2396 809 -3190 809 -3192 1605 -2394 1599 -2394 1605 -2398 1607 -2398 1599 -2398 1607 -2396 1601 -2396 811 -3192 1605 -2400 811 -3184 813 -3192 811 -3192 1607 -2398 1601 -2398 1607 -2400 811 -3190 1603 -2394 1607 -16176 131 -204 213 -447994 135 -1330 977 -1036 65 -230 181 -514880 135 -66 67 -1140 1815 -3192 1615 -2388 803 -3214 1603 -2396 813 -3190 811 -3190 1609 -2398 1601 -2392 1603 -2396 1607 -2398 1607 -2398 1607 -2398 1605 -2394 813 -3186 1611 -2404 809 -3186 811 -3192 813 -3188 1605 -2394 1607 -2400 1607 -2398 807 -3190 1605 -2398 1603 -16172 131 -200 217 -395118 99 -1304 1841 -3160 1621 -2388 805 -3186 1631 -2392 809 -3182 811 -3188 1605 -2402 1601 -2398 1601 -2402 1607 -2398 1605 -2398 1601 -2392 1605 -2404 809 -3188 1599 -2396 817 -3192 811 -3192 811 -3190 1599 -2392 1603 -2398 1607 -2404 809 -3192 1603 -2402 1605 -16164 133 -198 187 -382202 137 -1302 1803 -3192 1601 -2404 803 -3184 1633 -2392 807 -3190 811 -3188 1601 -2400 1607 -2402 1601 -2392 1601 -2396 1607 -2400 1603 -2398 1605 -2398 811 -3184 1601 -2400 813 -3188 809 -3190 811 -3192 1605 -2400 1605 -2396 1599 -2402 809 -3192 1607 -2400 1607 -16174 99 -232 181 -361858 65 -1368 1785 -3228 1567 -2426 777 -3222 1601 -2402 785 -3198 815 -3186 1609 -2402 1601 -2396 1605 -2402 1603 -2396 1605 -2398 1601 -2400 1603 -2398 813 -3194 1605 -2400 809 -3186 813 -3190 813 -3188 1607 -2396 1605 -2400 1605 -2394 809 -3182 1607 -2400 1607 -16210 65 -236 177 -379100 1819 -3192 1611 -2384 799 -3220 1601 -2396 811 -3186 809 -3188 1605 -2394 1607 -2398 +RAW_Data: 1599 -2394 1603 -2396 1609 -2402 1603 -2396 1607 -2398 809 -3196 1603 -2394 809 -3188 813 -3194 809 -3188 1605 -2394 1599 -2396 1609 -2404 811 -3184 1605 -2400 1601 -16162 133 -234 191 -393094 97 -1316 1021 -1010 67 -246 173 -677840 131 -64 59 -1178 1773 -3210 1593 -2430 773 -3222 1601 -2396 811 -3186 813 -3190 1603 -2398 1603 -2396 1607 -2398 1605 -2400 1601 -2396 1605 -2396 1601 -2396 815 -3190 1607 -2402 811 -3190 811 -3186 811 -3192 1607 -2396 1603 -2396 1601 -2400 813 -3188 1607 -2404 1607 -15160 827 -3204 1577 -2404 813 -3200 1591 -2390 807 -3220 807 -3186 1599 -2400 1605 -2398 1603 -2400 1603 -2396 1605 -2398 1605 -2398 1599 -2394 809 -3190 1611 -2394 809 -3186 815 -3192 809 -3192 1603 -2398 1601 -2396 1599 -2396 815 -3188 1609 -2400 1605 -15178 801 -3222 1575 -2392 805 -3190 1615 -2396 809 -3190 819 -3198 1601 -2402 1577 -2402 1609 -2400 1605 -2398 1605 -2396 1605 -2398 1601 -2400 809 -3184 1605 -2396 809 -3190 811 -3192 813 -3190 1601 -2404 1605 -2394 1599 -2396 811 -3190 1605 -2400 1601 -15174 831 -3166 1615 -2396 783 -3200 1611 -2398 813 -3192 811 -3188 1601 -2396 1603 -2398 1607 -2396 1605 -2396 1603 -2398 1601 -2394 1601 -2398 811 -3192 1601 -2394 813 -3192 809 -3194 807 -3190 1605 -2394 1599 -2394 1601 -2394 813 -3194 1601 -2398 1603 -15178 793 -3228 1577 -2404 809 -3186 1611 -2396 815 -3192 811 -3188 1605 -2400 1577 -2404 1603 -2396 1607 -2400 1601 -2398 1603 -2394 1605 -2396 811 -3192 1605 -2394 811 -3190 811 -3188 807 -3186 1601 -2396 1605 -2398 1601 -2398 813 -3186 1601 -2394 1605 -15178 797 -3204 1581 -2400 813 -3188 1603 -2400 813 -3192 811 -3192 1607 -2398 1605 -2398 1607 -2394 1601 -2400 1605 -2398 1601 -2396 1601 -2394 809 -3190 1605 -2400 811 -3186 813 -3192 811 -3184 1601 -2396 1605 -2396 1605 -2400 809 -3184 1599 -2400 1605 -15146 827 -3188 1613 -2402 785 -3200 1605 -2396 815 -3192 811 -3190 1605 -2396 1605 -2396 1607 -2398 1601 -2398 1601 -2392 1599 -2398 1603 -2398 811 -3186 1605 -2396 811 -3190 811 -3192 809 -3190 1597 -2394 1599 -2398 1605 -2398 813 -3186 1599 -2396 1605 -15176 793 -3226 1577 -2398 817 -3198 1605 -2370 817 -3192 813 -3192 1609 -2396 1601 -2396 1605 -2396 1603 -2394 1605 -2394 1607 -2398 1607 -2394 809 -3188 1605 -2400 811 -3194 809 -3188 811 -3192 1605 -2400 1603 -2388 1605 -2394 811 -3186 1605 -2396 1605 -15180 797 -3202 1605 -2398 781 -3196 1605 -2396 815 -3184 817 -3196 1603 -2398 1605 -2398 1605 -2400 1603 -2394 1601 -2396 1607 -2398 1605 -2396 811 -3188 1605 -2396 809 -3186 807 -3194 811 -3194 1603 -2396 1597 -2396 1605 -2396 811 -3192 1603 -2396 1601 -15146 831 -3190 1603 -2404 777 -3224 1605 -2398 785 -3196 813 -3192 1607 -2396 1601 -2398 1607 -2394 1605 -2398 1603 -2394 1601 -2400 1599 -2396 807 -3192 1603 -2396 809 -3186 +RAW_Data: 813 -3190 809 -3190 1603 -2400 1603 -2398 1599 -2390 813 -3192 1603 -2394 1603 -15146 823 -3204 1603 -2398 787 -3204 1595 -2388 805 -3216 807 -3192 1605 -2400 1601 -2398 1597 -2394 1605 -2396 1603 -2398 1605 -2392 1601 -2396 809 -3192 1605 -2398 807 -3190 809 -3186 811 -3194 1603 -2394 1605 -2394 1599 -2398 813 -3192 1609 -2394 1603 -15174 795 -3206 1605 -2398 783 -3196 1607 -2400 809 -3188 813 -3188 1609 -2402 1601 -2394 1609 -2396 1603 -2396 1603 -2400 1599 -2388 1599 -2400 811 -3192 1607 -2394 811 -3186 811 -3190 809 -3194 1603 -2400 1599 -2394 1601 -2392 813 -3192 1603 -2396 1605 -15162 799 -3218 1581 -2404 807 -3202 1603 -2396 787 -3196 819 -3192 1611 -2394 1603 -2396 1607 -2402 1601 -2392 1599 -2392 1601 -2400 1607 -2398 811 -3192 1603 -2396 811 -3190 813 -3188 813 -3186 1603 -2394 1601 -2392 1605 -2400 809 -3188 1609 -2394 1601 -15156 829 -3188 1601 -2414 797 -3182 1629 -2394 807 -3192 811 -3188 1603 -2398 1599 -2392 1603 -2398 1603 -2396 1603 -2396 1607 -2400 1607 -2392 809 -3186 1603 -2400 813 -3194 811 -3192 809 -3188 1599 -2400 1603 -2404 1609 -2394 809 -3194 1599 -2394 1607 -15176 829 -3158 1623 -2404 773 -3224 1605 -2386 799 -3210 811 -3190 1607 -2392 1605 -2398 1607 -2400 1601 -2388 1605 -2398 1603 -2402 1599 -2392 809 -3188 1605 -2400 811 -3186 813 -3190 809 -3190 1603 -2396 1599 -2404 1603 -2400 809 -3188 1605 -2396 1607 -15146 829 -3216 1561 -2414 817 -3196 1599 -2404 797 -3180 809 -3212 1607 -2400 1601 -2394 1603 -2400 1603 -2396 1599 -2400 1605 -2396 1605 -2394 813 -3186 1607 -2394 811 -3186 809 -3190 813 -3192 1607 -2398 1603 -2398 1601 -2396 813 -3192 1603 -2392 1605 -15144 827 -3188 1613 -2404 809 -3192 1615 -2370 811 -3188 811 -3188 1611 -2398 1603 -2400 1601 -2394 1601 -2402 1605 -2396 1601 -2394 1609 -2398 811 -3186 1603 -2392 813 -3196 811 -3182 809 -3188 1603 -2402 1605 -2400 1599 -2394 809 -3194 1607 -2398 1601 -15148 831 -3186 1587 -2424 811 -3192 1575 -2408 803 -3188 805 -3222 1599 -2392 1601 -2400 1609 -2398 1601 -2392 1597 -2394 1601 -2400 1601 -2400 807 -3190 1601 -2400 813 -3190 809 -3190 809 -3188 1607 -2398 1601 -2396 1605 -2398 809 -3184 1603 -2400 1605 -15176 795 -3200 1613 -2400 783 -3198 1603 -2398 813 -3192 809 -3186 1607 -2398 1605 -2398 1605 -2398 1599 -2394 1605 -2398 1603 -2392 1599 -2396 809 -3194 1609 -2390 811 -3194 813 -3188 811 -3188 1599 -2394 1603 -2398 1601 -2394 815 -3192 1605 -2402 1601 -15150 831 -3186 1605 -2412 799 -3206 1601 -2392 809 -3186 813 -3192 1603 -2402 1601 -2392 1605 -2398 1607 -2396 1601 -2392 1609 -2402 1599 -2396 811 -3186 1601 -2398 815 -3196 809 -3188 809 -3188 1607 -2394 1605 -2394 1603 -2394 811 -3194 1603 -2398 1607 -15174 797 -3218 1563 -2424 785 -3194 1633 -2396 795 -3178 805 -3214 1607 -2394 1603 -2400 +RAW_Data: 1603 -2392 1603 -2398 1605 -2400 1597 -2392 1603 -2396 811 -3198 1603 -2390 809 -3192 811 -3188 813 -3192 1599 -2394 1607 -2398 1607 -2394 811 -3192 1601 -2394 1605 -15152 861 -3154 1603 -2410 799 -3186 1601 -2422 809 -3190 809 -3186 1601 -2392 1603 -2394 1611 -2398 1605 -2400 1601 -2392 1609 -2396 1603 -2398 809 -3186 1605 -2400 811 -3188 811 -3188 807 -3192 1605 -2402 1597 -2392 1603 -2396 813 -3194 1601 -2398 1605 -15174 795 -3200 1597 -2428 785 -3196 1613 -2398 785 -3192 815 -3192 1611 -2400 1603 -2394 1603 -2400 1601 -2398 1607 -2394 1603 -2398 1603 -2396 811 -3190 1605 -2394 811 -3194 811 -3182 811 -3192 1607 -2392 1599 -2394 1601 -2398 811 -3192 1607 -2392 1603 -15156 827 -3190 1607 -2408 797 -3210 1599 -2398 777 -3218 811 -3192 1601 -2400 1599 -2398 1601 -2396 1597 -2396 1601 -2392 1607 -2392 1605 -2400 813 -3190 1601 -2396 811 -3194 809 -3190 813 -3190 1603 -2394 1599 -2390 1603 -2400 811 -3190 1607 -2398 1605 -15150 825 -3192 1603 -2404 793 -3178 1605 -2422 777 -3222 775 -3218 1603 -2398 1605 -2398 1597 -2392 1603 -2398 1605 -2400 1601 -2394 1601 -2392 811 -3192 1609 -2398 813 -3190 809 -3182 809 -3184 1607 -2398 1603 -2396 1607 -2398 813 -3190 1607 -2396 1601 -15172 797 -3230 1563 -2424 805 -3190 1599 -2404 795 -3182 807 -3214 1601 -2402 1603 -2396 1601 -2396 1603 -2398 1605 -2398 1599 -2392 1601 -2402 813 -3192 1601 -2396 809 -3188 811 -3188 809 -3192 1605 -2400 1607 -2398 1599 -2394 809 -3190 1607 -2400 1605 -15180 827 -3190 1577 -2394 819 -3196 1613 -2366 815 -3196 813 -3190 1607 -2396 1607 -2402 1599 -2398 1605 -2400 1603 -2398 1601 -2392 1605 -2400 811 -3186 1603 -2400 813 -3190 809 -3196 807 -3182 1601 -2396 1609 -2398 1601 -2398 813 -3192 1601 -2394 1607 -15174 793 -3202 1599 -2424 787 -3196 1607 -2404 785 -3192 815 -3188 1609 -2404 1603 -2398 1607 -2396 1601 -2400 1603 -2394 1601 -2394 1603 -2400 811 -3190 1605 -2400 811 -3188 809 -3190 813 -3194 1597 -2398 1607 -2394 1603 -2394 809 -3190 1603 -2392 1607 -15180 795 -3230 1581 -2392 807 -3218 1593 -2408 777 -3220 781 -3204 1597 -2424 1605 -2394 1595 -2392 1605 -2394 1599 -2396 1605 -2402 1603 -2394 809 -3188 1601 -2396 813 -3194 811 -3188 813 -3190 1605 -2398 1607 -2392 1601 -2398 811 -3192 1599 -2400 1605 -15178 797 -3218 1595 -2400 803 -3198 1605 -2370 825 -3182 803 -3214 1603 -2396 1601 -2398 1601 -2394 1599 -2398 1603 -2396 1607 -2394 1605 -2394 809 -3190 1607 -2398 809 -3194 811 -3188 809 -3194 1605 -2394 1599 -2396 1605 -2396 809 -3196 1603 -2402 1599 -15160 829 -3192 1579 -2400 817 -3198 1605 -2398 783 -3194 813 -3190 1609 -2400 1607 -2400 1599 -2394 1601 -2394 1609 -2398 1601 -2396 1605 -2394 809 -3186 1609 -2400 811 -3186 809 -3196 811 -3190 1605 -2396 1601 -2398 1607 -2396 809 -3186 1605 -2398 1607 -15170 +RAW_Data: 827 -3192 1595 -2376 813 -3192 1601 -2414 797 -3186 807 -3220 1601 -2398 1603 -2394 1599 -2396 1607 -2400 1605 -2394 1597 -2398 1603 -2398 813 -3192 1603 -2394 809 -3188 809 -3188 811 -3192 1607 -2396 1601 -2398 1605 -2398 811 -3188 1603 -2400 1603 -15174 829 -3162 1627 -2374 803 -3224 1571 -2412 801 -3186 807 -3222 1605 -2392 1605 -2392 1599 -2394 1605 -2404 1601 -2396 1607 -2396 1601 -2394 809 -3188 1601 -2398 811 -3190 815 -3194 811 -3184 1603 -2400 1605 -2400 1605 -2394 809 -3188 1609 -2394 1605 -15154 829 -3188 1603 -2412 801 -3210 1603 -2398 807 -3182 809 -3196 1605 -2400 1605 -2396 1599 -2402 1607 -2390 1599 -2396 1599 -2400 1607 -2396 811 -3192 1605 -2396 813 -3196 809 -3190 811 -3192 1609 -2394 1597 -2396 1607 -2398 809 -3186 1605 -2394 1605 -15178 795 -3216 1577 -2408 819 -3190 1607 -2394 817 -3194 779 -3194 1607 -2404 1605 -2398 1607 -2394 1601 -2398 1605 -2400 1603 -2402 1607 -2394 811 -3192 1601 -2396 809 -3186 813 -3194 811 -3188 1599 -2394 1601 -2402 1601 -2398 811 -3186 1603 -2396 1611 -15176 799 -3224 1567 -2424 785 -3202 1599 -2396 809 -3204 783 -3198 1605 -2398 1607 -2402 1605 -2398 1607 -2400 1599 -2398 1605 -2392 1603 -2400 811 -3186 1601 -2400 811 -3194 809 -3184 811 -3186 1603 -2396 1603 -2398 1607 -2398 811 -3188 1605 -2396 1605 -15174 829 -3166 1605 -2402 809 -3190 1615 -2368 815 -3194 815 -3192 1605 -2396 1603 -2400 1603 -2400 1605 -2392 1601 -2400 1607 -2400 1601 -2402 811 -3194 1599 -2392 809 -3188 813 -3184 813 -3192 1605 -2400 1605 -2400 1607 -2390 809 -3188 1607 -2398 1603 -15174 829 -3158 1617 -2400 787 -3192 1607 -2404 811 -3184 815 -3190 1607 -2402 1601 -2396 1605 -2400 1601 -2396 1603 -2396 1607 -2400 1603 -2394 809 -3188 1609 -2398 809 -3190 811 -3188 809 -3188 1607 -2396 1599 -2396 1607 -2400 809 -3188 1607 -2398 1603 -15172 797 -3228 1575 -2424 771 -3202 1615 -2386 801 -3198 807 -3218 1587 -2390 1603 -2394 1603 -2424 1603 -2396 1603 -2398 1599 -2394 1605 -2396 809 -3192 1605 -2400 807 -3184 809 -3190 813 -3190 1603 -2390 1601 -2394 1601 -2396 813 -3196 1607 -2402 1599 -15152 829 -3194 1605 -2408 793 -3182 1597 -2426 807 -3184 811 -3192 1607 -2392 1603 -2396 1605 -2400 1601 -2394 1603 -2394 1605 -2400 1605 -2394 809 -3190 1605 -2396 811 -3192 809 -3186 811 -3190 1601 -2394 1605 -2400 1607 -2398 809 -3186 1601 -2394 1603 -15174 829 -3160 1635 -2382 811 -3194 1609 -2400 779 -3220 781 -3222 1575 -2406 1603 -2398 1607 -2402 1601 -2394 1603 -2396 1601 -2402 1605 -2398 805 -3190 1605 -2394 813 -3194 809 -3188 811 -3192 1603 -2396 1599 -2394 1607 -2394 811 -3194 1603 -2394 1601 -15174 799 -3216 1599 -2402 785 -3198 1615 -2376 819 -3194 811 -3190 1599 -2394 1623 -2392 1599 -2396 1611 -2382 1599 -2390 1633 -2392 1599 -2396 811 -3194 1599 -2394 811 -3188 +RAW_Data: 811 -3190 809 -3192 1603 -2398 1607 -2392 1599 -2398 811 -3190 1603 -2402 1601 -15172 795 -3194 1603 -2404 807 -3186 1599 -2394 821 -3196 785 -3196 1609 -2396 1609 -2398 1603 -2396 1605 -2400 1599 -2394 1605 -2396 1605 -2398 809 -3190 1601 -2396 811 -3190 809 -3194 809 -3188 1601 -2398 1603 -2398 1607 -2392 807 -3190 1603 -2402 1601 -15178 803 -3198 1579 -2410 807 -3188 1625 -2406 793 -3204 779 -3192 1609 -2408 1597 -2392 1595 -2424 1605 -2394 1601 -2394 1601 -2392 1601 -2396 809 -3194 1607 -2398 811 -3194 811 -3186 809 -3182 1607 -2398 1605 -2392 1605 -2392 809 -3190 1611 -2400 1605 -15154 799 -3228 1567 -2434 795 -3180 1627 -2392 809 -3190 809 -3186 1603 -2396 1599 -2394 1603 -2398 1603 -2398 1603 -2392 1607 -2400 1601 -2390 809 -3186 1601 -2404 811 -3188 811 -3190 813 -3186 1605 -2398 1605 -2392 1599 -2400 813 -3190 1599 -2396 1603 -15184 799 -3194 1607 -2410 795 -3176 1603 -2428 807 -3188 811 -3188 1603 -2394 1607 -2398 1599 -2396 1603 -2400 1605 -2394 1601 -2394 1607 -2400 809 -3186 1607 -2400 809 -3184 811 -3188 809 -3196 1605 -2396 1603 -2394 1607 -2392 809 -3188 1607 -2396 1609 -15176 795 -3226 1567 -2406 819 -3198 1579 -2406 809 -3190 821 -3198 1575 -2400 1603 -2398 1611 -2400 1603 -2398 1607 -2396 1601 -2394 1607 -2402 809 -3190 1603 -2396 811 -3186 809 -3192 811 -3192 1607 -2394 1603 -2398 1601 -2396 809 -3196 1605 -2398 1603 -15148 827 -3196 1597 -2426 775 -3200 1613 -2398 783 -3198 813 -3184 1613 -2400 1607 -2400 1605 -2398 1603 -2400 1601 -2394 1603 -2398 1605 -2398 809 -3192 1603 -2396 811 -3190 811 -3190 809 -3194 1605 -2394 1601 -2396 1601 -2400 811 -3192 1607 -2396 1603 -15162 797 -3244 1573 -2398 817 -3196 1603 -2404 795 -3210 775 -3220 1603 -2396 1603 -2396 1607 -2394 1607 -2368 1633 -2392 1601 -2396 1607 -2396 811 -3190 1605 -2400 807 -3186 811 -3194 809 -3186 1607 -2396 1603 -2392 1599 -2394 811 -3186 1611 -2396 1601 -15178 763 -3288 1501 -2496 741 -3254 1543 -2442 777 -3194 815 -3206 1585 -2390 1603 -2426 1601 -2392 1599 -2400 1605 -2396 1599 -2398 1603 -2398 809 -3190 1601 -2396 811 -3188 811 -3188 811 -3194 1607 -2396 1599 -2396 1605 -2398 813 -3190 1605 -2400 1603 -16206 65 -230 181 -579692 165 -1118 1797 -3196 1591 -2420 773 -3224 1603 -2394 809 -3188 809 -3188 1609 -2402 1603 -2398 1599 -2400 1605 -2400 1605 -2396 1605 -2396 1603 -2394 813 -3194 1607 -2398 809 -3188 809 -3192 809 -3196 1603 -2394 1599 -2396 1605 -2402 811 -3190 1605 -2400 1605 -15178 793 -3222 1577 -2402 809 -3192 1601 -2394 807 -3202 819 -3196 1609 -2396 1603 -2396 1601 -2392 1613 -2400 1579 -2398 1613 -2396 1603 -2396 813 -3192 1607 -2394 813 -3198 809 -3188 809 -3188 1599 -2398 1607 -2400 1607 -2398 807 -3186 1603 -2400 1607 -15178 823 -3168 1615 -2398 809 -3192 1615 -2398 783 -3228 +RAW_Data: 779 -3200 1607 -2394 1605 -2402 1607 -2394 1601 -2396 1603 -2400 1607 -2398 1603 -2396 809 -3182 1607 -2402 813 -3194 811 -3188 811 -3186 1603 -2402 1605 -2398 1607 -2396 809 -3188 1603 -2398 1609 -15162 829 -3204 1575 -2428 783 -3202 1597 -2422 775 -3214 809 -3192 1605 -2400 1603 -2402 1601 -2392 1603 -2394 1605 -2392 1611 -2398 1603 -2398 811 -3182 1609 -2400 813 -3196 811 -3186 811 -3186 1609 -2400 1605 -2394 1601 -2402 807 -3190 1607 -2398 1607 -15178 829 -3182 1595 -2424 773 -3224 1589 -2384 805 -3222 809 -3188 1603 -2398 1605 -2400 1599 -2396 1601 -2392 1601 -2400 1609 -2396 1605 -2398 813 -3190 1603 -2396 813 -3186 811 -3194 811 -3196 1603 -2394 1603 -2400 1603 -2396 809 -3186 1609 -2400 1605 -15160 825 -3198 1605 -2402 785 -3208 1599 -2426 775 -3220 807 -3186 1601 -2396 1609 -2398 1607 -2392 1599 -2402 1605 -2398 1599 -2398 1603 -2402 807 -3190 1603 -2398 813 -3186 813 -3196 809 -3184 1601 -2394 1603 -2428 1571 -2400 815 -3220 1575 -2402 1609 -15178 793 -3234 1575 -2404 819 -3188 1599 -2398 807 -3204 819 -3192 1581 -2400 1607 -2396 1607 -2402 1601 -2392 1605 -2398 1605 -2400 1605 -2398 811 -3190 1609 -2396 811 -3186 811 -3194 811 -3186 1603 -2396 1609 -2396 1601 -2400 811 -3192 1603 -2394 1603 -15188 831 -3184 1609 -2382 803 -3190 1629 -2396 809 -3192 809 -3188 1607 -2396 1607 -2398 1607 -2392 1599 -2396 1607 -2398 1605 -2398 1605 -2394 811 -3188 1607 -2400 813 -3192 811 -3190 813 -3186 1603 -2400 1603 -2402 1599 -2394 811 -3190 1609 -2396 1609 -15164 823 -3196 1599 -2396 789 -3210 1595 -2424 809 -3188 811 -3186 1603 -2398 1605 -2390 1601 -2394 1609 -2400 1605 -2396 1607 -2394 1603 -2398 813 -3194 1599 -2394 815 -3194 809 -3192 809 -3190 1607 -2398 1605 -2396 1603 -2394 809 -3188 1605 -2402 1601 -15162 827 -3196 1579 -2426 805 -3206 1605 -2366 811 -3208 799 -3212 1603 -2400 1603 -2394 1601 -2398 1605 -2398 1599 -2394 1601 -2394 1609 -2394 813 -3192 1607 -2398 813 -3186 807 -3188 813 -3188 1601 -2396 1609 -2396 1603 -2392 811 -3194 1601 -2398 1611 -15174 831 -3186 1595 -2406 785 -3200 1625 -2378 813 -3190 815 -3202 1593 -2424 1603 -2400 1603 -2394 1597 -2400 1603 -2400 1605 -2396 1597 -2400 811 -3192 1601 -2400 811 -3190 811 -3190 811 -3192 1607 -2398 1603 -2392 1607 -2394 811 -3194 1603 -2396 1601 -15154 831 -3190 1605 -2404 797 -3180 1635 -2392 807 -3190 809 -3188 1605 -2400 1603 -2394 1605 -2396 1605 -2398 1601 -2394 1605 -2400 1605 -2396 811 -3192 1603 -2394 809 -3186 815 -3192 805 -3192 1601 -2396 1601 -2366 1629 -2388 807 -3192 1603 -2396 1599 -15118 825 -3198 1605 -2400 783 -3198 1589 -2392 809 -3180 809 -3186 1629 -2400 1573 -2390 1631 -2362 1633 -2360 1631 -2366 1599 -2394 1603 -2392 805 -3214 1601 -2396 809 -3184 811 -3190 807 -3160 1631 -2394 1601 -2364 1601 -2420 +RAW_Data: 807 -3192 1603 -2360 1633 -15120 827 -3200 1575 -2400 809 -3196 1605 -2394 807 -3186 809 -3160 1633 -2392 1599 -2366 1599 -2394 1631 -2358 1635 -2366 1601 -2396 1629 -2390 777 -3190 1629 -2400 775 -3218 775 -3216 809 -3188 1603 -2362 1631 -2390 1601 -2396 811 -3190 1597 -2400 1601 -15128 809 -3220 1577 -2398 809 -3196 1603 -2364 811 -3194 811 -3192 1605 -2400 1605 -2360 1601 -2396 1601 -2392 1603 -2390 1631 -2394 1571 -2394 807 -3218 1603 -2396 775 -3214 807 -3186 807 -3162 1603 -2394 1601 -2390 1603 -2396 805 -3222 1603 -2364 1635 -15142 791 -3228 1571 -2400 811 -3188 1601 -2394 809 -3194 809 -3186 1605 -2398 1601 -2390 1603 -2362 1633 -2362 1601 -2394 1635 -2362 1631 -2390 807 -3190 1605 -2394 773 -3220 775 -3186 807 -3192 1599 -2424 1597 -2366 1603 -2396 807 -3216 1601 -2398 1601 -15140 813 -3196 1571 -2398 813 -3190 1601 -2396 811 -3186 811 -3192 1603 -2402 1601 -2392 1601 -2392 1609 -2394 1603 -2394 1607 -2394 1611 -2396 811 -3190 1605 -2400 811 -3190 811 -3190 813 -3190 1601 -2400 1603 -2394 1607 -2398 809 -3188 1607 -2398 1605 -15176 793 -3226 1579 -2400 811 -3186 1613 -2400 785 -3194 815 -3194 1611 -2396 1607 -2396 1601 -2400 1607 -2396 1605 -2398 1603 -2394 1599 -2392 811 -3190 1605 -2396 815 -3192 815 -3190 813 -3188 1601 -2398 1603 -2398 1603 -2398 811 -3194 1605 -2398 1601 -15156 859 -3148 1639 -2378 803 -3214 1569 -2430 807 -3186 809 -3194 1601 -2396 1605 -2398 1607 -2400 1605 -2396 1605 -2396 1607 -2392 1599 -2394 811 -3184 1609 -2400 815 -3194 809 -3188 809 -3196 1605 -2394 1607 -2394 1599 -2396 813 -3194 1603 -2398 1603 -15170 797 -3212 1595 -2402 819 -3184 1613 -2390 831 -3174 819 -3196 1597 -2402 1589 -2388 1601 -2396 1633 -2398 1601 -2392 1599 -2400 1607 -2396 811 -3186 1609 -2402 809 -3188 813 -3192 809 -3188 1607 -2396 1601 -2398 1601 -2398 811 -3192 1599 -2396 1607 -15174 831 -3186 1579 -2402 801 -3202 1615 -2388 827 -3178 803 -3188 1617 -2392 1601 -2392 1635 -2396 1599 -2392 1603 -2398 1605 -2400 1601 -2392 813 -3194 1601 -2400 813 -3190 811 -3190 809 -3188 1603 -2396 1601 -2398 1605 -2400 809 -3184 1601 -2396 1607 -15178 831 -3166 1611 -2398 815 -3198 1577 -2398 811 -3194 815 -3192 1607 -2400 1605 -2400 1599 -2398 1603 -2396 1601 -2398 1605 -2400 1609 -2392 811 -3192 1605 -2398 811 -3188 811 -3192 813 -3190 1607 -2394 1605 -2398 1603 -2400 809 -3190 1601 -2396 1609 -15174 795 -3230 1567 -2432 781 -3204 1597 -2394 807 -3222 807 -3182 1605 -2398 1603 -2392 1603 -2396 1607 -2396 1603 -2404 1605 -2400 1599 -2396 811 -3186 1611 -2398 813 -3188 811 -3192 811 -3188 1605 -2404 1603 -2400 1605 -2398 807 -3190 1605 -2396 1605 -15164 827 -3198 1605 -2398 783 -3206 1595 -2424 807 -3192 807 -3184 1605 -2396 1605 -2400 1607 -2398 1599 -2394 1607 -2396 1605 -2400 1599 -2394 +RAW_Data: 809 -3184 1609 -2400 809 -3194 813 -3188 807 -3188 1603 -2400 1605 -2400 1599 -2398 809 -3190 1605 -2400 1603 -15170 803 -3208 1595 -2402 809 -3196 1589 -2388 807 -3216 809 -3182 1603 -2394 1607 -2398 1603 -2396 1607 -2396 1603 -2400 1605 -2396 1599 -2394 811 -3188 1605 -2396 817 -3196 811 -3182 811 -3192 1607 -2394 1603 -2394 1607 -2398 811 -3190 1603 -2398 1601 -15186 833 -3156 1605 -2412 799 -3218 1595 -2392 809 -3186 811 -3186 1609 -2404 1607 -2394 1605 -2396 1601 -2400 1607 -2396 1601 -2392 1607 -2400 809 -3192 1603 -2396 815 -3194 809 -3188 809 -3192 1603 -2400 1599 -2394 1605 -2400 811 -3188 1599 -2400 1605 -16142 167 -202 diff --git a/applications/debug/unit_tests/resources/unit_tests/subghz/revers_rb2.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/revers_rb2.sub new file mode 100644 index 000000000..9a046fd7b --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/subghz/revers_rb2.sub @@ -0,0 +1,7 @@ +Filetype: Flipper SubGhz Key File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: Revers_RB2 +Bit: 64 +Key: FF FF FF FF 39 F9 0A 00 diff --git a/applications/debug/unit_tests/resources/unit_tests/subghz/revers_rb2_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/revers_rb2_raw.sub new file mode 100644 index 000000000..f50ad7442 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/subghz/revers_rb2_raw.sub @@ -0,0 +1,17 @@ +Filetype: Flipper SubGhz RAW File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: RAW +RAW_Data: 215 -498 257 -246 243 -276 215 -280 215 -278 247 -250 507 -258 247 -504 479 -274 253 -254 219 -290 213 -530 485 -482 481 -308 217 -254 253 -252 241 -234 257 -250 249 -278 219 -252 247 -250 279 -544 235 -268 259 -226 255 -250 249 -280 219 -252 249 -250 215 -280 247 -252 249 -250 249 -280 219 -252 249 -250 215 -280 279 -218 251 -250 249 -280 219 -252 251 -248 281 -220 251 -250 249 -248 247 -278 219 -252 249 -250 279 -220 251 -250 247 -280 219 -254 249 -248 217 -280 277 -220 507 -258 247 -504 225 -278 245 -246 469 -292 213 -530 227 -276 215 -274 247 -248 249 -248 247 -248 501 -258 249 -502 479 -272 251 -254 255 -254 249 -476 481 -514 481 -274 253 -254 255 -250 239 -262 225 -254 249 -248 217 -278 247 -250 251 -572 267 -238 263 -224 251 -280 219 -252 249 -250 279 -220 251 -250 249 -280 219 -252 249 -248 249 -248 247 -250 251 -250 249 -280 219 -252 251 -248 217 -280 247 -250 249 -250 249 -280 219 -252 249 -250 279 -220 251 -250 249 -248 247 -280 219 -252 249 -248 281 -220 251 -250 247 -280 479 -228 279 -500 255 -216 277 -214 531 -228 279 -500 257 -216 275 -214 275 -216 277 -278 217 -250 505 -260 217 -500 509 -258 243 -246 273 -216 275 -500 479 -546 479 -238 253 -256 253 -250 243 -268 221 -250 247 -250 251 -250 247 -280 219 -574 265 -268 199 -292 219 -250 279 -220 251 -250 249 -248 249 -278 219 -252 249 -250 279 -220 251 -248 249 -280 219 -252 251 -248 217 -280 247 -250 251 -250 249 -280 219 -252 249 -250 279 -220 251 -250 249 -248 247 -280 219 -250 249 -250 279 -220 251 -250 249 -248 247 -280 219 -252 503 -260 217 -532 225 -276 215 -278 503 -238 251 -502 249 -244 241 -278 215 -272 215 -272 277 -214 493 -310 215 -530 483 -232 261 -258 223 -254 249 -538 483 -482 509 -274 217 -290 217 -252 241 -236 259 -250 247 -248 247 -280 217 -252 249 -602 203 -272 229 -256 281 -220 251 -250 249 -280 219 -252 249 -250 279 -222 251 -250 247 -248 249 -278 219 -252 249 -250 279 -220 251 -250 247 -280 221 -252 249 -250 215 -280 247 -250 251 -250 249 -280 219 -252 249 -250 279 -220 251 -250 249 -248 247 -280 219 -250 249 -250 279 -220 507 -258 215 -530 227 -278 215 -276 501 -258 215 -530 227 -278 215 -276 215 -276 247 -250 247 -250 501 -260 249 -502 481 -272 251 -254 219 -290 247 -474 481 -514 481 -274 289 -218 253 -252 241 -230 253 -282 217 -252 249 -248 279 -220 251 -574 267 -238 263 -222 +RAW_Data: 251 -280 219 -252 249 -250 215 -280 279 -220 251 -248 249 -280 219 -252 249 -250 279 -220 251 -250 249 -248 247 -280 219 -252 249 -250 277 -222 251 -250 247 -280 219 -252 249 -250 217 -280 247 -250 251 -250 247 -280 219 -252 249 -250 279 -222 251 -250 247 -250 501 -258 249 -502 257 -218 275 -216 497 -272 287 -464 219 -274 275 -216 273 -216 271 -278 213 -274 499 -238 253 -498 479 -262 261 -228 257 -252 249 -506 481 -512 507 -236 291 -218 253 -290 211 -270 223 -250 247 -250 251 -250 247 -248 249 -568 267 -272 201 -292 221 -248 217 -280 247 -250 251 -250 247 -280 221 -252 249 -250 279 -220 251 -250 249 -248 247 -280 219 -250 249 -250 279 -220 251 -250 249 -248 247 -280 219 -250 249 -250 279 -222 251 -248 249 -280 219 -252 249 -250 217 -278 247 -252 251 -250 247 -280 219 -252 507 -260 217 -502 269 -252 253 -256 495 -236 265 -482 259 -250 247 -278 217 -252 249 -248 215 -280 503 -258 217 -532 515 -238 253 -254 255 -254 213 -504 513 -514 477 -274 215 -290 217 -256 247 -234 253 -252 249 -250 249 -280 219 -252 249 -602 203 -272 229 -256 281 -220 251 -250 249 -280 219 -252 249 -250 247 -248 249 -250 251 -250 249 -280 219 -250 249 -250 279 -222 251 -250 247 -250 247 -280 219 -250 249 -250 279 -220 251 -250 249 -248 247 -280 219 -252 249 -248 281 -220 249 -250 249 -280 219 -252 249 -250 215 -280 503 -260 217 -530 227 -280 213 -278 501 -258 217 -496 257 -276 215 -278 215 -278 245 -250 251 -250 501 -260 249 -502 479 -272 251 -256 217 -290 247 -474 483 -514 481 -274 251 -256 253 -252 241 -230 253 -282 217 -252 249 -248 217 -278 247 -576 269 -240 265 -222 251 -248 249 -278 219 -252 249 -250 279 -220 249 -250 249 -280 219 -252 249 -250 217 -278 279 -220 251 -250 249 -280 219 -252 249 -250 279 -220 251 -250 247 -250 247 -280 219 -250 249 -250 279 -220 251 -250 249 -248 247 -280 219 -250 249 -250 279 -220 251 -250 535 -226 251 -502 225 -246 275 -246 467 -290 245 -504 225 -276 245 -248 247 -246 247 -248 245 -280 475 -228 279 -500 479 -272 251 -256 253 -256 247 -470 483 -514 513 -238 289 -220 253 -288 209 -270 223 -250 215 -280 245 -250 251 -248 249 -570 267 -238 229 -288 221 -254 249 -250 279 -220 251 -250 249 -248 247 -280 219 -250 249 -250 279 -222 251 -250 247 -280 219 -252 249 -250 217 -278 247 -252 251 -250 247 -280 219 -252 249 -250 279 -222 251 -250 247 -248 249 -280 217 -252 +RAW_Data: 249 -250 279 -220 251 -250 249 -278 221 -252 505 -228 245 -530 257 -214 275 -246 505 -226 247 -532 257 -214 275 -216 275 -246 249 -248 217 -280 503 -258 217 -498 543 -236 251 -254 255 -254 251 -474 513 -516 477 -238 253 -292 217 -254 245 -234 253 -252 251 -248 249 -278 219 -252 251 -602 203 -272 229 -258 279 -222 251 -250 247 -250 247 -280 217 -252 249 -250 279 -220 251 -250 249 -248 247 -280 219 -252 249 -248 279 -220 251 -250 249 -280 219 -252 249 -250 247 -248 279 -220 251 -248 249 -280 219 -252 249 -250 279 -220 253 -250 247 -248 249 -278 219 -252 249 -250 503 -258 217 -564 195 -278 215 -276 503 -258 215 -500 271 -288 215 -256 253 -250 207 -262 251 -252 505 -256 247 -504 481 -306 215 -254 253 -256 213 -534 487 -480 511 -274 215 -292 253 -218 247 -236 257 -250 247 -278 219 -250 249 -250 279 -544 235 -268 259 -226 253 -250 249 -280 219 -252 249 -250 279 -220 253 -250 247 -248 249 -278 219 -252 249 -250 279 -220 251 -250 249 -248 247 -280 219 -250 249 -250 279 -220 251 -250 249 -280 219 -252 249 -250 215 -280 277 -220 251 -250 247 -280 221 -252 249 -250 279 -220 251 -250 471 -290 247 -502 225 -278 245 -246 469 -306 251 -466 251 -242 245 -272 245 -246 241 -246 241 -278 497 -238 253 -500 483 -264 229 -258 283 -220 253 -508 481 -508 477 -308 253 -218 253 -254 243 -264 225 -256 247 -250 279 -220 249 -250 249 -570 269 -236 231 -286 223 -252 251 -248 217 -278 247 -252 249 -250 249 -280 219 -252 249 -250 279 -220 251 -250 249 -248 247 -280 219 -252 249 -250 279 -220 251 -250 247 -280 221 -252 249 -250 215 -280 247 -250 251 -250 247 -280 219 -254 249 -250 247 -248 277 -220 251 -250 247 -280 479 -228 279 -500 257 -216 275 -214 531 -228 279 -500 257 -216 275 -216 275 -246 249 -250 247 -250 503 -260 215 -500 541 -238 249 -256 253 -256 215 -508 481 -548 479 -238 253 -292 217 -254 245 -232 221 -280 247 -250 251 -250 247 -280 219 -574 263 -268 199 -292 219 -250 279 -220 253 -250 247 -280 221 -252 249 -248 249 -248 277 -220 251 -250 249 -280 219 -252 249 -250 279 -220 251 -250 249 -248 247 -280 219 -252 249 -248 279 -220 251 -250 249 -280 219 -252 249 -250 247 -248 279 -220 251 -250 247 -280 219 -252 249 -250 505 -260 217 -498 257 -276 213 -278 503 -260 215 -498 257 -246 243 -276 215 -280 215 -278 247 -252 505 -256 247 -506 479 -274 253 -254 219 -290 213 -530 485 -482 509 -274 +RAW_Data: 215 -292 217 -252 243 -236 259 -250 249 -280 219 -250 249 -250 215 -632 205 -272 261 -226 255 -250 249 -248 247 -280 217 -252 249 -250 279 -220 251 -250 249 -280 219 -252 249 -250 215 -280 279 -220 249 -250 249 -280 219 -252 249 -250 279 -220 251 -250 249 -248 247 -280 219 -252 249 -248 279 -220 251 -250 249 -280 219 -252 249 -250 247 -248 279 -218 507 -258 247 -504 225 -278 215 -274 499 -258 215 -530 225 -280 213 -278 245 -250 249 -248 247 -248 501 -260 247 -502 479 -272 251 -256 253 -254 249 -476 481 -514 483 -274 253 -254 255 -252 239 -260 225 -254 249 -248 215 -280 247 -250 251 -572 267 -238 263 -224 249 -280 219 -252 249 -250 279 -220 251 -250 249 -280 219 -252 249 -250 217 -280 277 -220 251 -248 249 -280 219 -252 251 -248 247 -248 279 -220 251 -250 247 -282 219 -250 249 -250 279 -222 251 -250 247 -250 247 -280 219 -250 249 -250 279 -220 251 -250 249 -280 479 -228 277 -500 257 -214 275 -216 529 -228 279 -500 257 -216 275 -216 275 -216 277 -278 217 -250 507 -258 219 -500 511 -274 253 -254 255 -218 245 -500 483 -544 479 -226 273 -244 243 -276 213 -278 213 -278 245 -250 251 -248 247 -280 219 -572 265 -268 199 -292 219 -250 279 -222 251 -250 247 -248 249 -278 221 -250 249 -250 279 -220 251 -250 249 -280 219 -252 251 -248 247 -248 279 -220 251 -248 249 -280 219 -252 249 -250 247 -248 279 -220 251 -250 247 -280 219 -254 249 -248 281 -220 251 -250 247 -250 247 -280 219 -250 505 -260 217 -500 257 -276 215 -278 503 -236 249 -504 247 -246 241 -278 213 -274 243 -244 275 -214 493 -310 215 -516 477 -272 235 -262 227 -256 249 -504 509 -482 511 -274 215 -292 217 -252 279 -202 259 -250 249 -248 247 -278 219 -250 249 -604 205 -272 229 -254 281 -220 251 -250 249 -280 219 -252 249 -250 279 -220 251 -250 249 -248 247 -280 219 -250 249 -250 279 -222 249 -250 249 -280 219 -252 249 -250 247 -248 277 -220 251 -250 249 -280 219 -252 249 -250 247 -248 279 -220 251 -250 247 -280 221 -252 249 -248 279 -220 507 -256 215 -532 225 -280 213 -276 501 -258 217 -530 227 -280 213 -276 215 -276 247 -248 249 -250 535 -226 249 -502 479 -258 243 -276 213 -276 245 -502 481 -514 483 -272 253 -254 255 -250 239 -230 253 -280 219 -250 249 -248 279 -220 253 -572 267 -238 263 -224 249 -282 219 -250 249 -250 217 -280 277 -220 251 -250 247 -280 221 -252 249 -248 281 -220 251 -250 249 -248 247 -280 219 -250 +RAW_Data: 249 -250 279 -220 251 -250 249 -280 219 -252 249 -250 247 -248 277 -220 251 -250 249 -280 219 -252 249 -250 279 -220 251 -250 249 -248 501 -260 249 -502 257 -216 275 -216 497 -272 287 -464 243 -290 253 -212 271 -226 251 -280 219 -250 505 -226 247 -530 477 -274 251 -256 253 -218 283 -472 483 -514 515 -236 291 -218 253 -288 209 -268 223 -250 247 -250 251 -250 247 -248 247 -570 267 -270 201 -294 219 -250 247 -248 279 -220 249 -250 249 -280 219 -252 249 -250 247 -248 279 -220 251 -250 249 -280 219 -252 249 -250 277 -222 251 -250 247 -250 247 -278 219 -252 249 -250 279 -220 251 -250 249 -280 219 -252 249 -250 215 -280 279 -220 249 -250 247 -280 221 -252 505 -260 219 -500 271 -252 253 -256 487 -234 267 -488 257 -250 247 -248 245 -278 219 -252 249 -248 505 -258 217 -532 515 -238 253 -254 255 -254 213 -506 511 -514 477 -274 215 -290 219 -290 211 -234 285 -220 251 -250 247 -280 219 -252 249 -602 239 -238 229 -256 281 -220 251 -250 249 -280 219 -252 249 -250 215 -280 279 -220 251 -250 247 -280 219 -252 249 -250 247 -248 247 -252 251 -248 249 -280 219 -252 249 -250 279 -220 253 -250 247 -248 249 -278 219 -252 249 -250 279 -220 251 -248 249 -280 219 -252 251 -248 249 -248 503 -258 217 -532 227 -278 215 -276 501 -260 215 -498 257 -276 213 -278 215 -278 245 -252 249 -250 535 -226 249 -504 479 -272 251 -254 217 -292 247 -474 483 -514 481 -274 253 -256 253 -252 239 -230 253 -280 219 -252 247 -250 215 -280 277 -544 269 -240 265 -224 251 -248 247 -278 219 -252 249 -250 279 -220 251 -250 249 -280 219 -252 249 -250 215 -280 277 -220 251 -250 249 -280 219 -252 249 -250 279 -220 251 -250 249 -248 247 -280 219 -252 249 -248 279 -220 253 -250 247 -248 249 -278 219 -252 249 -250 277 -222 251 -250 535 -228 249 -502 225 -246 275 -246 469 -306 251 -464 243 -290 253 -212 273 -226 251 -248 247 -280 475 -228 279 -500 477 -274 251 -254 255 -254 247 -472 481 -514 515 -226 273 -214 271 -246 241 -276 215 -278 215 -278 245 -252 249 -250 249 -570 267 -236 231 -288 221 -254 249 -250 277 -222 251 -248 249 -248 247 -280 219 -252 249 -248 281 -220 251 -250 249 -280 219 -252 249 -250 215 -280 247 -250 251 -250 247 -280 221 -252 249 -248 281 -220 251 -250 249 -248 247 -280 219 -250 249 -250 279 -220 251 -250 249 -248 247 -280 477 -228 279 -500 255 -216 275 -246 505 -228 247 -530 255 -216 275 -216 275 -246 +RAW_Data: 247 -250 249 -248 503 -260 217 -498 541 -238 249 -256 253 -256 251 -474 513 -518 477 -238 253 -292 217 -254 243 -234 283 -222 249 -250 249 -248 247 -278 219 -606 239 -238 231 -256 249 -248 279 -220 249 -250 249 -280 219 -252 249 -250 279 -220 251 -250 249 -248 247 -280 219 -252 249 -250 279 -220 251 -250 249 -280 219 -250 249 -250 249 -248 247 -250 251 -250 247 -280 219 -252 251 -248 281 -220 251 -250 249 -248 247 -280 219 -252 249 -248 505 -258 217 -498 257 -276 215 -278 501 -258 217 -498 257 -276 215 -278 215 -278 215 -278 247 -250 505 -260 245 -504 481 -288 215 -274 215 -276 213 -530 515 -478 511 -274 215 -256 289 -220 243 -234 255 -250 245 -280 217 -252 249 -248 279 -544 237 -268 259 -224 255 -250 249 -280 219 -252 249 -250 279 -220 251 -250 249 -248 247 -280 219 -252 249 -248 279 -220 251 -250 249 -248 249 -278 219 -252 249 -248 279 -222 251 -250 247 -280 219 -254 249 -248 247 -248 279 -220 251 -250 249 -278 221 -252 249 -248 279 -222 251 -250 469 -292 245 -504 235 -288 253 -218 489 -266 269 -500 195 -280 215 -276 247 -248 249 -250 247 -280 479 -228 277 -502 477 -272 253 -254 253 -256 247 -474 481 -516 481 -276 253 -256 253 -214 273 -260 223 -254 249 -248 279 -220 251 -250 249 -570 267 -236 231 -288 221 -254 249 -250 215 -280 247 -252 249 -250 249 -280 219 -252 249 -250 279 -220 251 -250 249 -248 247 -280 219 -252 249 -248 279 -220 251 -250 249 -248 247 -280 219 -252 249 -248 279 -222 251 -250 247 -280 219 -252 249 -250 217 -280 247 -250 251 -250 249 -278 479 -228 279 -500 257 -214 275 -216 531 -228 279 -500 257 -214 275 -216 275 -248 247 -250 249 -248 503 -258 217 -500 545 -238 251 -256 253 -218 247 -502 481 -546 481 -238 289 -256 217 -290 213 -234 223 -280 247 -250 251 -250 247 -280 219 -574 263 -268 199 -292 219 -250 279 -220 251 -250 249 -280 219 -252 249 -250 247 -248 279 -220 251 -250 249 -280 219 -252 249 -248 281 -220 251 -250 247 -250 247 -280 219 -250 249 -250 279 -220 251 -250 249 -248 247 -280 219 -252 249 -248 279 -222 251 -250 247 -280 219 -252 249 -250 503 -260 217 -498 257 -276 215 -278 503 -258 217 -500 269 -254 253 -254 255 -250 205 -264 281 -220 507 -256 247 -504 481 -272 253 -256 217 -290 213 -532 487 -480 511 -274 215 -292 217 -252 243 -236 257 -250 247 -280 219 -250 249 -250 215 -632 205 -272 261 -226 255 -250 249 -248 249 -278 219 -252 +RAW_Data: 249 -248 279 -222 251 -250 247 -280 219 -252 249 -250 247 -248 279 -220 249 -252 247 -280 219 -252 249 -250 279 -222 251 -250 247 -248 249 -278 221 -250 249 -250 279 -220 251 -250 249 -280 219 -252 249 -250 247 -248 279 -220 505 -258 245 -504 227 -278 213 -274 501 -258 215 -530 227 -280 213 -276 247 -248 249 -248 247 -248 501 -258 249 -502 479 -272 253 -254 253 -256 249 -474 483 -514 483 -274 255 -254 255 -214 273 -260 223 -254 249 -248 215 -280 247 -252 249 -574 267 -238 263 -224 249 -280 219 -252 249 -250 279 -220 251 -250 249 -248 247 -280 219 -250 249 -250 279 -220 253 -250 247 -280 219 -252 249 -250 247 -248 279 -220 251 -250 247 -280 219 -252 249 -250 279 -220 253 -250 247 -248 249 -278 219 -252 249 -250 279 -220 251 -250 249 -280 477 -228 279 -500 257 -216 275 -214 531 -226 279 -500 225 -244 277 -214 277 -216 277 -278 219 -250 505 -260 217 -500 513 -274 251 -256 253 -218 247 -500 481 -546 479 -238 289 -218 255 -286 211 -270 223 -250 247 -250 249 -250 249 -280 219 -572 265 -268 199 -292 221 -248 281 -220 251 -250 247 -250 247 -280 219 -250 249 -250 279 -220 251 -250 249 -248 247 -280 219 -252 249 -248 279 -220 251 -250 249 -280 219 -252 249 -250 217 -278 279 -220 249 -250 249 -280 219 -252 249 -250 279 -220 253 -250 247 -248 247 -280 219 -252 505 -260 217 -532 225 -276 215 -278 503 -236 251 -504 247 -254 251 -280 201 -260 251 -248 247 -252 505 -258 215 -530 513 -238 253 -254 255 -254 213 -504 513 -514 477 -274 215 -290 219 -252 281 -204 259 -252 247 -248 247 -278 219 -250 249 -604 203 -272 229 -256 281 -220 251 -250 247 -280 219 -252 251 -248 281 -220 251 -250 247 -250 247 -280 217 -252 249 -250 279 -220 251 -250 249 -280 219 -252 249 -250 215 -280 277 -220 251 -250 249 -280 219 -252 249 -250 217 -278 247 -252 249 -250 249 -280 219 -252 249 -250 279 -220 507 -258 215 -530 227 -278 215 -276 499 -260 215 -530 225 -280 213 -276 215 -278 247 -248 249 -248 503 -260 249 -502 479 -258 243 -274 215 -274 247 -504 481 -512 477 -274 251 -256 253 -252 241 -230 253 -280 219 -252 249 -248 279 -220 251 -574 267 -238 263 -224 249 -282 219 -250 249 -250 217 -280 245 -252 251 -250 247 -280 219 -254 249 -248 281 -220 251 -250 249 -248 247 -280 219 -250 249 -250 279 -220 251 -250 249 -280 219 -252 249 -248 217 -280 277 -220 251 -250 249 -280 219 -252 249 -250 215 -280 279 -220 +RAW_Data: 249 -250 535 -228 249 -502 257 -218 275 -216 497 -272 285 -466 243 -290 253 -212 271 -226 251 -280 219 -250 505 -228 245 -530 479 -272 253 -254 253 -220 283 -470 481 -516 515 -236 255 -254 253 -288 209 -268 223 -248 217 -278 247 -250 251 -250 247 -570 267 -238 231 -286 221 -254 249 -250 279 -220 251 -250 249 -280 219 -252 249 -250 217 -278 247 -252 251 -248 249 -280 219 -252 249 -250 279 -222 251 -250 247 -248 249 -278 219 -252 249 -248 281 -220 251 -248 249 -280 219 -252 249 -250 249 -248 247 -252 249 -250 249 -280 219 -252 505 -228 247 -530 227 -246 273 -246 505 -228 245 -530 255 -216 275 -214 275 -248 247 -250 247 -250 503 -258 217 -564 483 -238 251 -256 253 -254 215 -504 515 -514 475 -258 213 -274 243 -276 213 -276 245 -248 247 -248 247 -280 219 -250 249 -604 203 -272 229 -256 281 -220 251 -250 247 -280 221 -252 249 -250 215 -280 277 -220 251 -250 249 -280 219 -252 249 -250 215 -280 279 -218 251 -250 249 -280 219 -252 249 -250 279 -220 251 -250 249 -248 247 -280 219 -252 249 -248 279 -220 253 -250 247 -280 219 -252 249 -250 217 -280 503 -258 217 -532 225 -280 213 -278 501 -258 217 -496 257 -276 215 -278 215 -278 247 -250 249 -250 469 -292 245 -504 481 -272 251 -254 217 -292 247 -474 485 -512 481 -274 253 -254 255 -252 239 -230 255 -280 219 -250 249 -248 217 -278 279 -544 269 -238 265 -224 251 -248 249 -278 219 -252 249 -250 279 -220 251 -250 247 -280 219 -252 249 -250 217 -280 247 -252 249 -250 249 -280 219 -252 249 -250 247 -248 279 -220 249 -250 249 -280 219 -252 249 -250 279 -220 251 -250 249 -248 247 -280 219 -250 249 -250 279 -220 251 -250 535 -228 249 -504 237 -250 291 -218 491 -264 265 -498 193 -280 247 -248 249 -248 249 -248 247 -278 477 -230 277 -500 479 -272 215 -292 253 -256 247 -472 481 -514 515 -236 291 -218 255 -252 239 -262 225 -252 249 -250 279 -220 249 -250 249 -570 267 -238 231 -286 221 -254 249 -250 279 -220 251 -250 249 -248 247 -280 219 -250 249 -250 279 -220 253 -250 247 -280 219 -252 249 -250 247 -248 279 -220 251 -248 249 -280 221 -252 249 -248 247 -248 279 -220 251 -250 249 -280 219 -252 249 -250 279 -220 251 -250 249 -248 247 -280 477 -228 277 -500 257 -216 275 -246 505 -226 247 -530 257 -214 275 -216 275 -246 249 -250 247 -250 503 -258 217 -498 541 -228 245 -244 243 -278 247 -500 511 -516 479 -238 253 -292 217 -252 209 -266 281 -222 +RAW_Data: 249 -250 249 -248 247 -278 219 -606 239 -238 231 -258 249 -248 247 -250 251 -250 249 -280 219 -252 249 -250 279 -220 251 -250 249 -248 247 -280 219 -250 249 -250 279 -220 251 -250 249 -280 219 -252 249 -250 217 -278 279 -220 251 -250 249 -278 221 -250 249 -250 279 -222 251 -250 247 -248 249 -278 219 -252 249 -250 503 -260 217 -498 257 -276 215 -278 501 -258 217 -498 257 -276 215 -278 215 -278 215 -278 247 -250 505 -258 245 -506 479 -308 215 -254 219 -290 213 -534 487 -482 479 -288 213 -274 243 -246 275 -214 275 -216 277 -278 217 -250 247 -248 279 -546 235 -268 259 -226 253 -250 249 -280 219 -252 249 -250 247 -248 279 -220 251 -250 249 -280 219 -252 249 -248 281 -220 251 -250 249 -248 247 -280 219 -252 249 -248 279 -220 251 -250 249 -280 219 -252 249 -250 247 -248 279 -218 251 -250 249 -280 219 -252 249 -250 279 -220 251 -250 471 -292 245 -502 227 -276 245 -248 467 -308 215 -504 243 -290 217 -244 265 -228 253 -250 247 -280 477 -228 279 -500 479 -272 251 -256 253 -254 249 -472 483 -514 479 -256 275 -244 243 -246 243 -276 215 -278 215 -276 247 -250 249 -250 249 -570 267 -236 231 -286 221 -254 249 -250 217 -278 279 -220 251 -250 247 -280 221 -252 249 -248 247 -248 279 -220 251 -250 247 -280 219 -254 249 -248 279 -222 251 -250 247 -250 247 -280 219 -250 249 -250 279 -220 251 -250 249 -280 219 -252 249 -250 215 -280 279 -220 249 -250 249 -280 477 -228 277 -502 255 -216 275 -216 531 -228 279 -500 257 -216 275 -214 277 -246 249 -250 247 -250 503 -260 215 -500 541 -226 245 -246 243 -246 275 -500 477 -548 477 -236 255 -290 217 -254 245 -234 221 -282 247 -250 249 -250 249 -280 219 -572 265 -266 199 -292 221 -248 281 -220 251 -250 249 -278 221 -252 249 -250 247 -248 279 -220 249 -250 249 -280 219 -252 249 -250 279 -220 251 -250 249 -248 247 -280 219 -252 249 -250 279 -220 249 -250 249 -248 249 -278 219 -252 249 -250 279 -220 251 -250 249 -280 219 -252 249 -250 503 -260 217 -498 257 -276 215 -278 503 -258 217 -498 257 -244 243 -278 215 -278 217 -278 245 -252 505 -258 247 -506 479 -272 255 -254 217 -292 211 -532 485 -480 509 -274 217 -290 219 -252 243 -236 259 -250 247 -280 219 -250 249 -250 217 -630 205 -272 261 -226 255 -252 247 -248 247 -280 219 -250 249 -250 279 -220 251 -250 249 -280 219 -252 249 -250 217 -278 279 -220 251 -250 247 -280 219 -252 249 -250 279 -220 251 -250 +RAW_Data: 249 -248 247 -280 219 -252 249 -248 279 -222 251 -250 247 -250 247 -280 217 -252 249 -250 279 -220 507 -258 247 -504 225 -278 213 -274 501 -258 215 -530 225 -280 213 -276 247 -248 249 -248 249 -246 501 -260 249 -502 479 -272 251 -254 255 -254 249 -476 481 -514 483 -274 253 -256 253 -250 239 -230 251 -280 219 -252 247 -250 279 -220 251 -572 269 -236 265 -222 251 -280 219 -252 249 -250 277 -222 251 -250 247 -250 247 -280 217 -252 249 -250 279 -220 251 -250 249 -280 219 -252 249 -250 215 -280 279 -220 251 -250 247 -280 219 -252 249 -250 279 -220 251 -250 249 -248 247 -280 219 -252 249 -248 279 -220 251 -252 247 -248 503 -258 249 -502 257 -216 277 -214 531 -228 279 -502 237 -254 253 -256 253 -216 271 -260 223 -254 505 -260 217 -500 513 -274 251 -256 253 -218 247 -498 483 -546 479 -238 289 -220 253 -288 209 -268 223 -250 247 -252 249 -250 249 -278 219 -574 265 -268 199 -292 219 -250 279 -220 251 -250 249 -248 247 -280 219 -252 249 -248 279 -222 251 -250 247 -248 249 -278 219 -252 249 -250 279 -220 251 -250 249 -278 221 -252 249 -250 215 -280 279 -220 249 -250 249 -280 219 -252 249 -250 279 -220 251 -250 249 -248 247 -280 219 -252 505 -258 219 -498 257 -276 215 -278 503 -238 249 -504 245 -254 253 -280 203 -260 219 -280 215 -278 503 -260 215 -532 515 -238 253 -254 255 -254 213 -506 511 -516 477 -272 215 -292 217 -254 279 -204 261 -250 249 -246 247 -278 219 -252 249 -602 205 -272 229 -256 281 -220 249 -250 249 -280 219 -252 249 -250 279 -220 251 -250 249 -248 247 -280 219 -252 249 -248 281 -220 249 -250 249 -248 247 -280 219 -252 249 -250 279 -220 251 -250 249 -280 219 -250 249 -250 217 -280 247 -252 249 -250 249 -280 219 -252 249 -248 281 -220 507 -258 215 -530 227 -280 213 -276 499 -258 215 -532 225 -280 213 -276 215 -276 247 -250 249 -248 503 -258 251 -502 479 -272 251 -254 219 -290 247 -474 515 -482 481 -274 253 -256 253 -252 239 -230 253 -280 219 -252 247 -250 279 -220 251 -572 269 -236 263 -224 251 -280 219 -252 249 -250 215 -280 279 -220 249 -250 249 -280 219 -252 249 -250 279 -220 251 -250 249 -248 247 -280 219 -250 249 -250 279 -220 251 -250 249 -248 247 -280 219 -252 249 -248 279 -220 253 -250 247 -280 219 -252 249 -250 217 -280 247 -250 251 -250 535 -226 251 -502 257 -216 277 -216 497 -272 287 -464 245 -288 253 -212 271 -224 251 -280 219 -250 507 -228 247 -528 +RAW_Data: 477 -274 251 -254 255 -218 283 -472 481 -516 515 -236 255 -254 255 -250 245 -266 223 -250 217 -278 245 -252 249 -250 249 -570 267 -238 229 -288 221 -254 249 -250 279 -220 251 -250 247 -280 221 -252 249 -248 217 -278 279 -220 251 -250 247 -282 219 -252 249 -250 277 -222 251 -250 247 -250 247 -280 219 -250 249 -250 279 -220 251 -250 249 -280 219 -252 249 -250 217 -278 247 -252 251 -250 247 -280 219 -252 507 -228 247 -530 255 -216 275 -246 503 -228 245 -530 257 -214 275 -216 275 -246 249 -248 249 -248 503 -260 217 -532 515 -238 253 -254 253 -256 213 -506 511 -516 477 -272 215 -292 217 -254 245 -234 255 -252 249 -250 249 -278 219 -252 249 -602 205 -272 229 -256 281 -220 251 -250 249 -248 249 -278 219 -252 249 -250 279 -220 249 -250 249 -280 219 -254 249 -248 217 -280 277 -220 251 -250 247 -280 221 -252 249 -250 279 -220 251 -250 249 -248 247 -280 219 -250 249 -250 279 -220 251 -250 247 -280 221 -252 249 -248 249 -248 503 -258 217 -532 227 -280 213 -278 499 -258 215 -500 271 -288 217 -254 255 -248 241 -234 259 -250 469 -306 249 -464 501 -280 215 -284 207 -262 251 -508 515 -480 481 -274 253 -254 253 -254 241 -230 253 -282 219 -250 249 -248 217 -278 247 -576 269 -240 265 -222 251 -248 249 -278 219 -252 249 -248 279 -220 251 -250 249 -248 249 -278 219 -252 249 -248 281 -220 249 -250 249 -280 219 -252 249 -250 217 -280 247 -250 251 -250 247 -280 219 -254 249 -248 281 -220 251 -250 249 -248 247 -280 219 -250 249 -250 279 -220 251 -250 535 -228 249 -502 225 -246 275 -248 465 -290 245 -504 225 -276 245 -246 247 -248 247 -246 247 -278 477 -228 279 -500 477 -274 251 -254 255 -254 247 -474 483 -514 515 -238 253 -256 253 -250 239 -262 223 -254 249 -250 279 -220 249 -250 249 -570 267 -238 229 -288 221 -254 249 -250 279 -220 251 -250 249 -248 247 -280 219 -250 249 -250 279 -220 251 -250 249 -280 219 -252 249 -250 217 -278 247 -252 249 -250 249 -280 219 -252 249 -250 217 -278 279 -220 251 -248 249 -280 221 -252 249 -250 279 -220 251 -250 249 -248 247 -280 477 -228 277 -500 257 -216 275 -246 505 -228 245 -530 257 -214 275 -216 275 -246 249 -250 247 -250 503 -258 217 -498 543 -226 243 -246 243 -278 247 -500 479 -546 477 -238 255 -290 217 -252 245 -232 283 -222 249 -250 249 -248 247 -280 217 -608 239 -238 229 -256 219 -280 247 -250 251 -250 247 -280 219 -254 249 -248 281 -220 251 -250 +RAW_Data: 247 -250 247 -280 219 -250 249 -250 279 -220 251 -250 249 -280 219 -252 249 -250 215 -280 247 -252 249 -250 249 -280 219 -252 249 -250 217 -278 279 -220 251 -250 247 -280 219 -252 249 -250 505 -258 217 -500 255 -278 215 -278 501 -260 215 -498 257 -276 215 -278 215 -278 215 -278 247 -252 505 -258 245 -504 481 -272 251 -254 217 -292 213 -532 485 -480 511 -274 215 -292 217 -252 243 -236 259 -250 249 -278 219 -252 249 -248 279 -546 235 -268 259 -226 255 -250 249 -278 221 -250 249 -250 215 -280 279 -220 251 -250 247 -280 219 -254 249 -248 281 -220 251 -250 249 -248 247 -280 219 -250 249 -250 279 -220 251 -250 249 -280 219 -252 249 -250 215 -280 247 -252 249 -250 249 -280 219 -252 249 -250 279 -220 251 -250 471 -288 247 -502 227 -276 245 -248 469 -292 213 -530 225 -278 213 -276 245 -250 247 -248 249 -278 477 -228 279 -500 477 -256 245 -242 277 -246 247 -502 479 -512 475 -274 291 -218 255 -250 241 -262 223 -254 249 -250 247 -252 249 -250 249 -570 267 -238 231 -286 221 -254 251 -248 217 -278 247 -252 251 -250 247 -280 219 -252 249 -250 217 -278 247 -252 251 -250 247 -280 221 -252 249 -248 279 -220 253 -250 247 -248 249 -278 219 -252 249 -250 279 -220 251 -250 247 -280 221 -252 249 -248 217 -280 247 -250 251 -250 249 -280 477 -228 279 -500 257 -214 275 -216 529 -228 279 -500 257 -216 275 -216 275 -216 277 -278 219 -250 505 -258 217 -500 547 -238 251 -254 255 -218 245 -502 481 -544 479 -236 291 -254 217 -292 213 -268 221 -250 247 -252 249 -250 249 -280 219 -572 265 -268 199 -292 219 -250 279 -222 251 -250 247 -282 219 -252 diff --git a/applications/debug/unit_tests/resources/unit_tests/subghz/roger.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/roger.sub new file mode 100644 index 000000000..2304b43d2 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/subghz/roger.sub @@ -0,0 +1,7 @@ +Filetype: Flipper SubGhz Key File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: Roger +Bit: 28 +Key: 00 00 00 00 05 AB A1 01 diff --git a/applications/debug/unit_tests/resources/unit_tests/subghz/roger_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/roger_raw.sub new file mode 100644 index 000000000..0827fb0d2 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/subghz/roger_raw.sub @@ -0,0 +1,23 @@ +Filetype: Flipper SubGhz RAW File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: RAW +RAW_Data: 22710 -100 52363 -100 323 -66 1865 -66 32843 -66 753 -100 461 -100 19881 -66 16113 -66 20557 -66 6287 -64 25981 -100 587 -66 24707 -66 2367 -66 4681 -66 291 -98 4567 -100 1001 -100 33935 -66 20561 -100 5745 -66 2563 -66 4245 -66 22121 -66 1517 -100 2895 -66 15919 -66 20885 -100 12513 -890 839 -458 415 -914 843 -452 837 -482 387 -928 849 -446 415 -900 843 -474 415 -902 843 -470 841 -470 843 -468 383 -914 845 -460 415 -914 409 -884 423 -902 411 -902 835 -482 411 -880 423 -902 411 -908 397 -898 405 -920 411 -880 441 -896 835 -8268 411 -888 877 -430 415 -908 841 -460 869 -434 413 -920 845 -458 415 -912 845 -454 409 -884 879 -452 869 -428 873 -434 413 -904 871 -434 413 -918 415 -874 441 -882 425 -900 835 -448 443 -880 423 -904 409 -902 409 -904 409 -910 399 -896 417 -894 851 -8248 421 -912 853 -446 427 -872 863 -444 867 -446 441 -876 867 -456 415 -882 879 -452 419 -880 853 -448 881 -450 835 -460 415 -918 845 -454 409 -884 419 -906 411 -910 397 -898 865 -446 453 -860 421 -884 453 -876 415 -876 447 -878 441 -880 437 -882 861 -8216 457 -882 853 -456 405 -884 889 -420 879 -448 413 -890 873 -432 443 -892 841 -456 409 -922 843 -458 841 -460 873 -432 413 -918 843 -456 409 -918 387 -906 409 -912 427 -870 863 -446 439 -874 437 -888 409 -880 439 -898 419 -888 421 -880 453 -876 843 -8232 449 -874 891 -418 451 -878 865 -454 849 -448 413 -902 843 -474 411 -888 877 -432 413 -920 843 -454 869 -432 875 -432 413 -920 845 -458 415 -884 421 -910 395 -900 439 -888 839 -454 451 -880 419 -878 449 -876 415 -876 445 -878 439 -880 435 -870 867 -8246 423 -888 849 -454 445 -880 863 -418 879 -446 413 -896 875 -444 411 -902 873 -442 411 -904 839 -472 837 -472 837 -474 413 -874 871 -438 443 -892 415 -874 441 -878 439 -878 867 -454 417 -882 425 -908 399 -896 419 -896 419 -882 453 -876 413 -878 871 -8228 451 -876 853 -442 437 -904 841 -450 855 -450 449 -886 839 -474 411 -890 843 -460 415 -886 879 -456 843 -458 875 -428 409 -906 869 -436 413 -920 417 -884 423 -874 435 -892 857 -456 417 -880 453 -876 413 -910 413 -878 439 -870 439 -872 439 -870 871 -8248 421 -892 851 -454 445 -880 861 -452 843 -448 413 -896 877 -444 413 -898 853 -450 417 -898 877 -446 841 -474 839 -472 411 -892 843 -460 409 -918 417 -882 423 -872 435 -896 869 -418 453 -888 421 -878 451 -876 415 -876 443 -878 441 -876 437 -882 861 -8236 423 -878 883 -454 401 -884 +RAW_Data: 857 -450 881 -446 413 -888 873 -434 415 -906 875 -428 411 -904 869 -434 873 -438 875 -436 413 -906 869 -434 413 -906 411 -904 411 -906 415 -908 835 -460 411 -906 409 -904 411 -908 417 -874 411 -910 415 -914 421 -884 849 -8232 453 -870 853 -440 439 -902 841 -450 885 -452 413 -870 861 -452 447 -862 877 -446 411 -900 877 -444 839 -474 839 -472 411 -902 841 -470 409 -890 415 -882 421 -910 425 -872 863 -446 451 -866 417 -906 417 -906 419 -868 451 -870 415 -900 413 -904 841 -8244 439 -886 855 -456 415 -908 853 -444 861 -452 421 -886 851 -452 447 -860 877 -448 413 -898 875 -444 873 -442 837 -474 409 -904 839 -472 409 -870 441 -898 421 -886 413 -890 873 -434 443 -890 417 -882 423 -876 433 -896 417 -894 421 -880 453 -876 843 -8254 435 -870 865 -448 409 -904 869 -454 843 -458 415 -912 843 -456 409 -914 835 -458 409 -920 841 -456 867 -432 873 -434 443 -888 845 -460 415 -910 417 -882 425 -872 435 -892 867 -448 407 -898 409 -902 417 -898 419 -880 453 -874 415 -908 413 -878 871 -8236 417 -904 845 -454 451 -880 863 -420 881 -448 413 -902 841 -476 411 -892 873 -430 415 -918 843 -456 875 -426 869 -434 443 -890 841 -458 409 -920 415 -882 425 -870 439 -904 835 -450 421 -912 427 -872 437 -888 419 -888 421 -882 453 -876 413 -880 871 -8234 457 -848 881 -444 435 -880 867 -450 847 -448 415 -920 841 -470 411 -888 845 -460 415 -916 847 -454 835 -460 871 -466 381 -920 843 -458 409 -916 417 -878 411 -912 425 -870 887 -418 445 -878 419 -914 419 -876 415 -908 413 -876 443 -880 441 -880 863 -8232 423 -880 881 -454 401 -904 865 -450 847 -446 415 -898 875 -444 411 -902 839 -476 411 -902 839 -474 837 -472 839 -472 411 -888 875 -430 415 -908 415 -908 409 -882 421 -904 837 -450 421 -910 425 -872 451 -868 419 -882 451 -874 413 -906 413 -902 843 -8256 419 -892 851 -472 405 -906 837 -480 821 -482 417 -904 823 -482 387 -918 843 -472 413 -884 845 -462 875 -432 875 -468 381 -916 843 -460 415 -914 387 -908 395 -938 399 -892 863 -444 409 -902 405 -918 409 -898 407 -920 411 -878 417 -918 417 -880 839 -8254 439 -898 837 -448 411 -906 869 -454 845 -460 415 -882 881 -450 409 -914 845 -452 409 -914 843 -456 843 -458 843 -462 415 -914 843 -456 413 -912 387 -910 423 -908 415 -870 873 -460 413 -910 381 -906 411 -906 409 -906 411 -904 411 -904 411 -904 839 -8254 409 -882 865 -446 439 -874 889 -418 873 -458 417 -878 885 -454 +RAW_Data: 403 -884 857 -482 385 -918 843 -474 843 -468 839 -470 411 -906 837 -468 411 -904 379 -904 413 -900 415 -918 841 -474 381 -918 417 -912 385 -912 395 -900 451 -866 419 -918 417 -878 843 -8274 397 -896 865 -446 411 -904 871 -452 845 -458 417 -874 869 -458 415 -882 879 -454 417 -882 881 -448 837 -458 869 -432 413 -906 869 -434 413 -906 411 -904 411 -904 411 -904 869 -432 415 -916 387 -910 425 -908 415 -868 415 -908 415 -906 415 -876 879 -8236 409 -912 837 -454 451 -882 845 -452 875 -452 417 -890 849 -446 415 -896 877 -446 411 -900 841 -474 841 -470 839 -472 411 -904 839 -472 413 -886 415 -916 387 -904 411 -908 853 -448 409 -912 395 -900 435 -892 409 -916 407 -896 411 -912 409 -894 835 -8258 411 -890 871 -436 445 -888 841 -462 873 -430 413 -920 843 -456 409 -906 869 -430 415 -920 843 -454 869 -432 873 -434 413 -906 865 -470 411 -888 409 -884 449 -882 419 -890 845 -486 411 -870 437 -874 439 -870 439 -902 409 -902 409 -902 409 -902 839 -8252 405 -896 865 -446 441 -876 869 -454 845 -458 415 -882 879 -454 417 -882 879 -418 441 -882 881 -450 845 -456 867 -432 413 -906 869 -432 411 -906 409 -920 415 -884 425 -872 893 -412 439 -912 399 -892 409 -902 417 -900 421 -880 451 -878 413 -876 877 -8222 457 -886 845 -440 441 -880 869 -450 849 -450 415 -922 839 -472 411 -890 841 -462 413 -884 879 -452 869 -432 873 -434 413 -920 843 -458 409 -886 419 -912 417 -880 453 -882 847 -454 443 -880 399 -882 433 -884 427 -916 399 -886 429 -884 429 -886 853 -8258 423 -878 879 -454 403 -884 859 -452 877 -446 413 -888 873 -434 445 -890 843 -460 413 -884 879 -456 845 -458 867 -430 415 -920 843 -454 409 -920 385 -912 427 -872 435 -890 855 -456 419 -878 453 -876 415 -878 445 -878 439 -878 439 -880 401 -914 861 -8224 419 -906 845 -470 407 -902 841 -450 887 -418 451 -884 841 -474 413 -888 875 -432 413 -908 843 -460 867 -434 873 -436 443 -874 869 -436 443 -874 411 -920 415 -884 425 -874 893 -412 435 -894 441 -870 451 -862 419 -880 453 -876 417 -910 413 -878 869 -8234 457 -848 879 -456 403 -902 865 -452 847 -448 413 -896 877 -444 411 -900 873 -444 411 -902 841 -474 841 -472 839 -472 411 -890 845 -462 413 -884 421 -912 425 -872 437 -888 871 -418 445 -878 419 -914 419 -878 415 -910 413 -876 443 -880 439 -880 863 -8226 419 -904 841 -472 417 -872 879 -442 861 -450 423 -884 857 -450 451 -858 879 -448 411 -898 875 -444 +RAW_Data: 841 -474 837 -474 411 -902 839 -472 411 -902 411 -904 409 -872 443 -898 845 -448 415 -900 413 -898 425 -886 425 -888 425 -886 425 -886 423 -898 863 -8226 451 -872 877 -434 409 -904 873 -416 891 -420 453 -886 841 -474 411 -888 873 -430 413 -908 843 -460 869 -432 873 -436 445 -874 869 -436 443 -890 415 -884 423 -902 409 -904 867 -418 419 -912 425 -872 435 -894 409 -914 407 -898 411 -880 447 -880 851 -8230 457 -868 879 -452 417 -868 869 -452 849 -450 449 -858 877 -448 411 -896 877 -444 413 -900 875 -446 841 -438 875 -438 445 -890 845 -460 415 -882 421 -912 395 -898 439 -870 867 -450 425 -910 399 -894 405 -920 413 -878 419 -914 419 -878 415 -910 843 -8232 457 -886 845 -442 439 -876 869 -454 855 -448 415 -886 875 -438 443 -876 875 -434 413 -904 871 -432 873 -434 873 -436 445 -874 869 -436 413 -920 417 -880 423 -902 411 -904 867 -418 421 -912 427 -868 437 -890 409 -916 413 -876 419 -914 417 -876 879 -8234 423 -888 843 -456 449 -880 863 -420 881 -448 413 -902 843 -476 413 -888 877 -430 413 -886 877 -454 867 -432 873 -434 413 -918 845 -460 415 -882 423 -908 397 -898 437 -890 855 -426 447 -874 453 -876 415 -878 447 -878 441 -878 437 -880 435 -880 859 -8212 457 -884 847 -454 445 -880 863 -420 881 -448 411 -902 875 -442 411 -902 839 -474 411 -888 875 -430 875 -432 875 -432 413 -922 843 -458 417 -884 421 -910 397 -898 453 -864 879 -454 415 -878 439 -878 435 -880 435 -882 395 -902 437 -868 437 -870 871 -8244 421 -894 845 -470 409 -908 843 -456 857 -448 415 -890 871 -436 445 -890 843 -460 415 -882 881 -452 869 -428 867 -436 443 -888 841 -460 409 -918 417 -882 453 -844 447 -876 853 -454 449 -880 435 -878 403 -916 397 -914 397 -884 431 -884 429 -884 889 -8228 425 -878 879 -422 435 -886 855 -452 877 -448 413 -888 877 -432 413 -886 877 -454 409 -886 891 -446 855 -444 867 -456 415 -882 851 -448 439 -884 425 -902 409 -874 435 -892 867 -444 409 -910 427 -872 437 -888 411 -880 439 -892 419 -890 421 -878 879 -8234 421 -894 881 -434 409 -902 841 -456 859 -450 415 -900 885 -420 451 -886 839 -472 411 -900 847 -448 877 -446 841 -474 411 -890 845 -460 415 -884 421 -906 409 -912 397 -898 889 -420 447 -876 421 -880 455 -880 411 -878 443 -878 439 -880 435 -882 861 -8210 437 -896 865 -442 407 -908 867 -452 845 -458 415 -912 843 -454 409 -884 879 -452 407 -908 867 -432 873 -432 873 -432 415 -904 869 -434 +RAW_Data: 415 -904 411 -906 409 -906 409 -906 869 -430 415 -908 415 -880 439 -876 411 -906 411 -912 395 -898 437 -890 851 -8232 443 -890 845 -460 415 -884 879 -454 843 -458 415 -884 879 -454 409 -884 879 -448 409 -884 879 -454 841 -458 869 -432 413 -904 869 -434 413 -906 411 -904 409 -908 417 -882 881 -450 409 -880 441 -876 411 -910 425 -109718 133 -700 197 -334 133 -132 133 -166 131 -134 167 -200 427 -234 1129 -132 923 -100 22805 -66 22225 -66 655 -64 24395 -98 5203 -98 15817 -66 5793 -98 819 -98 4477 -66 12519 -68 64793 -66 425 -100 3651 -66 10365 -66 2361 -100 697 -66 13695 -100 15599 -66 1829 -100 21103 -66 40353 -64 1751 -98 14265 -132 54383 -66 2653 -98 13411 -66 15223 -66 8943 -892 857 -432 443 -876 843 -480 857 -452 421 -882 843 -474 413 -886 879 -432 415 -916 845 -454 837 -464 875 -432 415 -914 845 -458 411 -914 389 -908 427 -874 433 -894 867 -450 409 -898 409 -916 409 -896 417 -888 417 -880 449 -876 413 -908 839 -8254 415 -896 853 -470 407 -880 875 -450 853 -450 415 -920 843 -470 411 -872 869 -470 413 -888 843 -462 875 -430 875 -432 415 -918 847 -458 417 -880 425 -908 415 -904 407 -896 835 -456 443 -878 419 -882 449 -878 413 -908 413 -876 441 -876 439 -878 867 -8218 457 -886 845 -452 445 -876 865 -450 847 -450 417 -900 877 -444 413 -870 871 -440 441 -870 873 -438 873 -440 873 -438 443 -870 881 -446 417 -902 413 -902 417 -870 429 -886 891 -416 387 -978 355 -964 353 -930 387 -938 353 -942 385 -940 383 -912 845 -8258 421 -876 879 -422 469 -848 893 -420 843 -506 353 -944 813 -502 383 -912 849 -460 419 -904 837 -460 871 -466 843 -466 381 -900 875 -446 419 -912 383 -934 383 -914 415 -886 843 -462 411 -904 417 -916 417 -882 383 -964 343 -986 307 -982 345 -980 767 -8306 375 -934 839 -484 377 -932 837 -458 839 -466 407 -910 841 -478 395 -916 861 -446 421 -886 851 -448 877 -446 809 -536 353 -962 779 -502 385 -930 383 -936 387 -926 385 -926 815 -474 417 -914 381 -914 417 -912 419 -882 397 -900 405 -918 409 -898 861 -8248 437 -878 861 -450 419 -886 851 -448 877 -444 413 -884 873 -472 411 -886 845 -460 411 -916 847 -454 871 -430 877 -434 415 -916 845 -460 417 -880 423 -904 411 -908 399 -898 853 -454 415 -914 415 -882 415 -908 411 -910 409 -880 439 -878 435 -872 867 -8234 451 -864 875 -428 443 -876 875 -454 865 -482 355 -926 819 -504 355 -940 813 -504 385 -944 815 -488 819 -486 807 -492 387 -934 841 -458 +RAW_Data: 413 -914 389 -904 393 -932 411 -906 825 -480 381 -930 413 -908 413 -866 413 -914 415 -884 449 -876 411 -912 803 -8306 381 -934 827 -480 381 -932 709 -24288 131 -15484 407 -916 799 -514 373 -914 829 -500 793 -510 393 -910 855 -448 387 -912 881 -444 383 -948 789 -524 791 -524 825 -490 375 -936 811 -456 407 -920 411 -918 373 -946 373 -920 831 -478 399 -918 395 -916 395 -914 395 -914 427 -882 431 -880 397 -918 893 -8186 479 -826 881 -438 265 -5122 99 -604 291 -990 785 -540 321 -958 815 -504 813 -472 849 -440 411 -916 845 -450 437 -900 411 -888 415 -916 413 -884 841 -478 393 -916 431 -882 431 -878 431 -878 431 -872 437 -876 437 -876 863 -8226 451 -890 849 -488 369 -920 825 -478 819 -510 385 -912 845 -470 383 -908 845 -476 415 -884 845 -464 877 -432 875 -436 417 -914 843 -460 417 -906 411 -880 425 -908 397 -900 849 -454 417 -914 417 -878 415 -910 411 -912 407 -878 439 -878 439 -876 865 -8228 437 -894 853 -428 449 -878 881 -442 857 -454 423 -888 883 -418 415 -890 875 -438 445 -888 845 -456 869 -432 875 -436 445 -868 869 -438 445 -878 447 -870 409 -886 451 -874 865 -420 441 -886 421 -912 429 -870 441 -854 455 -852 455 -878 451 -840 907 -8212 437 -876 857 -456 415 -918 845 -454 867 -450 417 -886 853 -446 413 -920 843 -440 441 -872 871 -438 871 -472 839 -472 411 -888 843 -464 415 -904 409 -910 417 -876 409 -912 853 -446 411 -906 411 -910 427 -870 453 -866 415 -910 419 -878 449 -876 843 -8242 453 -868 873 -450 415 -880 867 -446 849 -484 383 -918 843 -474 409 -906 841 -470 411 -886 843 -464 875 -432 875 -434 415 -914 845 -458 417 -884 423 -902 411 -906 397 -898 867 -446 409 -906 407 -918 407 -898 409 -900 409 -918 407 -898 417 -888 845 -8236 453 -872 849 -486 411 -872 871 -446 853 -482 417 -890 847 -446 413 -896 875 -446 413 -900 851 -448 875 -444 875 -442 413 -898 841 -474 411 -902 411 -902 411 -904 413 -886 843 -462 417 -906 417 -882 423 -906 397 -900 407 -922 417 -890 419 -880 875 -8242 407 -904 867 -420 421 -906 869 -456 835 -462 409 -918 843 -460 409 -884 879 -452 411 -906 835 -462 869 -434 873 -438 445 -870 871 -438 413 -920 417 -878 423 -902 409 -906 857 -448 429 -872 437 -892 419 -892 419 -880 451 -874 413 -878 441 -868 873 -8250 421 -890 853 -438 439 -878 873 -452 855 -450 415 -888 873 -438 445 -888 843 -462 415 -882 883 -452 835 -462 869 -434 415 -918 843 -458 409 -908 417 -874 +RAW_Data: 439 -874 441 -876 869 -454 409 -908 415 -880 443 -880 427 -872 437 -894 409 -882 449 -878 877 -8236 415 -878 881 -452 411 -880 883 -450 837 -460 411 -916 847 -456 409 -914 845 -456 419 -880 851 -452 881 -452 837 -460 413 -914 843 -458 411 -916 387 -910 425 -906 415 -868 875 -460 415 -878 415 -908 413 -910 409 -878 439 -878 437 -880 437 -880 863 -8226 449 -878 853 -452 443 -878 861 -450 847 -450 415 -920 841 -438 445 -888 843 -462 415 -882 879 -454 867 -432 875 -432 415 -916 845 -460 417 -880 423 -904 409 -912 397 -900 867 -448 419 -890 419 -882 451 -876 415 -912 409 -880 439 -880 435 -872 867 -8242 409 -914 835 -442 451 -870 875 -460 845 -440 437 -902 839 -446 425 -904 863 -452 423 -888 851 -448 879 -444 845 -474 413 -886 843 -462 417 -916 387 -912 427 -872 451 -870 877 -432 441 -872 445 -872 411 -904 411 -904 409 -906 409 -904 411 -902 839 -8242 453 -866 877 -450 413 -874 871 -448 851 -484 383 -920 843 -472 413 -888 845 -462 415 -882 881 -452 837 -460 871 -436 413 -920 845 -460 415 -882 423 -902 411 -910 395 -902 861 -444 441 -874 437 -894 413 -882 419 -912 419 -880 415 -908 413 -880 869 -8228 457 -884 845 -442 435 -878 871 -452 853 -450 415 -896 881 -444 413 -904 841 -474 411 -884 879 -430 875 -432 875 -434 415 -904 869 -432 445 -888 417 -876 411 -906 411 -904 867 -452 411 -906 409 -882 421 -910 397 -900 451 -864 419 -882 449 -878 873 -8238 421 -890 843 -452 447 -878 865 -450 847 -450 415 -894 881 -444 413 -902 841 -474 413 -890 841 -462 869 -436 873 -436 415 -918 845 -462 409 -904 409 -916 387 -912 427 -872 863 -446 441 -872 451 -866 419 -912 419 -880 413 -908 413 -876 443 -880 867 -8224 447 -880 879 -430 445 -876 877 -422 859 -452 449 -860 875 -448 415 -898 851 -450 417 -894 875 -448 877 -446 841 -440 443 -890 877 -428 409 -904 411 -884 453 -880 425 -872 861 -446 441 -874 453 -866 415 -908 419 -880 451 -876 415 -876 443 -878 869 -8224 455 -884 843 -468 407 -906 841 -448 887 -448 417 -892 849 -448 415 -918 839 -472 413 -886 875 -432 845 -462 873 -432 411 -906 873 -434 415 -920 417 -880 425 -904 399 -898 865 -450 407 -902 415 -894 419 -912 417 -878 413 -908 413 -910 409 -878 869 -8234 449 -874 877 -450 413 -872 869 -448 883 -448 413 -872 889 -450 417 -890 847 -448 415 -920 841 -438 877 -436 875 -436 441 -874 873 -434 441 -872 441 -868 451 -884 413 -900 839 -476 413 -902 +RAW_Data: 411 -886 417 -884 421 -910 397 -900 451 -864 415 -910 843 -8248 445 -858 879 -458 417 -882 881 -418 879 -454 409 -882 881 -452 411 -882 883 -418 443 -874 867 -458 871 -434 875 -434 415 -918 845 -460 417 -878 421 -908 427 -872 451 -864 879 -450 417 -880 439 -880 437 -878 435 -880 401 -914 429 -870 435 -870 867 -8252 411 -886 877 -430 447 -876 879 -414 887 -452 419 -886 851 -448 447 -860 877 -446 413 -896 877 -444 873 -444 871 -442 409 -902 839 -474 409 -900 411 -902 409 -900 411 -900 875 -442 411 -902 409 -902 409 -904 409 -900 421 -888 423 -888 423 -890 849 -8264 421 -878 877 -454 401 -884 857 -452 879 -446 411 -900 875 -444 411 -900 839 -474 411 -900 839 -474 839 -474 839 -472 409 -906 839 -472 411 -872 445 -888 417 -874 441 -882 851 -450 423 -912 427 -868 439 -888 411 -882 453 -884 419 -876 449 -874 841 -8238 453 -888 843 -470 407 -904 839 -446 887 -450 415 -904 825 -482 417 -890 849 -448 415 -898 877 -446 845 -474 843 -470 381 -918 845 -460 417 -882 421 -908 427 -872 451 -866 873 -444 413 -908 417 -878 449 -876 413 -908 411 -876 441 -876 439 -878 865 -8234 417 -906 851 -440 437 -904 839 -448 885 -448 419 -890 847 -446 415 -900 857 -450 417 -918 843 -474 841 -472 843 -468 383 -916 869 -430 411 -904 411 -904 411 -902 411 -904 869 -430 443 -870 411 -904 411 -904 411 -902 411 -904 411 -906 409 -906 837 -8266 413 -904 873 -414 453 -870 867 -450 883 -414 447 -860 875 -448 411 -888 873 -440 441 -874 875 -436 873 -436 871 -436 443 -874 875 -436 443 -890 415 -876 423 -910 395 -902 863 -448 441 -872 439 -888 421 -888 419 -880 451 -874 413 -910 411 -874 869 -8234 447 -876 875 -432 445 -874 877 -454 829 -450 449 -890 847 -446 413 -898 877 -444 411 -904 843 -472 841 -474 843 -470 413 -868 875 -436 445 -888 417 -880 423 -910 415 -874 877 -428 447 -876 449 -874 413 -910 411 -876 441 -878 439 -880 437 -878 863 -8232 417 -910 845 -470 405 -904 839 -478 855 -448 421 -882 845 -476 415 -884 875 -432 415 -916 845 -456 839 -464 875 -434 417 -914 845 -458 409 -916 387 -912 425 -906 399 -894 853 -456 413 -916 415 -880 413 -910 411 -912 409 -880 435 -874 437 -872 865 -8248 453 -866 879 -402 475 -842 907 -422 859 -450 415 -886 873 -438 443 -866 879 -446 415 -896 877 -446 873 -444 841 -474 411 -890 843 -462 415 -882 421 -912 427 -872 437 -892 857 -428 447 -880 449 -878 411 -904 409 -872 441 -872 +RAW_Data: 441 -872 441 -872 871 -8252 413 -880 865 -446 439 -872 899 -416 867 -456 409 -904 869 -432 415 -886 877 -460 407 -904 867 -432 877 -434 875 -434 415 -916 845 -460 417 -882 423 -902 411 -902 411 -910 857 -444 411 -904 409 -904 411 -904 409 -902 411 -908 399 -894 407 -922 837 -8230 443 -902 873 -444 411 -898 871 -442 839 -442 443 -890 875 -432 415 -914 845 -456 409 -912 837 -462 845 -462 871 -432 415 -914 843 -462 409 -918 419 -878 409 -904 411 -902 869 -450 411 -880 423 -904 411 -904 409 -910 397 -896 407 -922 417 -890 851 -8226 463 -868 867 -448 415 -882 891 -420 879 -450 419 -886 851 -450 417 -920 839 -474 411 -890 877 -430 875 -432 877 -430 415 -904 869 -434 413 -906 409 -916 407 -906 409 -882 879 -418 443 -882 421 -912 397 -900 453 -862 421 -878 453 -874 449 -878 841 -8230 449 -876 879 -426 449 -876 881 -416 889 -418 447 -890 847 -450 449 -858 877 -446 413 -900 883 -418 879 -446 845 -474 409 -902 839 -474 409 -902 409 -900 411 -900 411 -888 873 -436 413 -920 417 -882 425 -870 453 -868 417 -906 415 -906 417 -874 875 -8238 425 -876 893 -418 445 -880 875 -440 877 -408 447 -874 877 -442 433 -870 871 -450 423 -882 889 -420 879 -446 877 -444 413 -900 839 -476 411 -888 415 -882 421 -912 429 -870 865 -446 453 -864 419 -882 449 -870 417 -910 411 -906 411 -904 407 -904 837 -8248 457 -864 875 -408 449 -874 881 -440 863 -454 427 -888 881 -416 417 -896 875 -446 413 -900 877 -444 841 -440 873 -440 445 -890 843 -460 415 -884 453 -880 429 -868 451 -866 875 -442 415 -874 449 -872 449 -872 447 -870 413 -902 413 -904 409 -904 873 -8206 449 -880 879 -452 415 -880 865 -450 845 -448 415 -894 875 -446 413 -902 853 -448 449 -858 877 -446 877 -444 875 -444 411 -888 877 -426 441 -874 409 -920 417 -880 425 -904 835 -448 441 -882 423 -904 407 -876 433 -900 409 -884 439 -896 411 -882 889 -8232 413 -886 867 -460 415 -884 881 -420 867 -462 415 -916 845 -454 409 -908 843 -460 409 -918 845 -454 869 -432 877 -432 413 -918 845 -460 417 -880 441 -880 451 -848 453 -888 851 -442 433 -880 439 -878 437 -880 435 -882 397 -902 435 -872 437 -874 869 -8244 441 -854 909 -420 415 -906 847 -440 877 -442 433 -870 871 -448 423 -882 891 -450 419 -886 851 -450 879 -446 843 -472 413 -872 875 -470 381 -918 417 -880 425 -906 399 -900 867 -448 409 -920 413 -880 417 -914 417 -878 415 -908 413 -880 439 -878 869 -8228 457 -852 +RAW_Data: 883 -456 405 -882 889 -420 883 -448 415 -888 875 -434 445 -872 869 -436 443 -892 843 -458 869 -432 873 -434 445 -870 871 -436 445 -874 441 -886 415 -876 441 -882 885 -418 425 -902 409 -912 397 -898 419 -894 421 -882 451 -880 413 -876 875 -8222 447 -884 855 -452 449 -878 865 -454 849 -450 413 -902 875 -444 413 -888 875 -432 415 -908 843 -462 875 -430 867 -436 445 -890 843 -458 407 -888 451 -874 411 -906 409 -912 855 -448 429 -874 437 -892 413 -880 455 -880 419 -876 449 -878 413 -876 871 -8238 449 -876 879 -420 449 -878 863 -420 875 -448 413 -894 877 -446 413 -898 875 -444 411 -898 875 -444 839 -474 839 -474 409 -888 877 -432 415 -918 417 -880 411 -884 447 -880 877 -452 417 -876 439 -878 437 -878 437 -878 435 -878 435 -880 433 -848 893 -8224 457 -848 885 -456 437 -870 865 -420 879 -450 445 -862 877 -448 413 -890 873 -432 441 -876 869 -436 869 -438 867 -440 441 -900 847 -450 415 -900 413 -890 445 -890 417 -882 851 -450 421 -910 397 -900 439 -888 409 -886 453 -880 419 -880 449 -876 841 -8234 447 -878 891 -432 443 -876 843 -456 891 -418 415 -898 885 -420 451 -854 879 -450 413 -890 873 -438 875 -436 873 -436 443 -892 843 -458 409 -886 451 -874 439 -884 427 -872 891 -420 451 -852 453 -878 449 -842 445 -878 441 -880 439 -880 431 -870 869 -8242 435 -874 857 -458 413 -910 845 -440 865 -454 431 -884 887 -418 449 -886 841 -472 413 -888 877 -430 869 -434 875 -438 413 -918 843 -460 415 -906 409 -910 387 -912 425 -870 867 -450 425 -904 409 -904 411 -904 409 -908 415 -874 419 -914 417 -880 875 -8236 437 -884 855 -444 441 -880 853 -446 869 -446 429 -872 887 -418 447 -878 877 -430 445 -876 875 -420 895 -420 879 -448 413 -902 875 -444 411 -892 415 -884 421 -912 399 -900 867 -448 455 -860 423 -876 453 -872 449 -844 443 -870 443 -870 443 -870 873 -8232 451 -874 889 -402 441 -876 877 -456 853 -452 413 -896 877 -448 413 -890 875 -434 413 -884 879 -460 875 -426 869 -434 445 -892 845 -458 415 -886 423 -908 417 -872 455 -882 845 -440 433 -904 411 -878 443 -880 437 -882 435 -882 431 -870 433 -874 867 -8242 455 -862 879 -452 415 -880 867 -452 853 -448 415 -900 873 -446 411 -900 849 -450 415 -896 875 -446 873 -444 875 -410 443 -876 873 -436 443 -892 415 -884 451 -848 449 -878 879 -434 441 -870 447 -870 413 -902 411 -902 413 -870 445 -870 443 -900 839 -8242 453 -872 881 -452 413 -872 869 -448 849 -450 +RAW_Data: 445 -870 889 -418 449 -892 847 -446 413 -896 879 -446 875 -442 843 -474 411 -888 845 -462 415 -884 423 -910 427 -870 451 -862 875 -428 443 -876 445 -872 413 -906 413 -904 411 -904 411 -904 409 -904 839 -8264 409 -884 887 -430 445 -876 843 -448 887 -450 417 -888 849 -448 449 -890 847 -446 413 -898 877 -444 871 -444 839 -474 411 -902 839 -474 409 -902 411 -900 409 -888 415 -884 881 -456 417 -882 423 -910 399 -898 419 -896 419 -880 453 -878 413 -878 875 -8236 449 -876 879 -434 441 -870 875 -414 889 -454 419 -890 845 -450 417 -886 871 -440 443 -868 879 -448 879 -444 843 -474 413 -888 845 -462 415 -882 421 -912 427 -872 437 -894 837 -454 449 -884 419 -880 415 -910 413 -878 443 -878 439 -880 437 -878 863 -8230 457 -848 879 -452 437 -872 865 -450 849 -450 415 -902 851 -450 417 -900 887 -418 451 -884 843 -474 839 -476 839 -472 411 -866 877 -448 415 -886 445 -888 417 -880 423 -910 853 -446 409 -912 395 -904 451 -866 415 -910 415 -874 449 -872 445 -874 873 -8212 455 -868 877 -428 445 -874 877 -446 863 -450 425 -882 843 -476 413 -904 839 -472 413 -888 875 -430 875 -432 875 -434 413 -904 873 -434 413 -904 411 -904 411 -916 417 -880 867 -456 409 -908 417 -876 409 -914 425 -872 451 -866 421 -882 415 -944 805 -8276 413 -900 839 -484 389 -908 855 -452 853 -450 425 -906 855 -450 395 -938 827 -448 435 -896 851 -456 841 -476 841 -468 407 -914 839 -452 429 -886 427 -884 427 -884 393 -982 757 -540 325 -992 357 -942 359 -942 355 -976 353 -944 353 -974 355 -942 813 -8292 405 -906 835 -450 431 -884 853 -480 847 -446 413 -886 875 -468 381 -920 845 -460 415 -916 845 -458 843 -458 869 -434 413 -920 843 -460 417 -878 411 -910 411 -908 427 -872 861 -446 441 -874 451 -866 419 -916 417 -878 415 -910 411 -880 443 -878 867 -8226 457 -882 851 -440 433 -904 837 -474 835 -446 427 -916 857 -450 419 -888 849 -448 415 -918 843 -472 845 -470 843 -470 383 -900 871 -468 381 -916 415 -884 423 -908 397 -902 863 -450 439 -874 437 -890 413 -882 419 -882 453 -876 449 -844 445 -876 871 -8232 451 -876 853 -450 445 -880 863 -452 853 -448 415 -886 871 -438 441 -886 871 -432 439 -890 845 -458 843 -460 871 -468 379 -898 879 -444 415 -918 381 -918 417 -880 423 -906 853 -450 427 -902 409 -908 399 -894 417 -894 419 -912 419 -876 415 -910 843 -8250 407 -902 851 -454 445 -878 841 -484 843 -452 427 -886 851 -482 385 -916 843 -472 +RAW_Data: 413 -886 843 -464 843 -466 845 -466 415 -916 847 -458 417 -880 425 -906 413 -100576 129 -4900 97 -1286 555 -132 65 -198 361 -132 98215 -96 21029 -64 5709 -66 22509 -98 23929 -66 4017 -66 687 -98 1353 -66 3641 -66 1391 -64 565 -66 40435 -102 51423 -66 10357 -66 10493 -98 5979 -66 22915 -100 1281 -100 62401 -66 26927 -66 20563 -100 4151 -100 12369 -66 45939 -858 863 -446 437 -894 853 -458 845 -452 449 -878 863 -454 421 -888 849 -448 415 -886 875 -470 841 -470 839 -472 411 -888 843 -464 415 -910 419 -880 425 -872 437 -894 871 -416 449 -882 419 -880 453 -876 415 -906 413 -876 443 -876 441 -878 865 -8230 457 -850 879 -440 449 -872 875 -438 873 -422 433 -882 887 -422 451 -854 875 -440 443 -892 845 -460 875 -428 871 -436 413 -906 869 -434 443 -868 449 -890 419 -884 413 -888 875 -434 415 -916 415 -876 411 -908 411 -910 427 -870 437 -894 409 -882 871 -8250 409 -900 875 -444 411 -888 875 -434 873 -434 445 -864 877 -446 415 -900 873 -446 411 -888 873 -434 871 -436 873 -436 445 -892 841 -458 409 -886 451 -882 453 -844 451 -874 845 -486 415 -878 439 -880 435 -880 401 -914 399 -914 399 -914 397 -904 863 -8224 457 -882 845 -452 441 -872 865 -450 847 -450 417 -918 841 -472 413 -888 841 -464 413 -910 845 -460 875 -432 877 -432 413 -908 843 -460 409 -918 417 -884 425 -874 435 -894 853 -456 417 -880 451 -876 413 -912 411 -904 411 -880 435 -872 439 -872 871 -8244 409 -916 835 -446 439 -904 837 -448 869 -456 411 -906 869 -430 415 -918 845 -458 415 -876 867 -460 875 -430 875 -432 415 -904 869 -434 413 -904 411 -904 409 -906 409 -906 867 -432 413 -906 409 -918 417 -882 425 -870 439 -872 439 -872 439 -904 865 -8220 439 -868 871 -450 423 -884 857 -450 881 -446 413 -902 843 -474 411 -888 877 -430 415 -908 843 -460 869 -434 871 -436 445 -874 869 -436 443 -876 409 -906 409 -906 409 -906 871 -432 415 -916 417 -882 425 -908 399 -896 409 -916 409 -896 411 -880 887 -8232 449 -844 867 -446 439 -872 893 -412 895 -446 429 -868 855 -456 451 -884 845 -442 435 -878 869 -454 853 -448 881 -444 413 -890 875 -432 413 -918 409 -874 441 -876 441 -878 867 -456 415 -882 423 -910 397 -900 437 -888 413 -880 455 -880 419 -876 877 -8238 421 -890 843 -488 413 -880 863 -450 847 -446 415 -892 879 -448 411 -898 875 -444 413 -900 841 -476 843 -472 841 -470 413 -888 843 -464 415 -906 409 -884 421 -908 397 -900 885 -420 445 -880 417 -914 419 -876 +RAW_Data: 415 -910 411 -880 441 -880 435 -872 867 -8254 421 -890 843 -444 445 -874 875 -442 865 -452 427 -890 849 -448 415 -918 841 -472 411 -886 841 -462 871 -434 873 -470 379 -920 843 -458 409 -900 413 -918 413 -886 415 -884 879 -452 409 -906 409 -884 421 -912 395 -900 437 -888 411 -880 441 -896 873 -8228 411 -902 843 -476 411 -888 877 -428 869 -434 415 -916 843 -460 415 -882 869 -458 409 -904 873 -432 875 -436 873 -436 413 -908 875 -430 413 -906 409 -886 419 -914 427 -872 889 -420 451 -882 419 -878 451 -876 415 -876 443 -878 439 -876 437 -878 869 -8228 421 -918 845 -452 403 -904 865 -444 853 -446 417 -924 847 -446 415 -920 839 -468 411 -904 841 -468 839 -468 839 -472 411 -902 841 -470 411 -904 411 -872 413 -902 413 -902 877 -434 411 -900 413 -906 411 -900 415 -916 379 -936 381 -916 415 -908 835 -8254 419 -910 841 -450 423 -884 855 -452 879 -448 417 -900 849 -448 415 -920 841 -472 411 -874 869 -438 871 -472 839 -470 411 -872 869 -440 445 -874 441 -866 449 -884 413 -888 875 -432 413 -906 409 -906 409 -906 409 -886 421 -908 427 -872 451 -866 875 -8236 415 -882 871 -460 417 -880 881 -454 845 -456 417 -880 879 -452 409 -884 881 -452 409 -880 881 -450 837 -462 871 -434 415 -914 845 -458 413 -916 387 -910 427 -904 401 -894 867 -450 411 -884 413 -908 419 -912 417 -876 415 -908 413 -910 409 -878 865 -8234 455 -882 843 -452 445 -880 859 -450 847 -446 415 -894 877 -444 415 -896 879 -444 413 -902 841 -472 841 -472 841 -472 411 -890 845 -462 415 -908 417 -876 411 -908 409 -912 851 -450 425 -908 399 -894 407 -904 437 -888 411 -880 447 -876 419 -880 881 -8238 439 -888 837 -456 451 -880 851 -452 871 -420 449 -892 845 -448 413 -894 879 -446 411 -900 875 -444 839 -474 837 -474 409 -904 839 -472 409 -902 411 -902 409 -904 409 -904 839 -472 409 -904 409 -872 443 -872 443 -890 417 -882 423 -910 417 -874 875 -8220 441 -904 841 -468 411 -904 841 -470 841 -470 413 -886 843 -462 417 -906 843 -458 411 -918 843 -456 869 -432 875 -434 415 -916 845 -456 411 -916 417 -876 411 -912 425 -870 889 -418 445 -882 421 -880 449 -878 415 -910 413 -878 441 -878 439 -880 865 -8230 417 -906 843 -468 407 -906 839 -478 857 -450 423 -884 843 -474 413 -886 877 -432 415 -916 845 -458 845 -460 871 -432 415 -916 845 -458 417 -916 387 -908 427 -870 437 -892 851 -458 415 -882 451 -876 413 -912 413 -878 439 -878 441 -880 435 -878 +RAW_Data: 863 -8240 423 -888 841 -486 415 -878 869 -452 849 -448 415 -920 841 -470 381 -920 845 -460 415 -910 843 -454 869 -432 873 -434 415 -904 869 -436 413 -920 417 -876 441 -880 425 -908 857 -444 409 -910 399 -898 417 -894 419 -882 451 -874 415 -908 413 -878 873 -8254 405 -898 853 -458 417 -914 847 -452 865 -452 421 -884 845 -474 413 -886 873 -430 411 -904 873 -432 871 -470 837 -470 413 -886 843 -460 413 -918 417 -874 413 -906 411 -910 851 -446 411 -914 425 -872 451 -866 417 -916 419 -878 415 -910 413 -878 871 -8228 455 -884 845 -452 445 -880 863 -450 851 -446 413 -920 841 -470 409 -874 873 -468 409 -872 873 -470 843 -470 839 -470 381 -918 843 -462 415 -912 411 -880 423 -906 397 -900 865 -446 419 -894 419 -912 419 -878 413 -910 411 -910 409 -880 441 -878 865 -8254 413 -878 877 -430 445 -880 841 -458 863 -452 415 -920 841 -438 415 -908 871 -434 413 -914 845 -458 871 -466 841 -470 413 -884 843 -460 409 -920 417 -880 425 -872 435 -894 867 -448 407 -902 409 -916 407 -896 407 -902 409 -886 439 -898 411 -880 875 -8248 411 -900 843 -476 413 -886 873 -432 875 -434 413 -918 843 -460 411 -916 845 -454 411 -918 845 -456 845 -460 841 -462 411 -916 843 -456 411 -916 417 -880 425 -908 399 -896 851 -456 415 -916 417 -880 415 -908 413 -880 439 -870 439 -904 407 -874 871 -8238 439 -868 869 -448 411 -906 869 -454 837 -460 411 -916 843 -458 411 -916 843 -456 409 -906 871 -432 873 -434 875 -434 413 -918 845 -458 411 -916 387 -910 425 -908 415 -870 875 -428 447 -880 415 -908 413 -880 439 -872 439 -872 441 -872 439 -872 871 -8246 419 -896 847 -486 413 -882 865 -448 849 -446 415 -928 845 -446 415 -886 875 -468 381 -916 843 -460 843 -464 877 -432 413 -916 843 -458 411 -916 385 -906 411 -910 425 -906 823 -478 411 -902 411 -904 411 -906 397 -898 407 -918 411 -880 417 -916 847 -8236 435 -892 867 -448 397 -898 869 -448 869 -456 417 -906 837 -460 415 -914 847 -456 387 -908 849 -482 837 -458 871 -430 415 -914 847 -458 411 -916 385 -902 411 -910 423 -904 839 -450 423 -910 395 -900 405 -922 417 -888 417 -912 417 -876 415 -910 841 -8264 421 -890 843 -452 445 -880 863 -448 849 -448 417 -918 841 -470 413 -886 871 -430 411 -904 873 -466 837 -470 841 -468 411 -904 839 -470 411 -902 411 -902 381 -918 417 -906 839 -460 415 -914 389 -908 427 -870 437 -892 409 -916 407 -896 409 -916 853 -8234 425 -906 825 -480 401 -894 +RAW_Data: 865 -450 851 -456 415 -918 845 -454 405 -902 865 -448 421 -902 829 -482 847 -446 875 -444 413 -898 845 -474 413 -902 411 -904 413 -904 383 -914 843 -462 411 -914 417 -880 423 -906 397 -900 405 -922 417 -890 417 -882 879 -8232 409 -904 871 -434 411 -904 871 -466 839 -470 381 -932 845 -446 415 -918 839 -470 381 -916 843 -460 873 -466 841 -468 381 -916 841 -460 413 -916 387 -910 423 -908 397 -896 869 -448 427 -906 399 -894 415 -894 417 -912 417 -880 415 -908 411 -882 869 -8228 455 -888 845 -452 409 -916 861 -450 847 -446 415 -896 877 -444 413 -902 853 -448 415 -894 879 -444 841 -476 843 -472 415 -902 839 -472 381 -914 417 -914 387 -910 395 -938 825 -446 437 -894 417 -892 417 -914 415 -876 413 -910 411 -910 409 -910 835 -8232 447 -878 853 -452 451 -880 863 -450 847 -448 413 -920 839 -472 411 -888 877 -430 415 -916 843 -456 843 -460 867 -436 443 -874 869 -436 443 -890 415 -884 421 -904 407 -912 855 -444 409 -906 409 -912 397 -898 439 -890 411 -880 453 -880 421 -872 879 -8246 417 -876 849 -482 411 -880 883 -450 837 -458 411 -916 847 -456 411 -916 847 -454 409 -916 845 -452 835 -462 875 -432 413 -916 843 -460 411 -912 409 -906 411 -878 421 -910 855 -446 411 -910 425 -872 435 -892 409 -918 413 -880 419 -880 451 -880 843 -8264 421 -890 843 -486 411 -872 867 -448 851 -448 417 -926 847 -444 415 -896 875 -442 415 -900 851 -448 877 -444 843 -476 415 -888 843 -462 415 -916 387 -910 425 -908 399 -894 867 -444 411 -908 395 -902 435 -894 417 -892 417 -882 451 -878 413 -878 871 -8250 405 -900 867 -448 409 -910 853 -446 869 -454 411 -914 845 -456 417 -880 881 -448 411 -880 881 -452 837 -460 873 -434 413 -916 845 -460 415 -882 421 -910 395 -902 437 -888 853 -458 415 -882 449 -878 413 -912 411 -880 439 -880 437 -878 435 -880 865 -8224 455 -884 843 -442 437 -878 869 -452 855 -448 415 -920 839 -472 413 -886 845 -462 417 -914 847 -454 869 -426 873 -434 415 -916 843 -460 409 -918 415 -882 425 -906 399 -896 865 -448 407 -902 439 -890 411 -880 417 -916 417 -878 417 -908 413 -880 873 -8230 457 -886 849 -452 407 -906 863 -450 849 -446 415 -894 877 -446 413 -898 877 -442 413 -904 839 -474 843 -472 843 -470 381 -918 843 -462 417 -914 387 -910 427 -872 435 -892 869 -450 407 -896 409 -900 407 -918 407 -896 409 -900 407 -918 407 -898 855 -8234 443 -892 845 -460 415 -916 845 -454 835 -462 417 -912 837 -460 +RAW_Data: 411 -916 843 -458 417 -914 845 -454 835 -462 871 -468 381 -914 871 -430 415 -918 387 -910 425 -906 399 -896 849 -474 407 -896 409 -902 407 -918 407 -898 409 -916 409 -894 409 -884 875 -8230 443 -874 873 -434 413 -920 841 -460 871 -432 413 -918 847 -456 411 -904 871 -430 415 -916 845 -458 845 -460 875 -432 415 -916 843 -456 411 -916 387 -910 425 -908 415 -870 851 -486 411 -872 439 -870 441 -872 439 -904 409 -878 439 -878 437 -878 865 -8230 455 -882 845 -442 437 -878 869 -452 855 -448 415 -920 843 -470 381 -918 845 -460 415 -912 845 -454 869 -432 873 -434 415 -916 845 -458 411 -914 387 -910 425 -902 411 -902 835 -450 441 -880 425 -902 411 -908 397 -894 409 -902 435 -890 419 -892 843 -8266 395 -900 865 -446 439 -890 835 -474 867 -442 409 -910 853 -446 411 -912 851 -450 425 -902 837 -448 869 -458 841 -462 411 -916 843 -456 411 -916 387 -910 425 -908 399 -896 855 -458 415 -914 419 -876 415 -912 411 -880 439 -878 437 -882 401 -912 865 -8226 419 -910 843 -452 447 -880 863 -452 849 -446 413 -898 875 -444 413 -900 875 -442 411 -904 841 -472 841 -472 839 -472 411 -890 843 -460 409 -906 409 -920 417 -880 425 -874 889 -418 445 -880 421 -880 453 -880 413 -910 413 -880 439 -878 437 -878 863 -8238 417 -904 843 -470 405 -902 841 -478 859 -448 421 -890 849 -448 415 -918 841 -472 413 -886 843 -462 869 -468 841 -470 381 -916 845 -460 411 -918 387 -910 425 -906 399 -894 851 -456 415 -916 417 -880 415 -910 411 -912 405 -904 409 -904 407 -904 839 -8246 441 -886 855 -430 449 -878 879 -416 889 -450 415 -900 861 -420 449 -868 889 -418 453 -886 841 -474 839 -472 843 -470 411 -898 845 -450 415 -898 413 -886 445 -874 441 -890 841 -458 409 -898 413 -920 411 -872 441 -890 415 -884 421 -910 397 -902 853 -8254 415 -898 879 -444 413 -904 841 -472 841 -472 413 -886 841 -462 411 -916 843 -458 409 -916 845 -458 841 -462 869 -436 413 -904 869 -470 379 -920 417 -876 421 -908 425 -872 889 -418 451 -884 419 -880 449 -874 415 -908 411 -878 443 -880 435 -872 865 -8226 455 -886 845 -450 445 -880 863 -452 853 -444 415 -918 841 -470 381 -916 845 -462 415 -914 845 -456 837 -460 871 -470 381 -916 843 -460 411 -914 417 -880 423 -902 409 -904 837 -484 389 -910 427 -904 399 -896 407 -916 409 -892 409 -918 409 -896 835 -8254 413 -896 879 -444 413 -900 843 -474 841 -472 411 -902 841 -472 409 -904 839 -472 411 -902 843 -468 +RAW_Data: 839 -472 843 -472 381 -916 843 -462 415 -918 387 -910 427 -870 435 -894 851 -456 415 -914 419 -880 413 -912 411 -880 441 -878 439 -880 401 -914 865 -8224 417 -908 843 -468 407 -904 839 -480 857 -448 421 -886 853 -448 417 -918 841 -472 413 -886 845 -462 867 -466 841 -470 381 -918 847 -460 415 -914 409 -878 413 -908 427 -906 825 -446 441 -902 409 -902 411 -908 397 -894 409 -900 417 -898 419 -914 843 -8246 425 -872 861 -480 399 -898 865 -448 867 -452 411 -880 881 -452 409 -908 845 -456 409 -914 835 -462 873 -432 877 -432 415 -904 869 -434 413 -916 417 -874 441 -880 411 -910 853 -448 411 -912 425 -872 451 -868 419 -914 419 -878 415 -908 411 -910 841 -8226 455 -886 843 -452 449 -878 863 -452 855 -446 415 -886 877 -434 415 -916 843 -462 415 -882 881 -454 845 -460 845 -460 411 -916 845 -456 411 -916 387 -910 425 -872 449 -870 875 -430 445 -880 413 -910 413 -878 441 -868 441 -872 441 -872 441 -902 841 -8246 421 -892 853 -452 443 -872 867 -448 849 -450 415 -926 847 -446 413 -898 877 -444 413 -900 845 -472 839 -470 839 -474 409 -902 843 -470 411 -872 445 -874 411 -904 411 -906 875 -432 413 -908 417 -884 423 -910 399 -898 407 -922 411 -880 445 -878 851 -8250 423 -904 857 -448 397 -902 853 -454 881 -430 443 -878 843 -478 431 -880 867 -450 423 -888 849 -448 843 -476 845 -474 413 -906 843 -470 381 -904 415 -914 417 -880 423 -902 837 -484 411 -878 425 -906 399 -896 405 -922 411 -916 381 -910 415 -910 841 -8232 453 -870 875 -430 447 -878 875 -442 841 -478 397 -912 861 -450 423 -886 845 -474 413 -906 841 -470 843 -470 839 -470 381 -916 843 -462 411 -918 387 -910 425 -902 411 -908 851 -450 411 -882 419 -914 417 -880 415 -908 415 -910 411 -878 439 -878 865 -8232 455 -882 845 -452 443 -880 861 -452 849 -448 417 -902 843 -474 413 -888 873 -430 411 -904 871 -434 869 -470 839 -472 409 -904 839 -470 413 -888 417 -906 411 -874 441 -876 869 -456 409 -886 419 -912 425 -870 451 -866 417 -908 415 -906 415 -872 877 -8248 407 -906 853 -446 411 -906 869 -452 847 -458 417 -880 883 -452 387 -912 851 -450 423 -906 853 -448 223 -124912 1217 -330 99 -132 163 -66 2031 -64 59635 -66 2765 -98 26085 -98 4367 -100 563 -66 24625 -66 1849 -66 10545 -68 21363 -66 15231 -100 42927 -100 3021 -66 55509 -100 4287 -66 2435 -98 1017 -132 3371 -66 11541 -100 1265 -100 799 -66 1859 -68 3385 -66 965 -100 2221 -100 2355 -68 6165 -66 1025 -100 +RAW_Data: 899 -66 173525 -166 2373 -68 397 -66 40101 -66 1891 -66 10969 -132 2617 -66 20187 -66 10559 -100 10491 -98 35135 -66 2287 -68 2649 -66 3393 -98 12375 -100 1265 -100 5605 -66 9997 -68 435 -132 16583 -100 3003 -66 10083 -64 13329 -66 64855 -100 13269 -132 10773 -100 4673 -17250 97 -1358 297 -164 165 -132 199 -66 199 -66 165 -664 65 -98 265 -66 201 -132 531 -132 235 -66 3355 -68 2757 -98 16517 -66 12659 -66 3773 -66 26419 -66 12433 -66 531 -100 20227 -66 1851 -66 11499 -64 10717 diff --git a/applications/debug/unit_tests/tests/subghz/subghz_test.c b/applications/debug/unit_tests/tests/subghz/subghz_test.c index a58875fe8..ae83ed562 100644 --- a/applications/debug/unit_tests/tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/tests/subghz/subghz_test.c @@ -17,7 +17,7 @@ #define NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s") #define ALUTECH_AT_4N_DIR_NAME EXT_PATH("subghz/assets/alutech_at_4n") #define TEST_RANDOM_DIR_NAME EXT_PATH("unit_tests/subghz/test_random_raw.sub") -#define TEST_RANDOM_COUNT_PARSE 331 +#define TEST_RANDOM_COUNT_PARSE 328 #define TEST_TIMEOUT 10000 static SubGhzEnvironment* environment_handler; @@ -672,6 +672,62 @@ MU_TEST(subghz_decoder_dickert_test) { "Test decoder " SUBGHZ_PROTOCOL_DICKERT_MAHS_NAME " error\r\n"); } +MU_TEST(subghz_decoder_legrand_test) { + mu_assert( + subghz_decoder_test( + EXT_PATH("unit_tests/subghz/legrand_raw.sub"), SUBGHZ_PROTOCOL_LEGRAND_NAME), + "Test decoder " SUBGHZ_PROTOCOL_LEGRAND_NAME " error\r\n"); +} + +MU_TEST(subghz_decoder_marantec24_test) { + mu_assert( + subghz_decoder_test( + EXT_PATH("unit_tests/subghz/marantec24_raw.sub"), SUBGHZ_PROTOCOL_MARANTEC24_NAME), + "Test decoder " SUBGHZ_PROTOCOL_MARANTEC24_NAME " error\r\n"); +} + +MU_TEST(subghz_decoder_roger_test) { + mu_assert( + subghz_decoder_test( + EXT_PATH("unit_tests/subghz/roger_raw.sub"), SUBGHZ_PROTOCOL_ROGER_NAME), + "Test decoder " SUBGHZ_PROTOCOL_ROGER_NAME " error\r\n"); +} + +MU_TEST(subghz_decoder_feron_test) { + mu_assert( + subghz_decoder_test( + EXT_PATH("unit_tests/subghz/feron_raw.sub"), SUBGHZ_PROTOCOL_FERON_NAME), + "Test decoder " SUBGHZ_PROTOCOL_FERON_NAME " error\r\n"); +} + +MU_TEST(subghz_decoder_gangqi_test) { + mu_assert( + subghz_decoder_test( + EXT_PATH("unit_tests/subghz/gangqi_raw.sub"), SUBGHZ_PROTOCOL_GANGQI_NAME), + "Test decoder " SUBGHZ_PROTOCOL_GANGQI_NAME " error\r\n"); +} + +MU_TEST(subghz_decoder_hollarm_test) { + mu_assert( + subghz_decoder_test( + EXT_PATH("unit_tests/subghz/hollarm_raw.sub"), SUBGHZ_PROTOCOL_HOLLARM_NAME), + "Test decoder " SUBGHZ_PROTOCOL_HOLLARM_NAME " error\r\n"); +} + +MU_TEST(subghz_decoder_reversrb2_test) { + mu_assert( + subghz_decoder_test( + EXT_PATH("unit_tests/subghz/revers_rb2_raw.sub"), SUBGHZ_PROTOCOL_REVERSRB2_NAME), + "Test decoder " SUBGHZ_PROTOCOL_REVERSRB2_NAME " error\r\n"); +} + +MU_TEST(subghz_decoder_hay21_test) { + mu_assert( + subghz_decoder_test( + EXT_PATH("unit_tests/subghz/hay21_raw.sub"), SUBGHZ_PROTOCOL_HAY21_NAME), + "Test decoder " SUBGHZ_PROTOCOL_HAY21_NAME " error\r\n"); +} + //test encoders MU_TEST(subghz_encoder_princeton_test) { mu_assert( @@ -835,6 +891,48 @@ MU_TEST(subghz_encoder_dickert_test) { "Test encoder " SUBGHZ_PROTOCOL_DICKERT_MAHS_NAME " error\r\n"); } +MU_TEST(subghz_encoder_legrand_test) { + mu_assert( + subghz_encoder_test(EXT_PATH("unit_tests/subghz/legrand.sub")), + "Test encoder " SUBGHZ_PROTOCOL_LEGRAND_NAME " error\r\n"); +} + +MU_TEST(subghz_encoder_feron_test) { + mu_assert( + subghz_encoder_test(EXT_PATH("unit_tests/subghz/feron.sub")), + "Test encoder " SUBGHZ_PROTOCOL_FERON_NAME " error\r\n"); +} + +MU_TEST(subghz_encoder_gangqi_test) { + mu_assert( + subghz_encoder_test(EXT_PATH("unit_tests/subghz/gangqi.sub")), + "Test encoder " SUBGHZ_PROTOCOL_GANGQI_NAME " error\r\n"); +} + +MU_TEST(subghz_encoder_hollarm_test) { + mu_assert( + subghz_encoder_test(EXT_PATH("unit_tests/subghz/hollarm.sub")), + "Test encoder " SUBGHZ_PROTOCOL_HOLLARM_NAME " error\r\n"); +} + +MU_TEST(subghz_encoder_reversrb2_test) { + mu_assert( + subghz_encoder_test(EXT_PATH("unit_tests/subghz/revers_rb2.sub")), + "Test encoder " SUBGHZ_PROTOCOL_REVERSRB2_NAME " error\r\n"); +} + +MU_TEST(subghz_encoder_roger_test) { + mu_assert( + subghz_encoder_test(EXT_PATH("unit_tests/subghz/roger.sub")), + "Test encoder " SUBGHZ_PROTOCOL_ROGER_NAME " error\r\n"); +} + +MU_TEST(subghz_encoder_marantec24_test) { + mu_assert( + subghz_encoder_test(EXT_PATH("unit_tests/subghz/marantec24.sub")), + "Test encoder " SUBGHZ_PROTOCOL_MARANTEC24_NAME " error\r\n"); +} + MU_TEST(subghz_random_test) { mu_assert(subghz_decode_random_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n"); } @@ -887,6 +985,14 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_decoder_kinggates_stylo4k_test); MU_RUN_TEST(subghz_decoder_mastercode_test); MU_RUN_TEST(subghz_decoder_dickert_test); + MU_RUN_TEST(subghz_decoder_roger_test); + MU_RUN_TEST(subghz_decoder_hollarm_test); + MU_RUN_TEST(subghz_decoder_reversrb2_test); + MU_RUN_TEST(subghz_decoder_gangqi_test); + MU_RUN_TEST(subghz_decoder_hay21_test); + MU_RUN_TEST(subghz_decoder_feron_test); + MU_RUN_TEST(subghz_decoder_legrand_test); + MU_RUN_TEST(subghz_decoder_marantec24_test); MU_RUN_TEST(subghz_encoder_princeton_test); MU_RUN_TEST(subghz_encoder_came_test); @@ -915,6 +1021,13 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_encoder_dooya_test); MU_RUN_TEST(subghz_encoder_mastercode_test); MU_RUN_TEST(subghz_encoder_dickert_test); + MU_RUN_TEST(subghz_encoder_feron_test); + MU_RUN_TEST(subghz_encoder_roger_test); + MU_RUN_TEST(subghz_encoder_gangqi_test); + MU_RUN_TEST(subghz_encoder_marantec24_test); + MU_RUN_TEST(subghz_encoder_hollarm_test); + MU_RUN_TEST(subghz_encoder_reversrb2_test); + MU_RUN_TEST(subghz_encoder_legrand_test); MU_RUN_TEST(subghz_random_test); subghz_test_deinit(); diff --git a/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes index 39d9b3e3c..b77080f8b 100644 --- a/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes +++ b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes @@ -1,59 +1,66 @@ Filetype: Flipper SubGhz Keystore File Version: 0 Encryption: 1 -IV: 43 68 65 63 6B 20 70 61 73 73 65 64 20 4F 77 4F -00A92D8AE07E4998E826AF5C89AD659BD8C2BC6A40DA78B1AC05CF5B066243ED -A3C71FA36145D0EE56D78C05DDDDA97E487BCDCA6BDAC2C6F87402A0B20EE3CB -B9BDFBD18A63503580C18ABF84101B33D7F720900201510086EB3F0C1F533564 -EAB736F80371447008CC3BE3CE952CE429E7BF743A70C7CC62FF415B9E38467B -9C50D75C6E4A82F49AF285EC3545E58F8815FF4FCF5C9FFFCF0151FB693413FF13B594C8A28077450C8E561B0272C264 -A54C7E6DD1CAE0758F7A123B187C6EDA5BF3789969FE1E5F5A167E2DA7719671 -1461891D4AB3500B5BC0859166377CE098C04AAF6FD721F9C58A155F23F8E75A -BD1FD5645367BA76D8A87C271FF71E71C407B276BB0B165AF8CF6317250B77A6 -B18718EA6EB53CCDF9C26FC46E36D17234D93EF578376123B1F3F9953302CF62 -B633458C1948BC65357904F901F6DD5CD9D795887C176C6AA48E477F0EB693DB -B1352AC0DE8EBDD838F5DF7E040B062CF8FBB73180F3E712C5B2BDBFE1257A2C18694C51F242BCEA62DF317708771AA1 -842294FA0BACCD5B7710F002E40E4BB6142BC7C3125B2E56D4431FD8D6DD1CCE3397A4EB502B9E4248FA68CE8013E93D -5379E56AD483C0F870D9446C9CDE65ACA40C3A699D0653EC2F356A076D72D6C2 -8FD394F533CD2B03AE247B18F4A5214ED0AF64892F497362129FEF5837012BE4 -FA8BAB9CD11B306F9FCD924B3A66C678A0316F048A30312B5FA69B9E86EDD8C9 -35D6E447F3B8BA11331805027BCA1D972E9B29E07F1D2993C974DCAF5EE1EB76 -5FBF06E449C471D0AE2DECD141B937666C30EE72273CF58A99E19DCF5D2F69A703F17880B5FB1873480AFA82E2CE7674 -06C1463057544BDB86BD91B3BCC507FD3A5939CFF3315AFD252511FE4F6109EC -9AB5C03053AF5836E71FE27D0CD74515C967273EB240B7C37825B94D9D9F08AC -66EB1C65F32D5DBDC37B5FF7ACD7C4273B88298E8F0BED08F533F41F38DE651010686B79623804F67398B020E109B9A4 -7B1846759EDD928EF35B2E8FB7E3FFFE545C9A8B349476A9CE2BDDE55CE0E97F53002A543D0FB738CA67490501629296 -EB61CB652F414A70441ADACA46DBCC78A5AAC7A2ED4527AA2A93995482985867 -1E119E453C38C3242EFE3D9A3BBB3D257D91D15710C47811C1ADA934515DBD9E -6D8B689C37F7CFD52123BA77B6A4FABB16C3D22BF66FB78B4387BDBC975A3EE5 -997A46848917B76A1D728DA8C3A072F16F020AF50070BAD91AC2D841FA9805C1 -C51BFD0B93FECC7234D342F1DA785736A9229A21ACA8C9AA171906AA8916856F -86188B0DF2C25CA48DC0ED0B4524C17D93585873877FAFBBB55EB24A1D6FDABC -8178B7DFF235094BDCA1E5D72E48B113F2290C0FEBC95CF842013AB9CD84E0AD -25AEF7C490788DCC142CBC96FBCAB598B7EA1D15B7DE4ABBF75DE70832B519E4 -8C5FB85C16C5C833E1D8CE13A9432009D98B0D1639EB0C7C86D0AF4ACD7EB694 -AC9422E946BB8482CFD808B8E17BE01D9F5D9AB49E1192174A04BB0F032F2182 -793EEE939544D18C547E2198FA6F4A72D518C15C14146FC6CAD6AB642A3C9824 -C910617B5DC2C3137E96AC1869B7C5E90A1585181CC1B585C4CACC2624B7A72A -0AAEBC3463300A0391C2B2B14865A68EF44EAE8B1C2462B2730E28B25881B6462B3CCA631DC8A750F97EF5003E6B3060 -0861D4A3FE5970BB36BF8B525C69009212520A3B79F48ACEC6CB8F6DF96D913F -6327F56523F609D4ABC552912EF808E5919EA42104137AD206EF93AA4B34C097 -F88B71DAE547754731CD1C45ACBC355E52FE6D7B984A27B454DD7E8BFF12A023 -824025D56ED8B11BB65722F8C04168767D059BEB156B183D718CFB6AA38B9D81 -66E60820D0A6C452D9E209FB56FE3F49CAEFCBDC6116177063E0759FA11FDC8F -52D6ACA1A6928C52462BABD9A76628B25A2B913FC5BE316A9248C90B6F952529 -6FF5E9DAC956EF849FB58C11948C6F00743E157A8C2C631769EFCD80BAA3C048E10E682437EB53ED906C3060CCC7FECC -42757F22BDC1059154C41EDEE74E227E6D980F39939D3AA78FEC3F4DC303BE87 -689C2694447C4FF56C85B60E9FA66CD70A7EB9703A7C0202AD34374E9B290178 -E1A8E0ACF1E30714A7E580CA6380879C48992BA93A1637F24A66E15A03509B96 -5E1176A42E6C0DCF80E7B445372E3C054875A39D5F7A8B9C35985EDB46F65BB0 -12A6349CB3EC6D79B3C85052D6155730A036AF7F54BCD8CC9EC8F36557005D5E -B6D3D039D8B6D6F298665B16EBCB908D5F64D37C31F583C4F660AC0535DEC2DD -327BEE9B3E7A989B49787DE9D0C573C97343D79527FA6F442F8BDA0B9336E666 -1BDEDA4766FB3EFC1867836267E632B12020262B1EABA4BD31F9AB20DE49632A -8DE80C00CAD7F6E555713414DE8CE56B1C8603777F38F553F528907FA25E153EA5D3459739C66B46F6854A75F19A1511 -3A07DD2876F794FDF920F5D7B50A6D15C618FCF02F064E5AD5871C5B098EE8EC -66B7166156874006B35AF6997F61B84F016868FBAD283304B256F3DA065E65E3 -1E6D841D11C065F88B3F52EF60862E579E717E6AEFABF6A79FCF90D0810B35BB -B3B168E2532A6B659503736AA8933FB01A88DB9EA339C1C19EF7566600312B26 -974628362FD1951C207BDC3F5363F2747BAFA14ED7155772ADAA2FE8D1F7DC702F8DDB8CB4C17DBA803EC0DB6CDA43AC +IV: 48 69 69 69 2C 20 4D 69 73 68 61 21 21 30 31 21 +5914BA15A974B44BBF5886001E9CA8C05FED2B9A46D16F862484BC4760201A72 +885AB8E254F1403894740B50C6943A5F0A426C79CD8FA11D16915455E6B9ACE8 +48106C10911CC932FCAB49A793B6777DE845AA0D383E1B8D9FAE04E4F943FC60 +C1B81F527CF227F8A8D342BBB86EFBD95C99BFE3E3F55FFF19CF66C03CDB8779 +3DAB5A367DC19D412E9D71A89852E246DDEC0E97D7F7CE77DEE4043D9A99A1DEC4BDCA38B728CDC7BCB9E112044DCB8B +8E718417DD703259B6EBBB799EC8ED428C6D65115023C1421C349DDB70359929 +DB110804D813EB3D2F04F5723305CFA13778D46DD261D246B05DB257535C9520 +135BEFEEBEBF17665C27951E43639A82AD5B15EFAD7D1D776ACF1070E5E81F9C +996528B0D85E81D1AB47FECB2A5C2B63AFF35281EE41E316D26E34BB7E0E5EFE +BA220B2E219DE8045F6C5C4D8B4CABFB9944252ACB82755907C44E09071A0F90 +C4509ACDC858711F42B96AA40B0FEC58566E71D91E1D167835B97D13A9C6B4FD63F62D911E17C15081ED1FFBA00E08D0 +499D1031FDC6B5D6109D4A9046599FEC77F1DB26FCA809235803CE5F670F2BF1622042AB04F768CB4BBB8E93595B229A +43107EA4D34AC00BD05F45CF7AB077742C06A3619B3E258389EFF2D8ED057570 +A2ED253FB0CE66CDE2FF3921BE78662A97A23C344A83493D36CE6CC5ACC3A821 +963B48D299D6935B228A3BFCB86CF75F11A30850A19E014E31E6559F7A9A7DA8 +35D23E9D1D85AA0273A13B887EFBE9F55DD2E6C85B14F7E1100A66892F2A05BE +F3134F0E2545623B439D581F2A42F6F4CE0512622B6E53937DFBE353C26DF2E7E69871412C0221D8CCCBB1948715CB3C +C7E5A6E301B8E18BD3B15748A2A74AA1A939073FFE2F4A126213C49567D5BE44 +6E60F1421A27CEA2521C1EFE014EE2036F706ED734C12A794EBE3EFFACEA59C4 +9B60C596CE660E6A582AEC2C2804188EDD3C7636A3BC0E423A8D969F083319BB5AB90BE0DF3470BADB8D23E5AE0832EC +001C3E6E3A36954427626FC4F2CC9DA39983C945F745EC9F25B11814D8EDB233AFFA0FD3C0B9B4D22EAFA951E734FBFD +C03C3CC316F88E71C2A41B4B947FDF3FFCB46E103AFC420B820D757DCF7C46B6 +5430C52B29FDE8A436C745C1821BA6CDE9FB42C297C0407F1BC185B137485ABB +F9EE48BC297FE3866E2BFBFEC341CFD57150088F88A06725ADFCAD891F9FF9F1 +E0BB992BDBFA5A2369A1DAEF6316F1779C16A33FE03F1A4A42C86B7FAA80A8B1 +747863CCE4A663B2C4C7533733CCD604C271C8BF65066E219C427C7208DCA9D5 +8C05EF25D017E06ED6D2818B4EAC50E56727FA84039FFE119837C2E5933EB3CD +17C31C9451349E42F010F0E7540D71567485FC7D5E9CFCFEAB0E7AA3FEA8E3AE +76D5BF46CE39C37F09549D281C5FDB5EC12CAA73152E3B295E439091365EEEF1 +0B7B9F563A09D953BC6DD141A1313B9CCB52CE5406ED727DE965CE920F0CA5DC +78C9F207EBE8942DCF78A345A8E56B9C6C75D63C110250377D6C04DBB4DC15EA +3518584CFE8238E0AFA1E6F4CDF1E1A2A6349A7D2D641404067BA482DA4C6345 +F3CD339419BFF39EFF2F2F0E7C06BB0537B238427345011436EF4F28D2FB4D02 +96A25E27313CC92A48B515143643D235C28DDCB53AECCFCFC7792A417FC78CCC +11D553C6363F1C53B0074DF36C5256C9695CEA3006AC8737689694848C14FE93F15925BE58AB9D063D06FD87C0F7E637 +81BE95390E912466EAA1465BA7E316D662D7FDCE4DA91D79551247BCBE0FD66E +D2DA1301B149C17E4E579266D0AD810E8043AC1F16781F7743FAFB61B0A74D14 +C4921F5E174C988212CB46362FABB6B99599537C4E5B983016463ACF11AFA9F9 +F1333F4B1706326B776A6701E4457A18A0E262EDEC1758D3A696741094EAE7C8 +6F3647542899B48A53A829FF3CE82B22A75AA9AB3D3E01BF33C6B3D1F0C9BF3D +C42ACCF63F19E529E0D6A9AD9D2505F103D734248DAC0E9F2FCA35EC3F86C0A3 +1C0370E20EDBF342A76B1B65F0DB7C66F870ABB56E848159A0DB6585749F57B36EF4E18F730B5D0A2732C03ED74ECF72 +C61D01023FBDD674B09C6FFDB38A6F152F3B5C3203AFE4CA014C28600670922C +6C5507A0AF35860EA2EAD029A6ED19AEFE2AD90B15B2EEFD65B1F14747C1EA66 +1C908E924A1BE763868ADB985DE4BB157CEF7BA96E58CED46BE08816E7D92239 +A3E883AC3AE22373815F2C02F6B808B0DD36FCE27BDF22C262FDBFC5D1B03153 +7CC9A80338315C477B55B4024324F2EAA56A770675F2229D32B44E304214059D +D9CF90A9C4B60860355301A842323D8E207473D4A5631067E5DF2BFFD5A0E38E +824D256362207A507DD40C13D765B1BA172C1E2AAA09DB164028CE094BFF33A8 +8F04BFBD27084AC2FEA04D18F88E98E3F4AE979B40343E2D55C20173EB5030B6 +D28B98D9030AC29D79B974FCDF176C19EF6998DB776D5A8B067F1F6908B951F7 +B7AA60298EB22D45A182A6E8BC76C7AD04334BE7D26746455C5CA44BC7D34B38 +C79E0C38724D4ED317A0507C3981DC4E3AFA84F039FF9595FDE4C4C81F3332A3 +F30A3803C59558A0FD41B15E45B133B901E0C66D898B10A2EF5189BC36EAE904 +3F2CFFEE683A76EB9FDD84E99CE0C0FCFFC1B5ECA9C8677CACA2F6E33A773823 +EDBC9BF637AFFB3F8BC0A90A4B8F56D452F64949E865855DC51882B6F0889E64 +C4234CE6419E399A6415EBC80FEF6049A3EF345D4D0692784BB58626241291A37C080BCEEFB71B135368E4E3790C93B3 +C1231A97B5642D908BD9524B482801A63F6AF3E25EF69F51C495EABF5C967238 +651FF3C3EB76F90CD8BC02A0884E4C830A2794F8CE2467CC2FA2E74D081D6567 +65CCA76B05D06AC1346B23BFE435864B648247075385B74AAE53C4006560854F +2C19A88D52E56B9E09574F3B0B05A1AB3F417D47F78DCEDB9F6593EE01FD9540 +D46CF8835857552C4E77C8B37D79A6DE133ECEFD234CCD2278C78872EBAFFF2DAD0571FEFDA998031A235673CF6F2EA2 diff --git a/lib/subghz/protocols/alutech_at_4n.h b/lib/subghz/protocols/alutech_at_4n.h index 189f2f0d8..871ece0ad 100644 --- a/lib/subghz/protocols/alutech_at_4n.h +++ b/lib/subghz/protocols/alutech_at_4n.h @@ -1,7 +1,7 @@ #pragma once #include "base.h" -#define SUBGHZ_PROTOCOL_ALUTECH_AT_4N_NAME "Alutech at-4n" +#define SUBGHZ_PROTOCOL_ALUTECH_AT_4N_NAME "Alutech AT-4N" typedef struct SubGhzProtocolDecoderAlutech_at_4n SubGhzProtocolDecoderAlutech_at_4n; typedef struct SubGhzProtocolEncoderAlutech_at_4n SubGhzProtocolEncoderAlutech_at_4n; diff --git a/lib/subghz/protocols/came.c b/lib/subghz/protocols/came.c index bbe3e487f..c3d9c8662 100644 --- a/lib/subghz/protocols/came.c +++ b/lib/subghz/protocols/came.c @@ -244,8 +244,11 @@ void subghz_protocol_decoder_came_feed(void* context, bool level, uint32_t durat switch(instance->decoder.parser_step) { case CameDecoderStepReset: if((!level) && (DURATION_DIFF(duration, subghz_protocol_came_const.te_short * 56) < - subghz_protocol_came_const.te_delta * 47)) { + subghz_protocol_came_const.te_delta * 63)) { + // 17920 us + 7050 us = 24970 us max possible value old one + // delta = 150 us x 63 = 9450 us + 17920 us = 27370 us max possible value //Found header CAME + // 26700 us or 24000 us max possible values instance->decoder.parser_step = CameDecoderStepFoundStartBit; } break; diff --git a/lib/subghz/protocols/came_twee.c b/lib/subghz/protocols/came_twee.c index 1d79d2201..2369e854d 100644 --- a/lib/subghz/protocols/came_twee.c +++ b/lib/subghz/protocols/came_twee.c @@ -260,6 +260,7 @@ SubGhzProtocolStatus subghz_protocol_came_twee_remote_controller(&instance->generic); subghz_protocol_encoder_came_twee_get_upload(instance); + instance->encoder.front = 0; // reset position before start instance->encoder.is_running = true; } while(false); @@ -269,6 +270,7 @@ SubGhzProtocolStatus void subghz_protocol_encoder_came_twee_stop(void* context) { SubGhzProtocolEncoderCameTwee* instance = context; instance->encoder.is_running = false; + instance->encoder.front = 0; // reset position } LevelDuration subghz_protocol_encoder_came_twee_yield(void* context) { diff --git a/lib/subghz/protocols/dooya.c b/lib/subghz/protocols/dooya.c index 47e95209e..816847840 100644 --- a/lib/subghz/protocols/dooya.c +++ b/lib/subghz/protocols/dooya.c @@ -308,7 +308,7 @@ void subghz_protocol_decoder_dooya_feed(void* context, bool level, uint32_t dura * Analysis of received data * @param instance Pointer to a SubGhzBlockGeneric* instance */ -static void subghz_protocol_somfy_telis_check_remote_controller(SubGhzBlockGeneric* instance) { +static void subghz_protocol_dooya_check_remote_controller(SubGhzBlockGeneric* instance) { /* * serial s/m ch key * long press down X * E1DC030533, 40b 111000011101110000000011 0000 0101 0011 0011 @@ -416,7 +416,7 @@ void subghz_protocol_decoder_dooya_get_string(void* context, FuriString* output) furi_assert(context); SubGhzProtocolDecoderDooya* instance = context; - subghz_protocol_somfy_telis_check_remote_controller(&instance->generic); + subghz_protocol_dooya_check_remote_controller(&instance->generic); furi_string_cat_printf( output, diff --git a/lib/subghz/protocols/feron.c b/lib/subghz/protocols/feron.c new file mode 100644 index 000000000..855856f51 --- /dev/null +++ b/lib/subghz/protocols/feron.c @@ -0,0 +1,352 @@ +#include "feron.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolFeron" + +static const SubGhzBlockConst subghz_protocol_feron_const = { + .te_short = 350, + .te_long = 750, + .te_delta = 150, + .min_count_bit_for_found = 32, +}; + +struct SubGhzProtocolDecoderFeron { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderFeron { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + FeronDecoderStepReset = 0, + FeronDecoderStepSaveDuration, + FeronDecoderStepCheckDuration, +} FeronDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_feron_decoder = { + .alloc = subghz_protocol_decoder_feron_alloc, + .free = subghz_protocol_decoder_feron_free, + + .feed = subghz_protocol_decoder_feron_feed, + .reset = subghz_protocol_decoder_feron_reset, + + .get_hash_data = subghz_protocol_decoder_feron_get_hash_data, + .serialize = subghz_protocol_decoder_feron_serialize, + .deserialize = subghz_protocol_decoder_feron_deserialize, + .get_string = subghz_protocol_decoder_feron_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_feron_encoder = { + .alloc = subghz_protocol_encoder_feron_alloc, + .free = subghz_protocol_encoder_feron_free, + + .deserialize = subghz_protocol_encoder_feron_deserialize, + .stop = subghz_protocol_encoder_feron_stop, + .yield = subghz_protocol_encoder_feron_yield, +}; + +const SubGhzProtocol subghz_protocol_feron = { + .name = SUBGHZ_PROTOCOL_FERON_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_feron_decoder, + .encoder = &subghz_protocol_feron_encoder, +}; + +void* subghz_protocol_encoder_feron_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderFeron* instance = malloc(sizeof(SubGhzProtocolEncoderFeron)); + + instance->base.protocol = &subghz_protocol_feron; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 256; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_feron_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderFeron* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderFeron instance + */ +static void subghz_protocol_encoder_feron_get_upload(SubGhzProtocolEncoderFeron* instance) { + furi_assert(instance); + size_t index = 0; + + // Send key and GAP + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + // Send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_feron_const.te_long); + if(i == 1) { + //Send 500/500 and gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_feron_const.te_short + 150); + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_feron_const.te_short + 150); + // Gap + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_feron_const.te_long * 6); + } else { + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_feron_const.te_short); + } + } else { + // Send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_feron_const.te_short); + if(i == 1) { + //Send 500/500 and gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_feron_const.te_short + 150); + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_feron_const.te_short + 150); + // Gap + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_feron_const.te_long * 6); + } else { + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_feron_const.te_long); + } + } + } + + instance->encoder.size_upload = index; + return; +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_feron_check_remote_controller(SubGhzBlockGeneric* instance) { + instance->serial = instance->data >> 16; +} + +SubGhzProtocolStatus + subghz_protocol_encoder_feron_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderFeron* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_feron_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_feron_check_remote_controller(&instance->generic); + subghz_protocol_encoder_feron_get_upload(instance); + instance->encoder.front = 0; + instance->encoder.is_running = true; + } while(false); + + return ret; +} + +void subghz_protocol_encoder_feron_stop(void* context) { + SubGhzProtocolEncoderFeron* instance = context; + instance->encoder.is_running = false; + instance->encoder.front = 0; +} + +LevelDuration subghz_protocol_encoder_feron_yield(void* context) { + SubGhzProtocolEncoderFeron* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_feron_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderFeron* instance = malloc(sizeof(SubGhzProtocolDecoderFeron)); + instance->base.protocol = &subghz_protocol_feron; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_feron_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderFeron* instance = context; + free(instance); +} + +void subghz_protocol_decoder_feron_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderFeron* instance = context; + instance->decoder.parser_step = FeronDecoderStepReset; +} + +void subghz_protocol_decoder_feron_feed(void* context, bool level, volatile uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderFeron* instance = context; + + // Feron Decoder + // 2025.04 - @xMasterX (MMX) + + // Key samples + /* + 0110001100111000 1000010101111010 - ON + 0110001100111000 1000010001111011 - OFF + + 0110001100111000 1000011001111001 - brightness up + 0110001100111000 1000011101111000 - brightness down + + 0110001100111000 1000001001111101 - scroll mode command + + ------------------------------------------ + 0110001100111000 0111000010001111 - R + 0110001100111000 0001101011100101 - B + 0110001100111000 0100000010111111 - G + */ + + switch(instance->decoder.parser_step) { + case FeronDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_feron_const.te_long * 6) < + subghz_protocol_feron_const.te_delta * 4)) { + //Found GAP + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = FeronDecoderStepSaveDuration; + } + break; + case FeronDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = FeronDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = FeronDecoderStepReset; + } + break; + case FeronDecoderStepCheckDuration: + if(!level) { + // Bit 0 is short and long timing = 350us HIGH (te_last) and 750us LOW + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_feron_const.te_short) < + subghz_protocol_feron_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_feron_const.te_long) < + subghz_protocol_feron_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = FeronDecoderStepSaveDuration; + // Bit 1 is long and short timing = 750us HIGH (te_last) and 350us LOW + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_feron_const.te_long) < + subghz_protocol_feron_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_feron_const.te_short) < + subghz_protocol_feron_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = FeronDecoderStepSaveDuration; + } else if( + // End of the key 500Low(we are here)/500High us + DURATION_DIFF( + duration, (uint16_t)(subghz_protocol_feron_const.te_short + (uint16_t)150)) < + subghz_protocol_feron_const.te_delta) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_feron_const.te_short) < + subghz_protocol_feron_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_feron_const.te_long) < + subghz_protocol_feron_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } + // If got 32 bits key reading is finished + if(instance->decoder.decode_count_bit == + subghz_protocol_feron_const.min_count_bit_for_found) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = FeronDecoderStepReset; + } else { + instance->decoder.parser_step = FeronDecoderStepReset; + } + } else { + instance->decoder.parser_step = FeronDecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_feron_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderFeron* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus subghz_protocol_decoder_feron_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderFeron* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + subghz_protocol_decoder_feron_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderFeron* instance = context; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, subghz_protocol_feron_const.min_count_bit_for_found); +} + +void subghz_protocol_decoder_feron_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderFeron* instance = context; + + subghz_protocol_feron_check_remote_controller(&instance->generic); + + furi_string_cat_printf( + output, + "%s %db\r\n" + "Key: 0x%08lX\r\n" + "Serial: 0x%04lX\r\n" + "Command: 0x%04lX\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0xFFFFFFFF), + instance->generic.serial, + (uint32_t)(instance->generic.data & 0xFFFF)); +} diff --git a/lib/subghz/protocols/feron.h b/lib/subghz/protocols/feron.h new file mode 100644 index 000000000..97f0eb6fe --- /dev/null +++ b/lib/subghz/protocols/feron.h @@ -0,0 +1,109 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_FERON_NAME "Feron" + +typedef struct SubGhzProtocolDecoderFeron SubGhzProtocolDecoderFeron; +typedef struct SubGhzProtocolEncoderFeron SubGhzProtocolEncoderFeron; + +extern const SubGhzProtocolDecoder subghz_protocol_feron_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_feron_encoder; +extern const SubGhzProtocol subghz_protocol_feron; + +/** + * Allocate SubGhzProtocolEncoderFeron. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderFeron* pointer to a SubGhzProtocolEncoderFeron instance + */ +void* subghz_protocol_encoder_feron_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderFeron. + * @param context Pointer to a SubGhzProtocolEncoderFeron instance + */ +void subghz_protocol_encoder_feron_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderFeron instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_encoder_feron_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderFeron instance + */ +void subghz_protocol_encoder_feron_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderFeron instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_feron_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderFeron. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderFeron* pointer to a SubGhzProtocolDecoderFeron instance + */ +void* subghz_protocol_decoder_feron_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderFeron. + * @param context Pointer to a SubGhzProtocolDecoderFeron instance + */ +void subghz_protocol_decoder_feron_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderFeron. + * @param context Pointer to a SubGhzProtocolDecoderFeron instance + */ +void subghz_protocol_decoder_feron_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderFeron instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_feron_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderFeron instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_feron_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderFeron. + * @param context Pointer to a SubGhzProtocolDecoderFeron instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus subghz_protocol_decoder_feron_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderFeron. + * @param context Pointer to a SubGhzProtocolDecoderFeron instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_decoder_feron_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderFeron instance + * @param output Resulting text + */ +void subghz_protocol_decoder_feron_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/gangqi.c b/lib/subghz/protocols/gangqi.c new file mode 100644 index 000000000..c8a241337 --- /dev/null +++ b/lib/subghz/protocols/gangqi.c @@ -0,0 +1,407 @@ +#include "gangqi.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolGangQi" + +static const SubGhzBlockConst subghz_protocol_gangqi_const = { + .te_short = 500, + .te_long = 1200, + .te_delta = 200, + .min_count_bit_for_found = 34, +}; + +struct SubGhzProtocolDecoderGangQi { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderGangQi { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + GangQiDecoderStepReset = 0, + GangQiDecoderStepSaveDuration, + GangQiDecoderStepCheckDuration, +} GangQiDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_gangqi_decoder = { + .alloc = subghz_protocol_decoder_gangqi_alloc, + .free = subghz_protocol_decoder_gangqi_free, + + .feed = subghz_protocol_decoder_gangqi_feed, + .reset = subghz_protocol_decoder_gangqi_reset, + + .get_hash_data = subghz_protocol_decoder_gangqi_get_hash_data, + .serialize = subghz_protocol_decoder_gangqi_serialize, + .deserialize = subghz_protocol_decoder_gangqi_deserialize, + .get_string = subghz_protocol_decoder_gangqi_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_gangqi_encoder = { + .alloc = subghz_protocol_encoder_gangqi_alloc, + .free = subghz_protocol_encoder_gangqi_free, + + .deserialize = subghz_protocol_encoder_gangqi_deserialize, + .stop = subghz_protocol_encoder_gangqi_stop, + .yield = subghz_protocol_encoder_gangqi_yield, +}; + +const SubGhzProtocol subghz_protocol_gangqi = { + .name = SUBGHZ_PROTOCOL_GANGQI_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_gangqi_decoder, + .encoder = &subghz_protocol_gangqi_encoder, +}; + +void* subghz_protocol_encoder_gangqi_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderGangQi* instance = malloc(sizeof(SubGhzProtocolEncoderGangQi)); + + instance->base.protocol = &subghz_protocol_gangqi; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 1024; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_gangqi_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderGangQi* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderGangQi instance + */ +static void subghz_protocol_encoder_gangqi_get_upload(SubGhzProtocolEncoderGangQi* instance) { + furi_assert(instance); + + // Generate new key + uint16_t serial = (uint16_t)((instance->generic.data >> 18) & 0xFFFF); + uint8_t const_and_button = (uint8_t)(0xD0 | instance->generic.btn); + uint8_t serial_high = (uint8_t)(serial >> 8); + uint8_t serial_low = (uint8_t)(serial & 0xFF); + uint8_t bytesum = (uint8_t)(0xC8 - serial_high - serial_low - const_and_button); + + instance->generic.data = (instance->generic.data >> 14) << 14 | (instance->generic.btn << 10) | + (bytesum << 2); + + size_t index = 0; + + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_gangqi_const.te_long * 2); + + for(size_t r = 0; r < 5; r++) { + // Send key and GAP between parcels + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + // Send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_gangqi_const.te_long); + if(i == 1) { + //Send gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, + (uint32_t)subghz_protocol_gangqi_const.te_short * 4 + + subghz_protocol_gangqi_const.te_delta); + } else { + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_gangqi_const.te_short); + } + } else { + // Send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_gangqi_const.te_short); + if(i == 1) { + //Send gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, + (uint32_t)subghz_protocol_gangqi_const.te_short * 4 + + subghz_protocol_gangqi_const.te_delta); + } else { + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_gangqi_const.te_long); + } + } + } + } + + instance->encoder.size_upload = index; + return; +} + +/** + * Analysis of received data and parsing serial number + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_gangqi_remote_controller(SubGhzBlockGeneric* instance) { + instance->btn = (instance->data >> 10) & 0xF; + instance->serial = (instance->data & 0xFFFFF0000) >> 16; + + // GangQi Decoder + // 09.2024 - @xMasterX (MMX) (last update - bytesum calculation at 02.2025) + // Thanks @Skorpionm for support! + // Thanks @Drone1950 and @mishamyte (who spent 2 weeks on this) for making this work properly + + // Example of correct bytesum calculation + // 0xC8 - serial_high - serial_low - constant_and_button +} + +SubGhzProtocolStatus + subghz_protocol_encoder_gangqi_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderGangQi* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_gangqi_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_gangqi_remote_controller(&instance->generic); + subghz_protocol_encoder_gangqi_get_upload(instance); + instance->encoder.front = 0; + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + uint8_t key_data[sizeof(uint64_t)] = {0}; + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF; + } + if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Key"); + break; + } + + instance->encoder.is_running = true; + } while(false); + + return ret; +} + +void subghz_protocol_encoder_gangqi_stop(void* context) { + SubGhzProtocolEncoderGangQi* instance = context; + instance->encoder.is_running = false; + instance->encoder.front = 0; +} + +LevelDuration subghz_protocol_encoder_gangqi_yield(void* context) { + SubGhzProtocolEncoderGangQi* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_gangqi_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderGangQi* instance = malloc(sizeof(SubGhzProtocolDecoderGangQi)); + instance->base.protocol = &subghz_protocol_gangqi; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_gangqi_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderGangQi* instance = context; + free(instance); +} + +void subghz_protocol_decoder_gangqi_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderGangQi* instance = context; + instance->decoder.parser_step = GangQiDecoderStepReset; +} + +void subghz_protocol_decoder_gangqi_feed(void* context, bool level, volatile uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderGangQi* instance = context; + + switch(instance->decoder.parser_step) { + case GangQiDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_long * 2) < + subghz_protocol_gangqi_const.te_delta * 5)) { + //Found GAP + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = GangQiDecoderStepSaveDuration; + } + break; + case GangQiDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = GangQiDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = GangQiDecoderStepReset; + } + break; + case GangQiDecoderStepCheckDuration: + if(!level) { + // Bit 0 is short and long timing + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_gangqi_const.te_short) < + subghz_protocol_gangqi_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_long) < + subghz_protocol_gangqi_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = GangQiDecoderStepSaveDuration; + // Bit 1 is long and short timing + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_gangqi_const.te_long) < + subghz_protocol_gangqi_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_short) < + subghz_protocol_gangqi_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = GangQiDecoderStepSaveDuration; + } else if( + // End of the key + DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_long * 2) < + subghz_protocol_gangqi_const.te_delta * 5) { + //Found next GAP and add bit 0 or 1 (only bit 0 was found on the remotes) + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_gangqi_const.te_short) < + subghz_protocol_gangqi_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_gangqi_const.te_long) < + subghz_protocol_gangqi_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } + // If got 34 bits key reading is finished + if(instance->decoder.decode_count_bit == + subghz_protocol_gangqi_const.min_count_bit_for_found) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = GangQiDecoderStepReset; + } else { + instance->decoder.parser_step = GangQiDecoderStepReset; + } + } else { + instance->decoder.parser_step = GangQiDecoderStepReset; + } + break; + } +} + +/** + * Get button name. + * @param btn Button number, 4 bit + */ +static const char* subghz_protocol_gangqi_get_button_name(uint8_t btn) { + const char* name_btn[16] = { + "Unknown", + "Exit settings", + "Volume setting", + "0x3", + "Vibro sens. setting", + "Settings mode", + "Ringtone setting", + "Ring", // D + "0x8", + "0x9", + "0xA", + "Alarm", // C + "0xC", + "Arm", // A + "Disarm", // B + "0xF"}; + return btn <= 0xf ? name_btn[btn] : name_btn[0]; +} + +uint8_t subghz_protocol_decoder_gangqi_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderGangQi* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus subghz_protocol_decoder_gangqi_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderGangQi* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + subghz_protocol_decoder_gangqi_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderGangQi* instance = context; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, subghz_protocol_gangqi_const.min_count_bit_for_found); +} + +void subghz_protocol_decoder_gangqi_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderGangQi* instance = context; + + // Parse serial + subghz_protocol_gangqi_remote_controller(&instance->generic); + + // Get byte sum + uint16_t serial = (uint16_t)((instance->generic.data >> 18) & 0xFFFF); + uint8_t const_and_button = (uint8_t)(0xD0 | instance->generic.btn); + uint8_t serial_high = (uint8_t)(serial >> 8); + uint8_t serial_low = (uint8_t)(serial & 0xFF); + // Type 1 is what original remotes use, type 2 is "backdoor" sum that receiver accepts too + uint8_t sum_type1 = (uint8_t)(0xC8 - serial_high - serial_low - const_and_button); + uint8_t sum_type2 = (uint8_t)(0x02 + serial_high + serial_low + const_and_button); + + furi_string_cat_printf( + output, + "%s %db\r\n" + "Key: 0x%X%08lX\r\n" + "Serial: 0x%05lX\r\n" + "Sum: 0x%02X Sum2: 0x%02X\r\n" + "Btn: 0x%01X - %s\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint8_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data & 0xFFFFFFFF), + instance->generic.serial, + sum_type1, + sum_type2, + instance->generic.btn, + subghz_protocol_gangqi_get_button_name(instance->generic.btn)); +} diff --git a/lib/subghz/protocols/gangqi.h b/lib/subghz/protocols/gangqi.h new file mode 100644 index 000000000..e90f4e7c3 --- /dev/null +++ b/lib/subghz/protocols/gangqi.h @@ -0,0 +1,109 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_GANGQI_NAME "GangQi" + +typedef struct SubGhzProtocolDecoderGangQi SubGhzProtocolDecoderGangQi; +typedef struct SubGhzProtocolEncoderGangQi SubGhzProtocolEncoderGangQi; + +extern const SubGhzProtocolDecoder subghz_protocol_gangqi_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_gangqi_encoder; +extern const SubGhzProtocol subghz_protocol_gangqi; + +/** + * Allocate SubGhzProtocolEncoderGangQi. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderGangQi* pointer to a SubGhzProtocolEncoderGangQi instance + */ +void* subghz_protocol_encoder_gangqi_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderGangQi. + * @param context Pointer to a SubGhzProtocolEncoderGangQi instance + */ +void subghz_protocol_encoder_gangqi_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderGangQi instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_encoder_gangqi_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderGangQi instance + */ +void subghz_protocol_encoder_gangqi_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderGangQi instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_gangqi_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderGangQi. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderGangQi* pointer to a SubGhzProtocolDecoderGangQi instance + */ +void* subghz_protocol_decoder_gangqi_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderGangQi. + * @param context Pointer to a SubGhzProtocolDecoderGangQi instance + */ +void subghz_protocol_decoder_gangqi_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderGangQi. + * @param context Pointer to a SubGhzProtocolDecoderGangQi instance + */ +void subghz_protocol_decoder_gangqi_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderGangQi instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_gangqi_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderGangQi instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_gangqi_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderGangQi. + * @param context Pointer to a SubGhzProtocolDecoderGangQi instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus subghz_protocol_decoder_gangqi_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderGangQi. + * @param context Pointer to a SubGhzProtocolDecoderGangQi instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_decoder_gangqi_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderGangQi instance + * @param output Resulting text + */ +void subghz_protocol_decoder_gangqi_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/hay21.c b/lib/subghz/protocols/hay21.c new file mode 100644 index 000000000..f4e109496 --- /dev/null +++ b/lib/subghz/protocols/hay21.c @@ -0,0 +1,269 @@ +#include "hay21.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolHay21" + +static const SubGhzBlockConst subghz_protocol_hay21_const = { + .te_short = 300, + .te_long = 700, + .te_delta = 150, + .min_count_bit_for_found = 21, +}; + +struct SubGhzProtocolDecoderHay21 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderHay21 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + Hay21DecoderStepReset = 0, + Hay21DecoderStepSaveDuration, + Hay21DecoderStepCheckDuration, +} Hay21DecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_hay21_decoder = { + .alloc = subghz_protocol_decoder_hay21_alloc, + .free = subghz_protocol_decoder_hay21_free, + + .feed = subghz_protocol_decoder_hay21_feed, + .reset = subghz_protocol_decoder_hay21_reset, + + .get_hash_data = subghz_protocol_decoder_hay21_get_hash_data, + .serialize = subghz_protocol_decoder_hay21_serialize, + .deserialize = subghz_protocol_decoder_hay21_deserialize, + .get_string = subghz_protocol_decoder_hay21_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_hay21_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol subghz_protocol_hay21 = { + .name = SUBGHZ_PROTOCOL_HAY21_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &subghz_protocol_hay21_decoder, + .encoder = &subghz_protocol_hay21_encoder, +}; + +/** + * Analysis of received data and parsing serial number + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_hay21_remote_controller(SubGhzBlockGeneric* instance) { + instance->btn = (instance->data >> 13) & 0xFF; + instance->serial = (instance->data >> 5) & 0xFF; + instance->cnt = (instance->data >> 1) & 0xF; + + // Hay21 Decoder + // 09.2024 - @xMasterX (MMX) + + // Key samples (inverted) + // button serial CNT (goes lower since 0/1 are inverted) + //14A84A = 000 10100101 01000010 0101 0 (cnt 5) + //14A848 = 000 10100101 01000010 0100 0 (cnt 4) + //14A846 = 000 10100101 01000010 0011 0 (cnt 3) + //14A844 = 000 10100101 01000010 0010 0 (cnt 2) + //14A842 = 000 10100101 01000010 0001 0 (cnt 1) + //14A840 = 000 10100101 01000010 0000 0 (cnt 0) + //14A85E = 000 10100101 01000010 1111 0 (cnt F) + //14A85C = 000 10100101 01000010 1110 0 (cnt E) + //14A85A = 000 10100101 01000010 1101 0 (cnt D) + //14A858 = 000 10100101 01000010 1100 0 (cnt C) + //14A856 = 000 10100101 01000010 1011 0 (cnt B) + // 0xA5 (Labeled as On/Off on the remote board) + // 0x3C (Labeled as Mode on the remote board) + // 0x42 (Serial) + // BTN Serial CNT + //078854 = 000 00111100 01000010 1010 0 (cnt A) + //078852 = 000 00111100 01000010 1001 0 (cnt 9) + //078850 = 000 00111100 01000010 1000 0 (cnt 8) + //07884E = 000 00111100 01000010 0111 0 (cnt 7) + // Inverted back + //1877B9 = 000 11000011 10111101 1100 1 + //1877BB = 000 11000011 10111101 1101 1 + //1877BD = 000 11000011 10111101 1110 1 + //0B57BF = 000 01011010 10111101 1111 1 +} + +void* subghz_protocol_decoder_hay21_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderHay21* instance = malloc(sizeof(SubGhzProtocolDecoderHay21)); + instance->base.protocol = &subghz_protocol_hay21; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_hay21_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHay21* instance = context; + free(instance); +} + +void subghz_protocol_decoder_hay21_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHay21* instance = context; + instance->decoder.parser_step = Hay21DecoderStepReset; +} + +void subghz_protocol_decoder_hay21_feed(void* context, bool level, volatile uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderHay21* instance = context; + + switch(instance->decoder.parser_step) { + case Hay21DecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_hay21_const.te_long * 6) < + subghz_protocol_hay21_const.te_delta * 3)) { + //Found GAP + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = Hay21DecoderStepSaveDuration; + } + break; + case Hay21DecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = Hay21DecoderStepCheckDuration; + } else { + instance->decoder.parser_step = Hay21DecoderStepReset; + } + break; + case Hay21DecoderStepCheckDuration: + if(!level) { + // Bit 1 is long + short timing + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hay21_const.te_long) < + subghz_protocol_hay21_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_hay21_const.te_short) < + subghz_protocol_hay21_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = Hay21DecoderStepSaveDuration; + // Bit 0 is short + long timing + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hay21_const.te_short) < + subghz_protocol_hay21_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_hay21_const.te_long) < + subghz_protocol_hay21_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = Hay21DecoderStepSaveDuration; + } else if( + // End of the key + DURATION_DIFF(duration, subghz_protocol_hay21_const.te_long * 6) < + subghz_protocol_hay21_const.te_delta * 2) { + //Found next GAP and add bit 0 or 1 + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hay21_const.te_long) < + subghz_protocol_hay21_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hay21_const.te_short) < + subghz_protocol_hay21_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } + // If got 21 bits key reading is finished + if(instance->decoder.decode_count_bit == + subghz_protocol_hay21_const.min_count_bit_for_found) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = Hay21DecoderStepReset; + } else { + instance->decoder.parser_step = Hay21DecoderStepReset; + } + } else { + instance->decoder.parser_step = Hay21DecoderStepReset; + } + break; + } +} + +/** + * Get button name. + * @param btn Button number, 4 bit + */ +static const char* subghz_protocol_hay21_get_button_name(uint8_t btn) { + const char* btn_name; + switch(btn) { + case 0x5A: + btn_name = "On/Off"; + break; + case 0xC3: + btn_name = "Mode"; + break; + case 0x88: + btn_name = "Hold"; + break; + default: + btn_name = "Unknown"; + break; + } + return btn_name; +} + +uint8_t subghz_protocol_decoder_hay21_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHay21* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus subghz_protocol_decoder_hay21_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderHay21* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + subghz_protocol_decoder_hay21_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderHay21* instance = context; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, subghz_protocol_hay21_const.min_count_bit_for_found); +} + +void subghz_protocol_decoder_hay21_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderHay21* instance = context; + + // Parse serial, button, counter + subghz_protocol_hay21_remote_controller(&instance->generic); + + 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", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0xFFFFFFFF), + (uint8_t)(instance->generic.serial & 0xFF), + instance->generic.btn, + subghz_protocol_hay21_get_button_name(instance->generic.btn), + (uint8_t)(instance->generic.cnt & 0xF)); +} diff --git a/lib/subghz/protocols/hay21.h b/lib/subghz/protocols/hay21.h new file mode 100644 index 000000000..22720d7dd --- /dev/null +++ b/lib/subghz/protocols/hay21.h @@ -0,0 +1,74 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_HAY21_NAME "Hay21" + +typedef struct SubGhzProtocolDecoderHay21 SubGhzProtocolDecoderHay21; +typedef struct SubGhzProtocolEncoderHay21 SubGhzProtocolEncoderHay21; + +extern const SubGhzProtocolDecoder subghz_protocol_hay21_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_hay21_encoder; +extern const SubGhzProtocol subghz_protocol_hay21; + +/** + * Allocate SubGhzProtocolDecoderHay21. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderHay21* pointer to a SubGhzProtocolDecoderHay21 instance + */ +void* subghz_protocol_decoder_hay21_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderHay21. + * @param context Pointer to a SubGhzProtocolDecoderHay21 instance + */ +void subghz_protocol_decoder_hay21_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderHay21. + * @param context Pointer to a SubGhzProtocolDecoderHay21 instance + */ +void subghz_protocol_decoder_hay21_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderHay21 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_hay21_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderHay21 instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_hay21_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderHay21. + * @param context Pointer to a SubGhzProtocolDecoderHay21 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus subghz_protocol_decoder_hay21_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderHay21. + * @param context Pointer to a SubGhzProtocolDecoderHay21 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_decoder_hay21_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderHay21 instance + * @param output Resulting text + */ +void subghz_protocol_decoder_hay21_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/hollarm.c b/lib/subghz/protocols/hollarm.c new file mode 100644 index 000000000..70ba6c586 --- /dev/null +++ b/lib/subghz/protocols/hollarm.c @@ -0,0 +1,408 @@ +#include "hollarm.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolHollarm" + +static const SubGhzBlockConst subghz_protocol_hollarm_const = { + .te_short = 200, + .te_long = 1000, + .te_delta = 200, + .min_count_bit_for_found = 42, +}; + +struct SubGhzProtocolDecoderHollarm { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderHollarm { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + HollarmDecoderStepReset = 0, + HollarmDecoderStepSaveDuration, + HollarmDecoderStepCheckDuration, +} HollarmDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_hollarm_decoder = { + .alloc = subghz_protocol_decoder_hollarm_alloc, + .free = subghz_protocol_decoder_hollarm_free, + + .feed = subghz_protocol_decoder_hollarm_feed, + .reset = subghz_protocol_decoder_hollarm_reset, + + .get_hash_data = subghz_protocol_decoder_hollarm_get_hash_data, + .serialize = subghz_protocol_decoder_hollarm_serialize, + .deserialize = subghz_protocol_decoder_hollarm_deserialize, + .get_string = subghz_protocol_decoder_hollarm_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_hollarm_encoder = { + .alloc = subghz_protocol_encoder_hollarm_alloc, + .free = subghz_protocol_encoder_hollarm_free, + + .deserialize = subghz_protocol_encoder_hollarm_deserialize, + .stop = subghz_protocol_encoder_hollarm_stop, + .yield = subghz_protocol_encoder_hollarm_yield, +}; + +const SubGhzProtocol subghz_protocol_hollarm = { + .name = SUBGHZ_PROTOCOL_HOLLARM_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_hollarm_decoder, + .encoder = &subghz_protocol_hollarm_encoder, +}; + +void* subghz_protocol_encoder_hollarm_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderHollarm* instance = malloc(sizeof(SubGhzProtocolEncoderHollarm)); + + instance->base.protocol = &subghz_protocol_hollarm; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 256; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_hollarm_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderHollarm* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderHollarm instance + */ +static void subghz_protocol_encoder_hollarm_get_upload(SubGhzProtocolEncoderHollarm* instance) { + furi_assert(instance); + + // Generate new key + uint64_t new_key = (instance->generic.data >> 12) << 12 | (instance->generic.btn << 8); + + uint8_t bytesum = ((new_key >> 32) & 0xFF) + ((new_key >> 24) & 0xFF) + + ((new_key >> 16) & 0xFF) + ((new_key >> 8) & 0xFF); + + instance->generic.data = (new_key | bytesum); + + size_t index = 0; + + // Send key and GAP between parcels + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + // Read and prepare levels with 2 bit (was saved for better parsing) to the left offset to fit with the original remote transmission + if(bit_read((instance->generic.data << 2), i - 1)) { + // Send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_hollarm_const.te_short); + if(i == 1) { + //Send gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_hollarm_const.te_short * 12); + } else { + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_hollarm_const.te_short * 8); + } + } else { + // Send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_hollarm_const.te_short); + if(i == 1) { + //Send gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_hollarm_const.te_short * 12); + } else { + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_hollarm_const.te_long); + } + } + } + + instance->encoder.size_upload = index; + return; +} + +/** + * Analysis of received data and parsing serial number + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_hollarm_remote_controller(SubGhzBlockGeneric* instance) { + instance->btn = (instance->data >> 8) & 0xF; + instance->serial = (instance->data & 0xFFFFFFF0000) >> 16; + + // Hollarm Decoder + // 09.2024 - @xMasterX (MMX) + // Thanks @Skorpionm for support! + + // F0B93422FF = FF 8bit Sum + // F0B93421FE = FE 8bit Sum + // F0B9342401 = 01 8bit Sum + // F0B9342805 = 05 8bit Sum + + // Serial (moved 2bit to right) | Btn | 8b previous 4 bytes sum + // 00001111000010111001001101000010 0010 11111111 btn = (0x2) + // 00001111000010111001001101000010 0001 11111110 btn = (0x1) + // 00001111000010111001001101000010 0100 00000001 btn = (0x4) + // 00001111000010111001001101000010 1000 00000101 btn = (0x8) +} + +SubGhzProtocolStatus + subghz_protocol_encoder_hollarm_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderHollarm* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_hollarm_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_hollarm_remote_controller(&instance->generic); + subghz_protocol_encoder_hollarm_get_upload(instance); + instance->encoder.front = 0; + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + uint8_t key_data[sizeof(uint64_t)] = {0}; + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF; + } + if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Key"); + break; + } + + instance->encoder.is_running = true; + } while(false); + + return ret; +} + +void subghz_protocol_encoder_hollarm_stop(void* context) { + SubGhzProtocolEncoderHollarm* instance = context; + instance->encoder.is_running = false; + instance->encoder.front = 0; +} + +LevelDuration subghz_protocol_encoder_hollarm_yield(void* context) { + SubGhzProtocolEncoderHollarm* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_hollarm_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderHollarm* instance = malloc(sizeof(SubGhzProtocolDecoderHollarm)); + instance->base.protocol = &subghz_protocol_hollarm; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_hollarm_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHollarm* instance = context; + free(instance); +} + +void subghz_protocol_decoder_hollarm_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHollarm* instance = context; + instance->decoder.parser_step = HollarmDecoderStepReset; +} + +void subghz_protocol_decoder_hollarm_feed(void* context, bool level, volatile uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderHollarm* instance = context; + + switch(instance->decoder.parser_step) { + case HollarmDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_hollarm_const.te_short * 12) < + subghz_protocol_hollarm_const.te_delta * 2)) { + //Found GAP between parcels + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = HollarmDecoderStepSaveDuration; + } + break; + case HollarmDecoderStepSaveDuration: + // Save HIGH level timing for next step + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = HollarmDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = HollarmDecoderStepReset; + } + break; + case HollarmDecoderStepCheckDuration: + if(!level) { + // Bit 0 is short 200us HIGH + long 1000us LOW timing + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hollarm_const.te_short) < + subghz_protocol_hollarm_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_hollarm_const.te_long) < + subghz_protocol_hollarm_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = HollarmDecoderStepSaveDuration; + // Bit 1 is short 200us HIGH + short x8 = 1600us LOW timing + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hollarm_const.te_short) < + subghz_protocol_hollarm_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_hollarm_const.te_short * 8) < + subghz_protocol_hollarm_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = HollarmDecoderStepSaveDuration; + } else if( + // End of the key + DURATION_DIFF(duration, subghz_protocol_hollarm_const.te_short * 12) < + subghz_protocol_hollarm_const.te_delta) { + // When next GAP is found add bit 0 and do check for read finish + // (we have 42 high level pulses, last or first one may be a stop/start bit but we will parse it as zero) + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + + // If got 42 bits key reading is finished + if(instance->decoder.decode_count_bit == + subghz_protocol_hollarm_const.min_count_bit_for_found) { + // Saving with 2bit to the right offset for proper parsing + instance->generic.data = (instance->decoder.decode_data >> 2); + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + + uint8_t bytesum = ((instance->generic.data >> 32) & 0xFF) + + ((instance->generic.data >> 24) & 0xFF) + + ((instance->generic.data >> 16) & 0xFF) + + ((instance->generic.data >> 8) & 0xFF); + + if(bytesum != (instance->generic.data & 0xFF)) { + // Check if the key is valid by verifying the sum + instance->generic.data = 0; + instance->generic.data_count_bit = 0; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = HollarmDecoderStepReset; + break; + } + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = HollarmDecoderStepReset; + } else { + instance->decoder.parser_step = HollarmDecoderStepReset; + } + } else { + instance->decoder.parser_step = HollarmDecoderStepReset; + } + break; + } +} + +/** + * Get button name. + * @param btn Button number, 4 bit + */ +static const char* subghz_protocol_hollarm_get_button_name(uint8_t btn) { + const char* name_btn[16] = { + "Unknown", + "Disarm", // B (2) + "Arm", // A (1) + "0x3", + "Ringtone/Alarm", // C (3) + "0x5", + "0x6", + "0x7", + "Ring", // D (4) + "Settings mode", + "Exit settings", + "Vibro sens. setting", + "Not used\n(in settings)", + "Volume setting", + "0xE", + "0xF"}; + return btn <= 0xf ? name_btn[btn] : name_btn[0]; +} + +uint8_t subghz_protocol_decoder_hollarm_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHollarm* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus subghz_protocol_decoder_hollarm_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderHollarm* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + subghz_protocol_decoder_hollarm_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderHollarm* instance = context; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, subghz_protocol_hollarm_const.min_count_bit_for_found); +} + +void subghz_protocol_decoder_hollarm_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderHollarm* instance = context; + + // Parse serial + subghz_protocol_hollarm_remote_controller(&instance->generic); + // Get byte sum + uint8_t bytesum = + ((instance->generic.data >> 32) & 0xFF) + ((instance->generic.data >> 24) & 0xFF) + + ((instance->generic.data >> 16) & 0xFF) + ((instance->generic.data >> 8) & 0xFF); + + furi_string_cat_printf( + output, + "%s %db\r\n" + "Key: 0x%02lX%08lX\r\n" + "Serial: 0x%06lX Sum: %02X\r\n" + "Btn: 0x%01X - %s\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)instance->generic.data, + instance->generic.serial, + bytesum, + instance->generic.btn, + subghz_protocol_hollarm_get_button_name(instance->generic.btn)); +} diff --git a/lib/subghz/protocols/hollarm.h b/lib/subghz/protocols/hollarm.h new file mode 100644 index 000000000..8749198a1 --- /dev/null +++ b/lib/subghz/protocols/hollarm.h @@ -0,0 +1,109 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_HOLLARM_NAME "Hollarm" + +typedef struct SubGhzProtocolDecoderHollarm SubGhzProtocolDecoderHollarm; +typedef struct SubGhzProtocolEncoderHollarm SubGhzProtocolEncoderHollarm; + +extern const SubGhzProtocolDecoder subghz_protocol_hollarm_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_hollarm_encoder; +extern const SubGhzProtocol subghz_protocol_hollarm; + +/** + * Allocate SubGhzProtocolEncoderHollarm. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderHollarm* pointer to a SubGhzProtocolEncoderHollarm instance + */ +void* subghz_protocol_encoder_hollarm_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderHollarm. + * @param context Pointer to a SubGhzProtocolEncoderHollarm instance + */ +void subghz_protocol_encoder_hollarm_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderHollarm instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_encoder_hollarm_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderHollarm instance + */ +void subghz_protocol_encoder_hollarm_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderHollarm instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_hollarm_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderHollarm. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderHollarm* pointer to a SubGhzProtocolDecoderHollarm instance + */ +void* subghz_protocol_decoder_hollarm_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderHollarm. + * @param context Pointer to a SubGhzProtocolDecoderHollarm instance + */ +void subghz_protocol_decoder_hollarm_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderHollarm. + * @param context Pointer to a SubGhzProtocolDecoderHollarm instance + */ +void subghz_protocol_decoder_hollarm_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderHollarm instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_hollarm_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderHollarm instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_hollarm_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderHollarm. + * @param context Pointer to a SubGhzProtocolDecoderHollarm instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus subghz_protocol_decoder_hollarm_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderHollarm. + * @param context Pointer to a SubGhzProtocolDecoderHollarm instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_decoder_hollarm_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderHollarm instance + * @param output Resulting text + */ +void subghz_protocol_decoder_hollarm_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/holtek_ht12x.c b/lib/subghz/protocols/holtek_ht12x.c index 302b78598..be2dfd406 100644 --- a/lib/subghz/protocols/holtek_ht12x.c +++ b/lib/subghz/protocols/holtek_ht12x.c @@ -234,8 +234,10 @@ void subghz_protocol_decoder_holtek_th12x_feed(void* context, bool level, uint32 switch(instance->decoder.parser_step) { case Holtek_HT12XDecoderStepReset: - if((!level) && (DURATION_DIFF(duration, subghz_protocol_holtek_th12x_const.te_short * 36) < - subghz_protocol_holtek_th12x_const.te_delta * 36)) { + if((!level) && (DURATION_DIFF(duration, subghz_protocol_holtek_th12x_const.te_short * 28) < + subghz_protocol_holtek_th12x_const.te_delta * 20)) { + // 18720 us old max value + // 12960 us corrected max value //Found Preambula instance->decoder.parser_step = Holtek_HT12XDecoderStepFoundStartBit; } diff --git a/lib/subghz/protocols/hormann.c b/lib/subghz/protocols/hormann.c index 70954bb39..43a490ff9 100644 --- a/lib/subghz/protocols/hormann.c +++ b/lib/subghz/protocols/hormann.c @@ -158,6 +158,7 @@ SubGhzProtocolStatus flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); if(!subghz_protocol_encoder_hormann_get_upload(instance)) { + instance->encoder.front = 0; // reset position before start ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; } @@ -170,6 +171,7 @@ SubGhzProtocolStatus void subghz_protocol_encoder_hormann_stop(void* context) { SubGhzProtocolEncoderHormann* instance = context; instance->encoder.is_running = false; + instance->encoder.front = 0; // reset position } LevelDuration subghz_protocol_encoder_hormann_yield(void* context) { diff --git a/lib/subghz/protocols/intertechno_v3.c b/lib/subghz/protocols/intertechno_v3.c index b9af72a57..da25e1e75 100644 --- a/lib/subghz/protocols/intertechno_v3.c +++ b/lib/subghz/protocols/intertechno_v3.c @@ -193,6 +193,7 @@ SubGhzProtocolStatus subghz_protocol_encoder_intertechno_v3_deserialize( void subghz_protocol_encoder_intertechno_v3_stop(void* context) { SubGhzProtocolEncoderIntertechno_V3* instance = context; instance->encoder.is_running = false; + instance->encoder.front = 0; // reset position } LevelDuration subghz_protocol_encoder_intertechno_v3_yield(void* context) { diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index b38abb75b..11b9e1587 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -299,7 +299,7 @@ SubGhzProtocolStatus ret = SubGhzProtocolStatusErrorParserKey; break; } - + instance->encoder.front = 0; // reset before start instance->encoder.is_running = true; } while(false); @@ -309,6 +309,7 @@ SubGhzProtocolStatus void subghz_protocol_encoder_keeloq_stop(void* context) { SubGhzProtocolEncoderKeeloq* instance = context; instance->encoder.is_running = false; + instance->encoder.front = 0; // reset position } LevelDuration subghz_protocol_encoder_keeloq_yield(void* context) { diff --git a/lib/subghz/protocols/legrand.c b/lib/subghz/protocols/legrand.c new file mode 100644 index 000000000..0df0d255c --- /dev/null +++ b/lib/subghz/protocols/legrand.c @@ -0,0 +1,398 @@ +#include "legrand.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolLegrand" + +static const SubGhzBlockConst subghz_protocol_legrand_const = { + .te_short = 375, + .te_long = 1125, + .te_delta = 150, + .min_count_bit_for_found = 18, +}; + +struct SubGhzProtocolDecoderLegrand { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint32_t te; + uint32_t last_data; +}; + +struct SubGhzProtocolEncoderLegrand { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; + + uint32_t te; +}; + +typedef enum { + LegrandDecoderStepReset = 0, + LegrandDecoderStepFirstBit, + LegrandDecoderStepSaveDuration, + LegrandDecoderStepCheckDuration, +} LegrandDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_legrand_decoder = { + .alloc = subghz_protocol_decoder_legrand_alloc, + .free = subghz_protocol_decoder_legrand_free, + + .feed = subghz_protocol_decoder_legrand_feed, + .reset = subghz_protocol_decoder_legrand_reset, + + .get_hash_data = subghz_protocol_decoder_legrand_get_hash_data, + .serialize = subghz_protocol_decoder_legrand_serialize, + .deserialize = subghz_protocol_decoder_legrand_deserialize, + .get_string = subghz_protocol_decoder_legrand_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_legrand_encoder = { + .alloc = subghz_protocol_encoder_legrand_alloc, + .free = subghz_protocol_encoder_legrand_free, + + .deserialize = subghz_protocol_encoder_legrand_deserialize, + .stop = subghz_protocol_encoder_legrand_stop, + .yield = subghz_protocol_encoder_legrand_yield, +}; + +const SubGhzProtocol subghz_protocol_legrand = { + .name = SUBGHZ_PROTOCOL_LEGRAND_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_legrand_decoder, + .encoder = &subghz_protocol_legrand_encoder, +}; + +void* subghz_protocol_encoder_legrand_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderLegrand* instance = malloc(sizeof(SubGhzProtocolEncoderLegrand)); + + instance->base.protocol = &subghz_protocol_legrand; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = + (subghz_protocol_legrand_const.min_count_bit_for_found * 6) * 2 + 2; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_legrand_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderLegrand* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderLegrand instance + * @return true On success + */ +static bool subghz_protocol_encoder_legrand_get_upload(SubGhzProtocolEncoderLegrand* instance) { + furi_assert(instance); + + //size_t size_upload = (instance->generic.data_count_bit * 2) + 1; + //if(size_upload != instance->encoder.size_upload) { + // FURI_LOG_E(TAG, "Invalid data bit count"); + // return false; + //} + + size_t index = 0; + + for(size_t r = 0; r < 5; r++) { + // Send sync + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)instance->te * 16); // 5728 + // Send key data + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + // send bit 1 + if(i == instance->generic.data_count_bit) { + //Send first bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)instance->te * 3); + } else { + // send bit 1 regular + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)instance->te); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)instance->te * 3); + } + } else { + // send bit 0 + if(i == instance->generic.data_count_bit) { + //Send first bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)instance->te); + } else { + // send bit 0 regular + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)instance->te * 3); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)instance->te); + } + } + } + } + instance->encoder.size_upload = index; + + return true; +} + +SubGhzProtocolStatus + subghz_protocol_encoder_legrand_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderLegrand* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_legrand_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + ret = SubGhzProtocolStatusErrorParserOthers; + break; + } + if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { + FURI_LOG_E(TAG, "Missing TE"); + ret = SubGhzProtocolStatusErrorParserTe; + break; + } + // optional parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_legrand_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } + instance->encoder.is_running = true; + } while(false); + + return ret; +} + +void subghz_protocol_encoder_legrand_stop(void* context) { + SubGhzProtocolEncoderLegrand* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_legrand_yield(void* context) { + SubGhzProtocolEncoderLegrand* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_legrand_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderLegrand* instance = malloc(sizeof(SubGhzProtocolDecoderLegrand)); + instance->base.protocol = &subghz_protocol_legrand; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_legrand_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderLegrand* instance = context; + free(instance); +} + +void subghz_protocol_decoder_legrand_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderLegrand* instance = context; + instance->decoder.parser_step = LegrandDecoderStepReset; + instance->last_data = 0; +} + +void subghz_protocol_decoder_legrand_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderLegrand* instance = context; + + switch(instance->decoder.parser_step) { + case LegrandDecoderStepReset: + if(!level && DURATION_DIFF(duration, subghz_protocol_legrand_const.te_short * 16) < + subghz_protocol_legrand_const.te_delta * 8) { // 6000 +- 1200 + instance->decoder.parser_step = LegrandDecoderStepFirstBit; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->te = 0; + } + break; + case LegrandDecoderStepFirstBit: + if(level) { + if(DURATION_DIFF(duration, subghz_protocol_legrand_const.te_short) < + subghz_protocol_legrand_const.te_delta) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->te += duration * 4; // long low that is part of sync, then short high + } + + if(DURATION_DIFF(duration, subghz_protocol_legrand_const.te_long) < + subghz_protocol_legrand_const.te_delta * 3) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->te += duration / 3 * 4; // short low that is part of sync, then long high + } + + if(instance->decoder.decode_count_bit > 0) { + // advance to the next step if either short or long is found + instance->decoder.parser_step = LegrandDecoderStepSaveDuration; + break; + } + } + + instance->decoder.parser_step = LegrandDecoderStepReset; + break; + case LegrandDecoderStepSaveDuration: + if(!level) { + instance->decoder.te_last = duration; + instance->te += duration; + instance->decoder.parser_step = LegrandDecoderStepCheckDuration; + break; + } + + instance->decoder.parser_step = LegrandDecoderStepReset; + break; + case LegrandDecoderStepCheckDuration: + if(level) { + uint8_t found = 0; + + if(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_legrand_const.te_long) < + subghz_protocol_legrand_const.te_delta * 3 && + DURATION_DIFF(duration, subghz_protocol_legrand_const.te_short) < + subghz_protocol_legrand_const.te_delta) { + found = 1; + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } + + if(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_legrand_const.te_short) < + subghz_protocol_legrand_const.te_delta && + DURATION_DIFF(duration, subghz_protocol_legrand_const.te_long) < + subghz_protocol_legrand_const.te_delta * 3) { + found = 1; + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } + + if(found) { + instance->te += duration; + + if(instance->decoder.decode_count_bit < + subghz_protocol_legrand_const.min_count_bit_for_found) { + instance->decoder.parser_step = LegrandDecoderStepSaveDuration; + break; + } + + // enough bits for a packet found, save it only if there was a previous packet + // with the same data + if(instance->last_data && (instance->last_data == instance->decoder.decode_data)) { + instance->te /= instance->decoder.decode_count_bit * 4; + + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + + if(instance->base.callback) { + instance->base.callback(&instance->base, instance->base.context); + } + } + instance->last_data = instance->decoder.decode_data; + // fallthrough to reset, the next bit is expected to be a sync + // it also takes care of resetting the decoder state + } + } + + instance->decoder.parser_step = LegrandDecoderStepReset; + break; + } +} + +uint8_t subghz_protocol_decoder_legrand_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderLegrand* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus subghz_protocol_decoder_legrand_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderLegrand* instance = context; + SubGhzProtocolStatus ret = + subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + if((ret == SubGhzProtocolStatusOk) && + !flipper_format_write_uint32(flipper_format, "TE", &instance->te, 1)) { + FURI_LOG_E(TAG, "Unable to add TE"); + ret = SubGhzProtocolStatusErrorParserTe; + } + return ret; +} + +SubGhzProtocolStatus + subghz_protocol_decoder_legrand_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderLegrand* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_legrand_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + ret = SubGhzProtocolStatusErrorParserOthers; + break; + } + if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { + FURI_LOG_E(TAG, "Missing TE"); + ret = SubGhzProtocolStatusErrorParserTe; + break; + } + } while(false); + + return ret; +} + +void subghz_protocol_decoder_legrand_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderLegrand* instance = context; + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%05lX\r\n" + "Te:%luus\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0xFFFFFF), + instance->te); +} diff --git a/lib/subghz/protocols/legrand.h b/lib/subghz/protocols/legrand.h new file mode 100644 index 000000000..0948ddb8b --- /dev/null +++ b/lib/subghz/protocols/legrand.h @@ -0,0 +1,117 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_LEGRAND_NAME "Legrand" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SubGhzProtocolDecoderLegrand SubGhzProtocolDecoderLegrand; +typedef struct SubGhzProtocolEncoderLegrand SubGhzProtocolEncoderLegrand; + +extern const SubGhzProtocolDecoder subghz_protocol_legrand_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_legrand_encoder; +extern const SubGhzProtocol subghz_protocol_legrand; + +/** + * Allocate SubGhzProtocolEncoderLegrand. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderLegrand* pointer to a SubGhzProtocolEncoderLegrand instance + */ +void* subghz_protocol_encoder_legrand_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderLegrand. + * @param context Pointer to a SubGhzProtocolEncoderLegrand instance + */ +void subghz_protocol_encoder_legrand_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderLegrand instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_encoder_legrand_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderLegrand instance + */ +void subghz_protocol_encoder_legrand_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderLegrand instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_legrand_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderLegrand. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderLegrand* pointer to a SubGhzProtocolDecoderLegrand instance + */ +void* subghz_protocol_decoder_legrand_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderLegrand. + * @param context Pointer to a SubGhzProtocolDecoderLegrand instance + */ +void subghz_protocol_decoder_legrand_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderLegrand. + * @param context Pointer to a SubGhzProtocolDecoderLegrand instance + */ +void subghz_protocol_decoder_legrand_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderLegrand instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_legrand_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderLegrand instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_legrand_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderLegrand. + * @param context Pointer to a SubGhzProtocolDecoderLegrand instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus subghz_protocol_decoder_legrand_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderLegrand. + * @param context Pointer to a SubGhzProtocolDecoderLegrand instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_decoder_legrand_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderLegrand instance + * @param output Resulting text + */ +void subghz_protocol_decoder_legrand_get_string(void* context, FuriString* output); + +#ifdef __cplusplus +} +#endif diff --git a/lib/subghz/protocols/magellan.c b/lib/subghz/protocols/magellan.c index 1b8eccc36..0706eec59 100644 --- a/lib/subghz/protocols/magellan.c +++ b/lib/subghz/protocols/magellan.c @@ -168,6 +168,7 @@ SubGhzProtocolStatus flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); if(!subghz_protocol_encoder_magellan_get_upload(instance)) { + instance->encoder.front = 0; // reset before start ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; } @@ -180,6 +181,7 @@ SubGhzProtocolStatus void subghz_protocol_encoder_magellan_stop(void* context) { SubGhzProtocolEncoderMagellan* instance = context; instance->encoder.is_running = false; + instance->encoder.front = 0; // reset position } LevelDuration subghz_protocol_encoder_magellan_yield(void* context) { @@ -359,15 +361,38 @@ static void subghz_protocol_magellan_check_remote_controller(SubGhzBlockGeneric* * * 0x1275EC => 0x12-event codes, 0x75EC-serial (dec 117236) * -* event codes -* bit_0: 1-Open/Motion, 0-close/ok -* bit_1: 1-Tamper On (alarm), 0-Tamper Off (ok) -* bit_2: ? -* bit_3: 1-power on -* bit_4: model type - wireless reed -* bit_5: model type - motion sensor -* bit_6: ? -* bit_7: ? +* Event codes consist of two parts: +* - The upper nibble (bits 7-4) represents the event type: +* - 0x00: Nothing +* - 0x01: Door +* - 0x02: Motion +* - 0x03: Smoke Alarm +* - 0x04: REM1 +* - 0x05: REM1 with subtype Off1 +* - 0x06: REM2 +* - 0x07: REM2 with subtype Off1 +* - Others: Unknown +* - The lower nibble (bits 3-0) represents the event subtype, which varies based on the model type: +* - If the model type is greater than 0x03 (e.g., REM1 or REM2): +* - 0x00: Arm1 +* - 0x01: Btn1 +* - 0x02: Btn2 +* - 0x03: Btn3 +* - 0x08: Reset +* - 0x09: LowBatt +* - 0x0A: BattOk +* - 0x0B: Learn +* - Others: Unknown +* - Otherwise: +* - 0x00: Sealed +* - 0x01: Alarm +* - 0x02: Tamper +* - 0x03: Alarm + Tamper +* - 0x08: Reset +* - 0x09: LowBatt +* - 0x0A: BattOk +* - 0x0B: Learn +* - Others: Unknown * */ uint64_t data_rev = subghz_protocol_blocks_reverse_key(instance->data >> 8, 24); @@ -376,18 +401,71 @@ static void subghz_protocol_magellan_check_remote_controller(SubGhzBlockGeneric* } static void subghz_protocol_magellan_get_event_serialize(uint8_t event, FuriString* output) { - furi_string_cat_printf( - output, - "%s%s%s%s%s%s%s%s", - ((event >> 4) & 0x1 ? (event & 0x1 ? " Open" : " Close") : - (event & 0x1 ? " Motion" : " Ok")), - ((event >> 1) & 0x1 ? ", Tamper On\n(Alarm)" : ""), - ((event >> 2) & 0x1 ? ", ?" : ""), - ((event >> 3) & 0x1 ? ", Power On" : ""), - ((event >> 4) & 0x1 ? ", MT:Wireless_Reed" : ""), - ((event >> 5) & 0x1 ? ", MT:Motion_Sensor" : ""), - ((event >> 6) & 0x1 ? ", ?" : ""), - ((event >> 7) & 0x1 ? ", ?" : "")); + const char* event_type; + const char* event_subtype; + + switch((event >> 4) & 0x0F) { + case 0x00: + event_type = "Nothing"; + break; + case 0x01: + event_type = "Door"; + break; + case 0x02: + event_type = "Motion"; + break; + case 0x03: + event_type = "Smoke Alarm"; + break; + case 0x04: + event_type = "REM1"; + break; + case 0x05: + event_type = "REM1"; + event_subtype = "Off1"; + furi_string_cat_printf(output, "%s - %s", event_type, event_subtype); + return; + case 0x06: + event_type = "REM2"; + event_subtype = "Off1"; + furi_string_cat_printf(output, "%s - %s", event_type, event_subtype); + return; + default: + event_type = "Unknown"; + break; + } + + switch(event & 0x0F) { + case 0x00: + event_subtype = (((event >> 4) & 0x0F) > 0x03) ? "Arm1" : "Sealed"; + break; + case 0x01: + event_subtype = (((event >> 4) & 0x0F) > 0x03) ? "Btn1" : "Alarm"; + break; + case 0x02: + event_subtype = (((event >> 4) & 0x0F) > 0x03) ? "Btn2" : "Tamper"; + break; + case 0x03: + event_subtype = (((event >> 4) & 0x0F) > 0x03) ? "Btn3" : "Alarm + Tamper"; + break; + case 0x08: + event_subtype = "Reset"; + break; + case 0x09: + event_subtype = "LowBatt"; + break; + case 0x0A: + event_subtype = "BattOk"; + break; + case 0x0B: + event_subtype = "Learn"; + break; + default: + event_subtype = "Unknown"; + break; + } + + furi_string_cat_printf(output, "%s - %s", event_type, event_subtype); } uint8_t subghz_protocol_decoder_magellan_get_hash_data(void* context) { diff --git a/lib/subghz/protocols/marantec.c b/lib/subghz/protocols/marantec.c index fc4aa0dca..658aac610 100644 --- a/lib/subghz/protocols/marantec.c +++ b/lib/subghz/protocols/marantec.c @@ -165,7 +165,7 @@ static void subghz_protocol_encoder_marantec_get_upload(SubGhzProtocolEncoderMar } uint8_t subghz_protocol_marantec_crc8(uint8_t* data, size_t len) { - uint8_t crc = 0x08; + uint8_t crc = 0x01; size_t i, j; for(i = 0; i < len; i++) { crc ^= data[i]; @@ -184,6 +184,18 @@ uint8_t subghz_protocol_marantec_crc8(uint8_t* data, size_t len) { * @param instance Pointer to a SubGhzBlockGeneric* instance */ static void subghz_protocol_marantec_remote_controller(SubGhzBlockGeneric* instance) { + // Key samples + // 1307EDF6486C5 = 000 100110000 01111110110111110110 0100 10000110 11000101 + // 1303EFAFD8683 = 000 100110000 00111110111110101111 1101 10000110 10000011 + + // From unittests + // 1300710DF869F + + // const serial button serial crc + // 130 7EDF6 4 86 C5 + // 130 3EFAF D 86 83 + // 130 0710D F 86 9F + instance->btn = (instance->data >> 16) & 0xF; instance->serial = ((instance->data >> 12) & 0xFFFFFF00) | ((instance->data >> 8) & 0xFF); } @@ -207,6 +219,7 @@ SubGhzProtocolStatus subghz_protocol_marantec_remote_controller(&instance->generic); subghz_protocol_encoder_marantec_get_upload(instance); + instance->encoder.front = 0; instance->encoder.is_running = true; } while(false); @@ -216,6 +229,7 @@ SubGhzProtocolStatus void subghz_protocol_encoder_marantec_stop(void* context) { SubGhzProtocolEncoderMarantec* instance = context; instance->encoder.is_running = false; + instance->encoder.front = 0; } LevelDuration subghz_protocol_encoder_marantec_yield(void* context) { @@ -253,6 +267,7 @@ void subghz_protocol_decoder_marantec_free(void* context) { void subghz_protocol_decoder_marantec_reset(void* context) { furi_assert(context); SubGhzProtocolDecoderMarantec* instance = context; + instance->decoder.parser_step = MarantecDecoderStepReset; manchester_advance( instance->manchester_saved_state, ManchesterEventReset, @@ -367,16 +382,30 @@ void subghz_protocol_decoder_marantec_get_string(void* context, FuriString* outp SubGhzProtocolDecoderMarantec* instance = context; subghz_protocol_marantec_remote_controller(&instance->generic); + uint8_t tdata[6] = { + instance->generic.data >> 48, + instance->generic.data >> 40, + instance->generic.data >> 32, + instance->generic.data >> 24, + instance->generic.data >> 16, + instance->generic.data >> 8}; + + uint8_t crc = subghz_protocol_marantec_crc8(tdata, sizeof(tdata)); + bool crc_ok = (crc == (instance->generic.data & 0xFF)); + furi_string_cat_printf( output, "%s %db\r\n" - "Key:0x%lX%08lX\r\n" - "Sn:0x%07lX \r\n" - "Btn:%X\r\n", + "Key: 0x%lX%08lX\r\n" + "Sn: 0x%07lX \r\n" + "CRC: 0x%02X - %s\r\n" + "Btn: %X\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)(instance->generic.data >> 32), (uint32_t)(instance->generic.data & 0xFFFFFFFF), instance->generic.serial, + crc, + crc_ok ? "Valid" : "Invalid", instance->generic.btn); } diff --git a/lib/subghz/protocols/marantec.h b/lib/subghz/protocols/marantec.h index 485c563b2..9a7b55b7e 100644 --- a/lib/subghz/protocols/marantec.h +++ b/lib/subghz/protocols/marantec.h @@ -107,3 +107,11 @@ SubGhzProtocolStatus * @param output Resulting text */ void subghz_protocol_decoder_marantec_get_string(void* context, FuriString* output); + +/** + * Calculate CRC8 for Marantec protocol. + * @param data Pointer to the data buffer + * @param len Length of the data buffer + * @return CRC8 value + */ +uint8_t subghz_protocol_marantec_crc8(uint8_t* data, size_t len); diff --git a/lib/subghz/protocols/marantec24.c b/lib/subghz/protocols/marantec24.c new file mode 100644 index 000000000..e2ff9218b --- /dev/null +++ b/lib/subghz/protocols/marantec24.c @@ -0,0 +1,352 @@ +#include "marantec24.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolMarantec24" + +static const SubGhzBlockConst subghz_protocol_marantec24_const = { + .te_short = 800, + .te_long = 1600, + .te_delta = 200, + .min_count_bit_for_found = 24, +}; + +struct SubGhzProtocolDecoderMarantec24 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderMarantec24 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + Marantec24DecoderStepReset = 0, + Marantec24DecoderStepSaveDuration, + Marantec24DecoderStepCheckDuration, +} Marantec24DecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_marantec24_decoder = { + .alloc = subghz_protocol_decoder_marantec24_alloc, + .free = subghz_protocol_decoder_marantec24_free, + + .feed = subghz_protocol_decoder_marantec24_feed, + .reset = subghz_protocol_decoder_marantec24_reset, + + .get_hash_data = subghz_protocol_decoder_marantec24_get_hash_data, + .serialize = subghz_protocol_decoder_marantec24_serialize, + .deserialize = subghz_protocol_decoder_marantec24_deserialize, + .get_string = subghz_protocol_decoder_marantec24_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_marantec24_encoder = { + .alloc = subghz_protocol_encoder_marantec24_alloc, + .free = subghz_protocol_encoder_marantec24_free, + + .deserialize = subghz_protocol_encoder_marantec24_deserialize, + .stop = subghz_protocol_encoder_marantec24_stop, + .yield = subghz_protocol_encoder_marantec24_yield, +}; + +const SubGhzProtocol subghz_protocol_marantec24 = { + .name = SUBGHZ_PROTOCOL_MARANTEC24_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_868 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_marantec24_decoder, + .encoder = &subghz_protocol_marantec24_encoder, +}; + +void* subghz_protocol_encoder_marantec24_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderMarantec24* instance = malloc(sizeof(SubGhzProtocolEncoderMarantec24)); + + instance->base.protocol = &subghz_protocol_marantec24; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 512; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_marantec24_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderMarantec24* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderMarantec24 instance + */ +static void + subghz_protocol_encoder_marantec24_get_upload(SubGhzProtocolEncoderMarantec24* instance) { + furi_assert(instance); + size_t index = 0; + // Send initial GAP to trigger decoder + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_marantec24_const.te_long * 9); + for(size_t r = 0; r < 4; r++) { + // Send key and GAP + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + // Send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_marantec24_const.te_short); + if(i == 1) { + //Send gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, + (uint32_t)subghz_protocol_marantec24_const.te_long * 9 + + subghz_protocol_marantec24_const.te_short); + } else { + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_marantec24_const.te_long * 2); + } + } else { + // Send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_marantec24_const.te_long); + if(i == 1) { + //Send gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, + (uint32_t)subghz_protocol_marantec24_const.te_long * 9 + + subghz_protocol_marantec24_const.te_short); // 15200 + } else { + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_marantec24_const.te_short * 3); + } + } + } + } + + instance->encoder.size_upload = index; + return; +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_marantec24_check_remote_controller(SubGhzBlockGeneric* instance) { + instance->serial = instance->data >> 4; + instance->btn = instance->data & 0xF; +} + +SubGhzProtocolStatus + subghz_protocol_encoder_marantec24_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderMarantec24* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_marantec24_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_marantec24_check_remote_controller(&instance->generic); + subghz_protocol_encoder_marantec24_get_upload(instance); + instance->encoder.front = 0; // reset before start + instance->encoder.is_running = true; + } while(false); + + return ret; +} + +void subghz_protocol_encoder_marantec24_stop(void* context) { + SubGhzProtocolEncoderMarantec24* instance = context; + instance->encoder.is_running = false; + instance->encoder.front = 0; // reset position +} + +LevelDuration subghz_protocol_encoder_marantec24_yield(void* context) { + SubGhzProtocolEncoderMarantec24* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_marantec24_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderMarantec24* instance = malloc(sizeof(SubGhzProtocolDecoderMarantec24)); + instance->base.protocol = &subghz_protocol_marantec24; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_marantec24_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderMarantec24* instance = context; + free(instance); +} + +void subghz_protocol_decoder_marantec24_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderMarantec24* instance = context; + instance->decoder.parser_step = Marantec24DecoderStepReset; +} + +void subghz_protocol_decoder_marantec24_feed(void* context, bool level, volatile uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderMarantec24* instance = context; + + // Marantec24 Decoder + // 2024 - @xMasterX (MMX) + + // 2025 update - The protocol is not real marantec, + // it comes from chinese remote that pretends to be replica of original marantec, actually it was a cloner + // which had some thing written on it, which is uknown, but since its pretentding to be marantec, + // it was decided to keep the name of the protocol as marantec24 (24 bits) + + // Key samples + // 101011000000010111001000 = AC05C8 + // 101011000000010111000100 = AC05C4 + // 101011000000010111001100 = AC05CC + // 101011000000010111000000 = AC05C0 + + switch(instance->decoder.parser_step) { + case Marantec24DecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_marantec24_const.te_long * 9) < + subghz_protocol_marantec24_const.te_delta * 6)) { + //Found GAP + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = Marantec24DecoderStepSaveDuration; + } + break; + case Marantec24DecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = Marantec24DecoderStepCheckDuration; + } else { + instance->decoder.parser_step = Marantec24DecoderStepReset; + } + break; + case Marantec24DecoderStepCheckDuration: + if(!level) { + // Bit 0 is long and short x2 timing = 1600us HIGH (te_last) and 2400us LOW + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_marantec24_const.te_long) < + subghz_protocol_marantec24_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_marantec24_const.te_short * 3) < + subghz_protocol_marantec24_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = Marantec24DecoderStepSaveDuration; + // Bit 1 is short and long x2 timing = 800us HIGH (te_last) and 3200us LOW + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_marantec24_const.te_short) < + subghz_protocol_marantec24_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_marantec24_const.te_long * 2) < + subghz_protocol_marantec24_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = Marantec24DecoderStepSaveDuration; + } else if( + // End of the key + DURATION_DIFF(duration, subghz_protocol_marantec24_const.te_long * 9) < + subghz_protocol_marantec24_const.te_delta * 6) { + //Found next GAP and add bit 0 or 1 (only bit 0 was found on the remotes) + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_marantec24_const.te_long) < + subghz_protocol_marantec24_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_marantec24_const.te_short) < + subghz_protocol_marantec24_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } + // If got 24 bits key reading is finished + if(instance->decoder.decode_count_bit == + subghz_protocol_marantec24_const.min_count_bit_for_found) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = Marantec24DecoderStepReset; + } else { + instance->decoder.parser_step = Marantec24DecoderStepReset; + } + } else { + instance->decoder.parser_step = Marantec24DecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_marantec24_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderMarantec24* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus subghz_protocol_decoder_marantec24_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderMarantec24* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + subghz_protocol_decoder_marantec24_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderMarantec24* instance = context; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_marantec24_const.min_count_bit_for_found); +} + +void subghz_protocol_decoder_marantec24_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderMarantec24* instance = context; + + subghz_protocol_marantec24_check_remote_controller(&instance->generic); + + furi_string_cat_printf( + output, + "%s %db\r\n" + "Key: 0x%06lX\r\n" + "Serial: 0x%05lX\r\n" + "Btn: %01X", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0xFFFFFF), + instance->generic.serial, + instance->generic.btn); +} diff --git a/lib/subghz/protocols/marantec24.h b/lib/subghz/protocols/marantec24.h new file mode 100644 index 000000000..862eb7e2a --- /dev/null +++ b/lib/subghz/protocols/marantec24.h @@ -0,0 +1,109 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_MARANTEC24_NAME "Marantec24" + +typedef struct SubGhzProtocolDecoderMarantec24 SubGhzProtocolDecoderMarantec24; +typedef struct SubGhzProtocolEncoderMarantec24 SubGhzProtocolEncoderMarantec24; + +extern const SubGhzProtocolDecoder subghz_protocol_marantec24_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_marantec24_encoder; +extern const SubGhzProtocol subghz_protocol_marantec24; + +/** + * Allocate SubGhzProtocolEncoderMarantec24. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderMarantec24* pointer to a SubGhzProtocolEncoderMarantec24 instance + */ +void* subghz_protocol_encoder_marantec24_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderMarantec24. + * @param context Pointer to a SubGhzProtocolEncoderMarantec24 instance + */ +void subghz_protocol_encoder_marantec24_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderMarantec24 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_encoder_marantec24_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderMarantec24 instance + */ +void subghz_protocol_encoder_marantec24_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderMarantec24 instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_marantec24_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderMarantec24. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderMarantec24* pointer to a SubGhzProtocolDecoderMarantec24 instance + */ +void* subghz_protocol_decoder_marantec24_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderMarantec24. + * @param context Pointer to a SubGhzProtocolDecoderMarantec24 instance + */ +void subghz_protocol_decoder_marantec24_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderMarantec24. + * @param context Pointer to a SubGhzProtocolDecoderMarantec24 instance + */ +void subghz_protocol_decoder_marantec24_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderMarantec24 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_marantec24_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderMarantec24 instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_marantec24_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderMarantec24. + * @param context Pointer to a SubGhzProtocolDecoderMarantec24 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus subghz_protocol_decoder_marantec24_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderMarantec24. + * @param context Pointer to a SubGhzProtocolDecoderMarantec24 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_decoder_marantec24_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderMarantec24 instance + * @param output Resulting text + */ +void subghz_protocol_decoder_marantec24_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/phoenix_v2.c b/lib/subghz/protocols/phoenix_v2.c index 2fabed73d..5665fd296 100644 --- a/lib/subghz/protocols/phoenix_v2.c +++ b/lib/subghz/protocols/phoenix_v2.c @@ -7,7 +7,6 @@ #include "../blocks/math.h" #define TAG "SubGhzProtocolPhoenixV2" - //transmission only static mode static const SubGhzBlockConst subghz_protocol_phoenix_v2_const = { @@ -91,6 +90,9 @@ void subghz_protocol_encoder_phoenix_v2_free(void* context) { free(instance); } +// Pre define functions +static void subghz_protocol_phoenix_v2_check_remote_controller(SubGhzBlockGeneric* instance); + /** * Generating an upload from data. * @param instance Pointer to a SubGhzProtocolEncoderPhoenix_V2 instance @@ -107,6 +109,7 @@ static bool } else { instance->encoder.size_upload = size_upload; } + //Send header instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)subghz_protocol_phoenix_v2_const.te_short * 60); @@ -153,6 +156,7 @@ SubGhzProtocolStatus ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; } + instance->encoder.is_running = true; } while(false); @@ -274,16 +278,66 @@ void subghz_protocol_decoder_phoenix_v2_feed(void* context, bool level, uint32_t } } +static uint16_t subghz_protocol_phoenix_v2_decrypt_counter(uint64_t full_key) { + uint16_t encrypted_value = (uint16_t)((full_key >> 40) & 0xFFFF); + + uint8_t byte1 = (uint8_t)(encrypted_value >> 8); // First encrypted counter byte + uint8_t byte2 = (uint8_t)(encrypted_value & 0xFF); // Second encrypted counter byte + + uint8_t xor_key1 = (uint8_t)(full_key >> 24); // First byte of serial + uint8_t xor_key2 = (uint8_t)((full_key >> 16) & 0xFF); // Second byte of serial + + for(int i = 0; i < 16; i++) { + // Store the most significant bit (MSB) of byte1. + // The check `(msb_of_byte1 == 0)` will determine if we apply the XOR keys. + uint8_t msb_of_byte1 = byte1 & 0x80; + + // Store the least significant bit (LSB) of byte2. + uint8_t lsb_of_byte2 = byte2 & 1; + + // Perform a bit shuffle between the two bytes + byte2 = (byte2 >> 1) | msb_of_byte1; + byte1 = (byte1 << 1) | lsb_of_byte2; + + // Conditionally apply the XOR keys based on the original MSB of byte1. + if(msb_of_byte1 == 0) { + byte1 ^= xor_key1; + // The mask `& 0x7F` clears the MSB of byte2 after the XOR. + byte2 = (byte2 ^ xor_key2) & 0x7F; + } + } + + return (uint16_t)byte2 << 8 | byte1; +} + /** * Analysis of received data * @param instance Pointer to a SubGhzBlockGeneric* instance */ static void subghz_protocol_phoenix_v2_check_remote_controller(SubGhzBlockGeneric* instance) { + // 2022.08 - @Skorpionm + // 2025.07 - @xMasterX & @RocketGod-git + // Fully supported now, with button switch and add manually + // + // Key samples + // Full key example: 0xC63E01B9615720 - after subghz_protocol_blocks_reverse_key was applied + // Serial - B9615720 + // Button - 01 + // Encrypted -> Decrypted counters + // C63E - 025C + // BCC1 - 025D + // 3341 - 025E + // 49BE - 025F + // 99D3 - 0260 + // E32C - 0261 + uint64_t data_rev = subghz_protocol_blocks_reverse_key(instance->data, instance->data_count_bit + 4); + instance->serial = data_rev & 0xFFFFFFFF; - instance->cnt = (data_rev >> 40) & 0xFFFF; + instance->cnt = subghz_protocol_phoenix_v2_decrypt_counter(data_rev); instance->btn = (data_rev >> 32) & 0xF; + // encrypted cnt is (data_rev >> 40) & 0xFFFF } uint8_t subghz_protocol_decoder_phoenix_v2_get_hash_data(void* context) { @@ -318,14 +372,15 @@ void subghz_protocol_decoder_phoenix_v2_get_string(void* context, FuriString* ou subghz_protocol_phoenix_v2_check_remote_controller(&instance->generic); furi_string_cat_printf( output, - "%s %dbit\r\n" - "Key:%02lX%08lX\r\n" + "V2 Phoenix %dbit\r\n" + "Key:%05lX%08lX\r\n" "Sn:0x%07lX \r\n" - "Btn:%X\r\n", - instance->generic.protocol_name, + "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), instance->generic.serial, + instance->generic.cnt, instance->generic.btn); } diff --git a/lib/subghz/protocols/power_smart.c b/lib/subghz/protocols/power_smart.c index 828c4a76e..1359f416d 100644 --- a/lib/subghz/protocols/power_smart.c +++ b/lib/subghz/protocols/power_smart.c @@ -212,6 +212,7 @@ SubGhzProtocolStatus subghz_protocol_power_smart_remote_controller(&instance->generic); subghz_protocol_encoder_power_smart_get_upload(instance); + instance->encoder.front = 0; // reset before start instance->encoder.is_running = true; } while(false); @@ -221,6 +222,7 @@ SubGhzProtocolStatus void subghz_protocol_encoder_power_smart_stop(void* context) { SubGhzProtocolEncoderPowerSmart* instance = context; instance->encoder.is_running = false; + instance->encoder.front = 0; // reset position } LevelDuration subghz_protocol_encoder_power_smart_yield(void* context) { diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index d7a14cd1c..91ceaec32 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -44,7 +44,15 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = { &subghz_protocol_kinggates_stylo_4k, &subghz_protocol_bin_raw, &subghz_protocol_mastercode, + &subghz_protocol_legrand, &subghz_protocol_dickert_mahs, + &subghz_protocol_gangqi, + &subghz_protocol_marantec24, + &subghz_protocol_hollarm, + &subghz_protocol_hay21, + &subghz_protocol_revers_rb2, + &subghz_protocol_feron, + &subghz_protocol_roger, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index ae531c3f9..1cde46ef5 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -45,4 +45,12 @@ #include "kinggates_stylo_4k.h" #include "bin_raw.h" #include "mastercode.h" +#include "legrand.h" #include "dickert_mahs.h" +#include "gangqi.h" +#include "marantec24.h" +#include "hollarm.h" +#include "hay21.h" +#include "revers_rb2.h" +#include "feron.h" +#include "roger.h" diff --git a/lib/subghz/protocols/public_api.h b/lib/subghz/protocols/public_api.h index 174f175cd..a5a980b3b 100644 --- a/lib/subghz/protocols/public_api.h +++ b/lib/subghz/protocols/public_api.h @@ -31,7 +31,7 @@ bool subghz_protocol_secplus_v2_create_data( * @param flipper_format Pointer to a FlipperFormat instance * @param serial Serial number, 28 bit * @param btn Button number, 4 bit - * @param cnt Container value, 16 bit + * @param cnt Counter value, 16 bit * @param manufacture_name Name of manufacturer's key * @param preset Modulation, SubGhzRadioPreset * @return true On success diff --git a/lib/subghz/protocols/raw.c b/lib/subghz/protocols/raw.c index d7b31be37..96908130e 100644 --- a/lib/subghz/protocols/raw.c +++ b/lib/subghz/protocols/raw.c @@ -245,8 +245,8 @@ void subghz_protocol_decoder_raw_reset(void* context) { void subghz_protocol_decoder_raw_feed(void* context, bool level, uint32_t duration) { furi_check(context); SubGhzProtocolDecoderRAW* instance = context; - - if(!instance->pause && (instance->upload_raw != NULL)) { + // Add check if we got duration higher than 1 second, we skipping it, temp fix + if((!instance->pause && (instance->upload_raw != NULL)) && (duration < ((uint32_t)1000000))) { if(duration > subghz_protocol_raw_const.te_short) { if(instance->last_level != level) { instance->last_level = (level ? true : false); @@ -273,7 +273,7 @@ void subghz_protocol_decoder_raw_get_string(void* context, FuriString* output) { furi_check(context); //SubGhzProtocolDecoderRAW* instance = context; UNUSED(context); - furi_string_cat_printf(output, "RAW Date"); + furi_string_cat_printf(output, "RAW Data"); } void* subghz_protocol_encoder_raw_alloc(SubGhzEnvironment* environment) { diff --git a/lib/subghz/protocols/revers_rb2.c b/lib/subghz/protocols/revers_rb2.c new file mode 100644 index 000000000..156a506be --- /dev/null +++ b/lib/subghz/protocols/revers_rb2.c @@ -0,0 +1,415 @@ +#include "revers_rb2.h" +#include +#include +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolRevers_RB2" + +static const SubGhzBlockConst subghz_protocol_revers_rb2_const = { + .te_short = 250, + .te_long = 500, + .te_delta = 160, + .min_count_bit_for_found = 64, +}; + +struct SubGhzProtocolDecoderRevers_RB2 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + ManchesterState manchester_saved_state; + uint16_t header_count; +}; + +struct SubGhzProtocolEncoderRevers_RB2 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + Revers_RB2DecoderStepReset = 0, + Revers_RB2DecoderStepHeader, + Revers_RB2DecoderStepDecoderData, +} Revers_RB2DecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_revers_rb2_decoder = { + .alloc = subghz_protocol_decoder_revers_rb2_alloc, + .free = subghz_protocol_decoder_revers_rb2_free, + + .feed = subghz_protocol_decoder_revers_rb2_feed, + .reset = subghz_protocol_decoder_revers_rb2_reset, + + .get_hash_data = subghz_protocol_decoder_revers_rb2_get_hash_data, + .serialize = subghz_protocol_decoder_revers_rb2_serialize, + .deserialize = subghz_protocol_decoder_revers_rb2_deserialize, + .get_string = subghz_protocol_decoder_revers_rb2_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_revers_rb2_encoder = { + .alloc = subghz_protocol_encoder_revers_rb2_alloc, + .free = subghz_protocol_encoder_revers_rb2_free, + + .deserialize = subghz_protocol_encoder_revers_rb2_deserialize, + .stop = subghz_protocol_encoder_revers_rb2_stop, + .yield = subghz_protocol_encoder_revers_rb2_yield, +}; + +const SubGhzProtocol subghz_protocol_revers_rb2 = { + .name = SUBGHZ_PROTOCOL_REVERSRB2_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_revers_rb2_decoder, + .encoder = &subghz_protocol_revers_rb2_encoder, +}; + +void* subghz_protocol_encoder_revers_rb2_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderRevers_RB2* instance = malloc(sizeof(SubGhzProtocolEncoderRevers_RB2)); + + instance->base.protocol = &subghz_protocol_revers_rb2; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 1768; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_revers_rb2_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderRevers_RB2* instance = context; + free(instance->encoder.upload); + free(instance); +} + +static LevelDuration + subghz_protocol_encoder_revers_rb2_add_duration_to_upload(ManchesterEncoderResult result) { + LevelDuration data = {.duration = 0, .level = 0}; + switch(result) { + case ManchesterEncoderResultShortLow: + data.duration = subghz_protocol_revers_rb2_const.te_short; + data.level = false; + break; + case ManchesterEncoderResultLongLow: + data.duration = subghz_protocol_revers_rb2_const.te_long; + data.level = false; + break; + case ManchesterEncoderResultLongHigh: + data.duration = subghz_protocol_revers_rb2_const.te_long; + data.level = true; + break; + case ManchesterEncoderResultShortHigh: + data.duration = subghz_protocol_revers_rb2_const.te_short; + data.level = true; + break; + + default: + furi_crash("SubGhz: ManchesterEncoderResult is incorrect."); + break; + } + return level_duration_make(data.level, data.duration); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderRevers_RB2 instance + */ +static void + subghz_protocol_encoder_revers_rb2_get_upload(SubGhzProtocolEncoderRevers_RB2* instance) { + furi_assert(instance); + size_t index = 0; + + for(size_t r = 0; r < 6; r++) { + ManchesterEncoderState enc_state; + manchester_encoder_reset(&enc_state); + ManchesterEncoderResult result; + + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(!manchester_encoder_advance( + &enc_state, bit_read(instance->generic.data, i - 1), &result)) { + instance->encoder.upload[index++] = + subghz_protocol_encoder_revers_rb2_add_duration_to_upload(result); + manchester_encoder_advance( + &enc_state, bit_read(instance->generic.data, i - 1), &result); + } + instance->encoder.upload[index++] = + subghz_protocol_encoder_revers_rb2_add_duration_to_upload(result); + } + instance->encoder.upload[index] = + subghz_protocol_encoder_revers_rb2_add_duration_to_upload( + manchester_encoder_finish(&enc_state)); + if(level_duration_get_level(instance->encoder.upload[index])) { + index++; + } + instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)320); + } + instance->encoder.size_upload = index; +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_revers_rb2_remote_controller(SubGhzBlockGeneric* instance) { + // Revers RB2 / RB2M Decoder + // 02.2025 - @xMasterX (MMX) + instance->serial = (((instance->data << 16) >> 16) >> 10); +} + +SubGhzProtocolStatus + subghz_protocol_encoder_revers_rb2_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderRevers_RB2* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_revers_rb2_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_revers_rb2_remote_controller(&instance->generic); + subghz_protocol_encoder_revers_rb2_get_upload(instance); + instance->encoder.front = 0; + instance->encoder.is_running = true; + } while(false); + + return ret; +} + +void subghz_protocol_encoder_revers_rb2_stop(void* context) { + SubGhzProtocolEncoderRevers_RB2* instance = context; + instance->encoder.is_running = false; + instance->encoder.front = 0; +} + +LevelDuration subghz_protocol_encoder_revers_rb2_yield(void* context) { + SubGhzProtocolEncoderRevers_RB2* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_revers_rb2_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderRevers_RB2* instance = malloc(sizeof(SubGhzProtocolDecoderRevers_RB2)); + instance->base.protocol = &subghz_protocol_revers_rb2; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_revers_rb2_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderRevers_RB2* instance = context; + free(instance); +} + +void subghz_protocol_decoder_revers_rb2_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderRevers_RB2* instance = context; + instance->decoder.parser_step = Revers_RB2DecoderStepReset; + instance->header_count = 0; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); +} + +void subghz_protocol_decoder_revers_rb2_addbit(void* context, bool data) { + SubGhzProtocolDecoderRevers_RB2* instance = context; + instance->decoder.decode_data = (instance->decoder.decode_data << 1) | data; + instance->decoder.decode_count_bit++; + + if(instance->decoder.decode_count_bit >= 65) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + return; + } + + if(instance->decoder.decode_count_bit < + subghz_protocol_revers_rb2_const.min_count_bit_for_found) { + return; + } + + // Revers RB2 / RB2M Decoder + // 02.2025 - @xMasterX (MMX) + + uint16_t preamble = (instance->decoder.decode_data >> 48) & 0xFF; + uint16_t stop_code = (instance->decoder.decode_data & 0x3FF); + + if(preamble == 0xFF && stop_code == 0x200) { + //Found header and stop code + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); + } +} + +void subghz_protocol_decoder_revers_rb2_feed(void* context, bool level, volatile uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderRevers_RB2* instance = context; + ManchesterEvent event = ManchesterEventReset; + + switch(instance->decoder.parser_step) { + case Revers_RB2DecoderStepReset: + if((!level) && + (DURATION_DIFF(duration, 600) < subghz_protocol_revers_rb2_const.te_delta)) { + instance->decoder.parser_step = Revers_RB2DecoderStepHeader; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); + } + break; + case Revers_RB2DecoderStepHeader: + if(!level) { + if(DURATION_DIFF(duration, subghz_protocol_revers_rb2_const.te_short) < + subghz_protocol_revers_rb2_const.te_delta) { + if(instance->decoder.te_last == 1) { + instance->header_count++; + } + instance->decoder.te_last = level; + } else { + instance->header_count = 0; + instance->decoder.te_last = 0; + instance->decoder.parser_step = Revers_RB2DecoderStepReset; + } + } else { + if(DURATION_DIFF(duration, subghz_protocol_revers_rb2_const.te_short) < + subghz_protocol_revers_rb2_const.te_delta) { + if(instance->decoder.te_last == 0) { + instance->header_count++; + } + instance->decoder.te_last = level; + } else { + instance->header_count = 0; + instance->decoder.te_last = 0; + instance->decoder.parser_step = Revers_RB2DecoderStepReset; + } + } + + if(instance->header_count == 4) { + instance->header_count = 0; + instance->decoder.decode_data = 0xF; + instance->decoder.decode_count_bit = 4; + instance->decoder.parser_step = Revers_RB2DecoderStepDecoderData; + } + break; + case Revers_RB2DecoderStepDecoderData: + if(!level) { + if(DURATION_DIFF(duration, subghz_protocol_revers_rb2_const.te_short) < + subghz_protocol_revers_rb2_const.te_delta) { + event = ManchesterEventShortLow; + } else if( + DURATION_DIFF(duration, subghz_protocol_revers_rb2_const.te_long) < + subghz_protocol_revers_rb2_const.te_delta) { + event = ManchesterEventLongLow; + } else { + instance->decoder.parser_step = Revers_RB2DecoderStepReset; + } + } else { + if(DURATION_DIFF(duration, subghz_protocol_revers_rb2_const.te_short) < + subghz_protocol_revers_rb2_const.te_delta) { + event = ManchesterEventShortHigh; + } else if( + DURATION_DIFF(duration, subghz_protocol_revers_rb2_const.te_long) < + subghz_protocol_revers_rb2_const.te_delta) { + event = ManchesterEventLongHigh; + } else { + instance->decoder.parser_step = Revers_RB2DecoderStepReset; + } + } + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + instance->manchester_saved_state, event, &instance->manchester_saved_state, &data); + + if(data_ok) { + subghz_protocol_decoder_revers_rb2_addbit(instance, data); + } + } + break; + } +} + +uint8_t subghz_protocol_decoder_revers_rb2_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderRevers_RB2* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus subghz_protocol_decoder_revers_rb2_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderRevers_RB2* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + subghz_protocol_decoder_revers_rb2_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderRevers_RB2* instance = context; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_revers_rb2_const.min_count_bit_for_found); +} + +void subghz_protocol_decoder_revers_rb2_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderRevers_RB2* instance = context; + subghz_protocol_revers_rb2_remote_controller(&instance->generic); + + furi_string_cat_printf( + output, + "%s %db\r\n" + "Key:%lX%08lX\r\n" + "Sn:0x%08lX \r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data & 0xFFFFFFFF), + instance->generic.serial); +} diff --git a/lib/subghz/protocols/revers_rb2.h b/lib/subghz/protocols/revers_rb2.h new file mode 100644 index 000000000..7ccd2a9a2 --- /dev/null +++ b/lib/subghz/protocols/revers_rb2.h @@ -0,0 +1,109 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_REVERSRB2_NAME "Revers_RB2" + +typedef struct SubGhzProtocolDecoderRevers_RB2 SubGhzProtocolDecoderRevers_RB2; +typedef struct SubGhzProtocolEncoderRevers_RB2 SubGhzProtocolEncoderRevers_RB2; + +extern const SubGhzProtocolDecoder subghz_protocol_revers_rb2_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_revers_rb2_encoder; +extern const SubGhzProtocol subghz_protocol_revers_rb2; + +/** + * Allocate SubGhzProtocolEncoderRevers_RB2. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderRevers_RB2* pointer to a SubGhzProtocolEncoderRevers_RB2 instance + */ +void* subghz_protocol_encoder_revers_rb2_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderRevers_RB2. + * @param context Pointer to a SubGhzProtocolEncoderRevers_RB2 instance + */ +void subghz_protocol_encoder_revers_rb2_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderRevers_RB2 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_encoder_revers_rb2_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderRevers_RB2 instance + */ +void subghz_protocol_encoder_revers_rb2_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderRevers_RB2 instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_revers_rb2_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderRevers_RB2. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderRevers_RB2* pointer to a SubGhzProtocolDecoderRevers_RB2 instance + */ +void* subghz_protocol_decoder_revers_rb2_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderRevers_RB2. + * @param context Pointer to a SubGhzProtocolDecoderRevers_RB2 instance + */ +void subghz_protocol_decoder_revers_rb2_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderRevers_RB2. + * @param context Pointer to a SubGhzProtocolDecoderRevers_RB2 instance + */ +void subghz_protocol_decoder_revers_rb2_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderRevers_RB2 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_revers_rb2_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderRevers_RB2 instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_revers_rb2_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderRevers_RB2. + * @param context Pointer to a SubGhzProtocolDecoderRevers_RB2 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus subghz_protocol_decoder_revers_rb2_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderRevers_RB2. + * @param context Pointer to a SubGhzProtocolDecoderRevers_RB2 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_decoder_revers_rb2_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderRevers_RB2 instance + * @param output Resulting text + */ +void subghz_protocol_decoder_revers_rb2_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/roger.c b/lib/subghz/protocols/roger.c new file mode 100644 index 000000000..f93b3366e --- /dev/null +++ b/lib/subghz/protocols/roger.c @@ -0,0 +1,341 @@ +#include "roger.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolRoger" + +static const SubGhzBlockConst subghz_protocol_roger_const = { + .te_short = 500, + .te_long = 1000, + .te_delta = 270, + .min_count_bit_for_found = 28, +}; + +struct SubGhzProtocolDecoderRoger { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderRoger { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + RogerDecoderStepReset = 0, + RogerDecoderStepSaveDuration, + RogerDecoderStepCheckDuration, +} RogerDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_roger_decoder = { + .alloc = subghz_protocol_decoder_roger_alloc, + .free = subghz_protocol_decoder_roger_free, + + .feed = subghz_protocol_decoder_roger_feed, + .reset = subghz_protocol_decoder_roger_reset, + + .get_hash_data = subghz_protocol_decoder_roger_get_hash_data, + .serialize = subghz_protocol_decoder_roger_serialize, + .deserialize = subghz_protocol_decoder_roger_deserialize, + .get_string = subghz_protocol_decoder_roger_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_roger_encoder = { + .alloc = subghz_protocol_encoder_roger_alloc, + .free = subghz_protocol_encoder_roger_free, + + .deserialize = subghz_protocol_encoder_roger_deserialize, + .stop = subghz_protocol_encoder_roger_stop, + .yield = subghz_protocol_encoder_roger_yield, +}; + +const SubGhzProtocol subghz_protocol_roger = { + .name = SUBGHZ_PROTOCOL_ROGER_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_AM | + SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | + SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_roger_decoder, + .encoder = &subghz_protocol_roger_encoder, +}; + +void* subghz_protocol_encoder_roger_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderRoger* instance = malloc(sizeof(SubGhzProtocolEncoderRoger)); + + instance->base.protocol = &subghz_protocol_roger; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 256; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_roger_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderRoger* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderRoger instance + */ +static void subghz_protocol_encoder_roger_get_upload(SubGhzProtocolEncoderRoger* instance) { + furi_assert(instance); + size_t index = 0; + + // Send key and GAP + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + // Send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_roger_const.te_long); + if(i == 1) { + //Send gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_roger_const.te_short * 19); + } else { + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_roger_const.te_short); + } + } else { + // Send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_roger_const.te_short); + if(i == 1) { + //Send gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_roger_const.te_short * 19); + } else { + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_roger_const.te_long); + } + } + } + + instance->encoder.size_upload = index; + return; +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_roger_check_remote_controller(SubGhzBlockGeneric* instance) { + // Roger Decoder + // 2025.07 - @xMasterX (MMX) + + // Key samples + // 0010001111111001 0001 00100000 // S/N: 0x23F9 Btn: 0x1 End: 0x20 + // 0010001111111001 0010 00100011 // S/N: 0x23F9 Btn: 0x2 End: 0x23 + // 0101011001010110 0001 00000001 // S/N: 0x5656 Btn: 0x1 End: 0x01 + // 0101011001010110 0010 00000010 // S/N: 0x5656 Btn: 0x2 End: 0x02 + // 0000110111111110 0001 00000001 // S/N: 0x0DFE Btn: 0x1 End: 0x01 + // 0000110111111110 0100 00000100 // S/N: 0x0DFE Btn: 0x4 End: 0x04 + // 0000110111111110 0010 00000010 // S/N: 0x0DFE Btn: 0x2 End: 0x02 + // 0000110111111110 1000 00001000 // S/N: 0x0DFE Btn: 0x8 End: 0x08 + + instance->serial = instance->data >> 12; + instance->btn = (instance->data >> 8) & 0xF; +} + +SubGhzProtocolStatus + subghz_protocol_encoder_roger_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderRoger* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_roger_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_roger_check_remote_controller(&instance->generic); + subghz_protocol_encoder_roger_get_upload(instance); + instance->encoder.front = 0; + + instance->encoder.is_running = true; + } while(false); + + return ret; +} + +void subghz_protocol_encoder_roger_stop(void* context) { + SubGhzProtocolEncoderRoger* instance = context; + instance->encoder.is_running = false; + instance->encoder.front = 0; +} + +LevelDuration subghz_protocol_encoder_roger_yield(void* context) { + SubGhzProtocolEncoderRoger* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_roger_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderRoger* instance = malloc(sizeof(SubGhzProtocolDecoderRoger)); + instance->base.protocol = &subghz_protocol_roger; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_roger_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderRoger* instance = context; + free(instance); +} + +void subghz_protocol_decoder_roger_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderRoger* instance = context; + instance->decoder.parser_step = RogerDecoderStepReset; +} + +void subghz_protocol_decoder_roger_feed(void* context, bool level, volatile uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderRoger* instance = context; + + switch(instance->decoder.parser_step) { + case RogerDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_roger_const.te_short * 19) < + subghz_protocol_roger_const.te_delta * 5)) { + //Found GAP + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = RogerDecoderStepSaveDuration; + } + break; + case RogerDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = RogerDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = RogerDecoderStepReset; + } + break; + case RogerDecoderStepCheckDuration: + if(!level) { + // Bit 1 is long and short timing = 1000us HIGH (te_last) and 500us LOW + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_long) < + subghz_protocol_roger_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_roger_const.te_short) < + subghz_protocol_roger_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = RogerDecoderStepSaveDuration; + // Bit 0 is short and long timing = 500us HIGH (te_last) and 1000us LOW + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_short) < + subghz_protocol_roger_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_roger_const.te_long) < + subghz_protocol_roger_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = RogerDecoderStepSaveDuration; + } else if( + // End of the key + DURATION_DIFF(duration, subghz_protocol_roger_const.te_short * 19) < + subghz_protocol_roger_const.te_delta * 5) { + //Found next GAP and add bit 1 or 0 + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_long) < + subghz_protocol_roger_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_short) < + subghz_protocol_roger_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } + // If got full 28 bits key reading is finished + if(instance->decoder.decode_count_bit == + subghz_protocol_roger_const.min_count_bit_for_found) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = RogerDecoderStepReset; + } else { + instance->decoder.parser_step = RogerDecoderStepReset; + } + } else { + instance->decoder.parser_step = RogerDecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_roger_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderRoger* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus subghz_protocol_decoder_roger_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderRoger* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + subghz_protocol_decoder_roger_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderRoger* instance = context; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, subghz_protocol_roger_const.min_count_bit_for_found); +} + +void subghz_protocol_decoder_roger_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderRoger* instance = context; + + subghz_protocol_roger_check_remote_controller(&instance->generic); + + furi_string_cat_printf( + output, + "%s %db\r\n" + "Key: 0x%07lX\r\n" + "Serial: 0x%04lX\r\n" + "End: 0x%02lX\r\n" + "Btn: %01X", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0xFFFFFFF), + instance->generic.serial, + (uint32_t)(instance->generic.data & 0xFF), + instance->generic.btn); +} diff --git a/lib/subghz/protocols/roger.h b/lib/subghz/protocols/roger.h new file mode 100644 index 000000000..c279164f9 --- /dev/null +++ b/lib/subghz/protocols/roger.h @@ -0,0 +1,109 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_ROGER_NAME "Roger" + +typedef struct SubGhzProtocolDecoderRoger SubGhzProtocolDecoderRoger; +typedef struct SubGhzProtocolEncoderRoger SubGhzProtocolEncoderRoger; + +extern const SubGhzProtocolDecoder subghz_protocol_roger_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_roger_encoder; +extern const SubGhzProtocol subghz_protocol_roger; + +/** + * Allocate SubGhzProtocolEncoderRoger. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderRoger* pointer to a SubGhzProtocolEncoderRoger instance + */ +void* subghz_protocol_encoder_roger_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderRoger. + * @param context Pointer to a SubGhzProtocolEncoderRoger instance + */ +void subghz_protocol_encoder_roger_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderRoger instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_encoder_roger_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderRoger instance + */ +void subghz_protocol_encoder_roger_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderRoger instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_roger_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderRoger. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderRoger* pointer to a SubGhzProtocolDecoderRoger instance + */ +void* subghz_protocol_decoder_roger_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderRoger. + * @param context Pointer to a SubGhzProtocolDecoderRoger instance + */ +void subghz_protocol_decoder_roger_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderRoger. + * @param context Pointer to a SubGhzProtocolDecoderRoger instance + */ +void subghz_protocol_decoder_roger_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderRoger instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_roger_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderRoger instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_roger_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderRoger. + * @param context Pointer to a SubGhzProtocolDecoderRoger instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus subghz_protocol_decoder_roger_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderRoger. + * @param context Pointer to a SubGhzProtocolDecoderRoger instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_decoder_roger_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderRoger instance + * @param output Resulting text + */ +void subghz_protocol_decoder_roger_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/scher_khan.c b/lib/subghz/protocols/scher_khan.c index bf8de10c9..f1c41bd1e 100644 --- a/lib/subghz/protocols/scher_khan.c +++ b/lib/subghz/protocols/scher_khan.c @@ -219,18 +219,48 @@ static void subghz_protocol_scher_khan_check_remote_controller( */ switch(instance->data_count_bit) { - // case 35: //MAGIC CODE, Static - // instance->protocol_name = "MAGIC CODE, Static"; - // break; + case 35: //MAGIC CODE, Static + *protocol_name = "MAGIC CODE, Static"; + instance->serial = 0; + instance->btn = 0; + instance->cnt = 0; + break; case 51: //MAGIC CODE, Dynamic *protocol_name = "MAGIC CODE, Dynamic"; instance->serial = ((instance->data >> 24) & 0xFFFFFF0) | ((instance->data >> 20) & 0x0F); instance->btn = (instance->data >> 24) & 0x0F; instance->cnt = instance->data & 0xFFFF; break; - // case 57: //MAGIC CODE PRO / PRO2 - // instance->protocol_name = "MAGIC CODE PRO / PRO2"; - // break; + case 57: //MAGIC CODE PRO / PRO2 + *protocol_name = "MAGIC CODE PRO/PRO2"; + instance->serial = 0; + instance->btn = 0; + instance->cnt = 0; + break; + case 63: //MAGIC CODE, Dynamic Response + *protocol_name = "MAGIC CODE, Response"; + instance->serial = 0; + instance->btn = 0; + instance->cnt = 0; + break; + case 64: //MAGICAR, Response ??? + *protocol_name = "MAGICAR, Response"; + instance->serial = 0; + instance->btn = 0; + instance->cnt = 0; + break; + case 81: //MAGIC CODE PRO / PRO2 Response ??? + *protocol_name = "MAGIC CODE PRO,\n Response"; + instance->serial = 0; + instance->btn = 0; + instance->cnt = 0; + break; + case 82: //MAGIC CODE PRO / PRO2 Response ??? + *protocol_name = "MAGIC CODE PRO,\n Response"; + instance->serial = 0; + instance->btn = 0; + instance->cnt = 0; + break; default: *protocol_name = "Unknown"; diff --git a/lib/subghz/protocols/secplus_v2.c b/lib/subghz/protocols/secplus_v2.c index 4f11e22b6..3a7a46513 100644 --- a/lib/subghz/protocols/secplus_v2.c +++ b/lib/subghz/protocols/secplus_v2.c @@ -554,6 +554,7 @@ SubGhzProtocolStatus break; } + instance->encoder.front = 0; // reset before start instance->encoder.is_running = true; } while(false); @@ -563,6 +564,7 @@ SubGhzProtocolStatus void subghz_protocol_encoder_secplus_v2_stop(void* context) { SubGhzProtocolEncoderSecPlus_v2* instance = context; instance->encoder.is_running = false; + instance->encoder.front = 0; // reset position } LevelDuration subghz_protocol_encoder_secplus_v2_yield(void* context) { From 85b6b2b8966552a3cd49bf29eae7b0da771dcfa8 Mon Sep 17 00:00:00 2001 From: Zinong Li <131403964+zinongli@users.noreply.github.com> Date: Wed, 1 Oct 2025 18:54:08 +0400 Subject: [PATCH 101/192] NFC FeliCa: Service Directory Traverse + Dump All Unencrypted-Readable Services' Blocks (#4254) * SimpleArray attached to FelicaData * tx rx done. response parsing done (in log) * dynamic vector as buffer. rendering begin * On screen render for directory tree * flags in render to indicate is_public_readable * beautify render flags * format * offload dynamic vector into individual files * saving. exposed dir tree writing for double use * save: additional formatting * save: clean up and some additional notes * load done * delete unnecessary debug log * Load: safer way to handle backward compatibility `parsed` being true is only contingent on whether the header (device type, UID, etc) are correctly read. The detailed data can be absent if saved from previous versions. Side effects: 1. The data format version number must not increment. 2. Newer sections of dumps must be appended in the end of the file. * format * handle block reading according to IC type Old version was aimed for FeliCa Lite dumping, which doesn't apply to FeliCa standard. Thus they need to be diverged in the poller run workflow. * read block content works. rendering begin * Render Refactor: dir & dump view from submenu * Render: show IC type name * IC parsing function cleanup * Revert "IC parsing function cleanup" This reverts commit ee3f7bf125b54b10d238b0aeb657ba15f27f93ba. * Load: Standard dump. Fully backward compatible * format * sync API version * format saved file * delete unused variable * clean ups * IC type addition * correction * beautify attribute parsing * correction * Lite save: delete extra line * correction: FeliCa link in Lite-S mode * format * Save: simplify printing * update IC type parsing * conform to api standard: const resp ptr to ptr also slightly faster and more readable block dump loop * disambiguate workflow type vs ic type It was too confusing to have the ic name string telling you one thing and ic_type enum saying the other. Might as well use better naming to indicate the use case for the two things * beautify on device render * reject dynamic_vector, embrace m-array * lint * use full variable name * partial fix: poller context's data proper init * edit unit test dump IC code and a small bug fix for the Lite auth workflow * unit test felica dump PMm correction * Fixes for static analysis warnings --------- Co-authored-by: hedger Co-authored-by: hedger --- .../resources/unit_tests/nfc/Felica.nfc | 2 +- .../helpers/protocol_support/felica/felica.c | 13 +- .../protocol_support/felica/felica_render.c | 111 +++- .../protocol_support/felica/felica_render.h | 9 +- .../main/nfc/scenes/nfc_scene_config.h | 2 + .../nfc/scenes/nfc_scene_felica_more_info.c | 151 +++++ lib/nfc/protocols/felica/felica.c | 622 ++++++++++++++++-- lib/nfc/protocols/felica/felica.h | 63 ++ lib/nfc/protocols/felica/felica_i.c | 22 + lib/nfc/protocols/felica/felica_i.h | 10 + lib/nfc/protocols/felica/felica_poller.c | 215 +++++- lib/nfc/protocols/felica/felica_poller_i.c | 43 ++ lib/nfc/protocols/felica/felica_poller_i.h | 13 +- lib/nfc/protocols/felica/felica_poller_sync.c | 15 +- lib/subghz/protocols/gangqi.c | 2 +- lib/subghz/protocols/hollarm.c | 2 +- lib/subghz/protocols/scher_khan.c | 9 +- targets/f7/api_symbols.csv | 4 + 18 files changed, 1219 insertions(+), 89 deletions(-) create mode 100644 applications/main/nfc/scenes/nfc_scene_felica_more_info.c create mode 100644 lib/nfc/protocols/felica/felica_i.c create mode 100644 lib/nfc/protocols/felica/felica_i.h diff --git a/applications/debug/unit_tests/resources/unit_tests/nfc/Felica.nfc b/applications/debug/unit_tests/resources/unit_tests/nfc/Felica.nfc index 93ba4ba6c..4fb938537 100644 --- a/applications/debug/unit_tests/resources/unit_tests/nfc/Felica.nfc +++ b/applications/debug/unit_tests/resources/unit_tests/nfc/Felica.nfc @@ -7,7 +7,7 @@ UID: 29 9F FA 53 AB 75 87 6E # FeliCa specific data Data format version: 1 Manufacture id: 29 9F FA 53 AB 75 87 6E -Manufacture parameter: 57 4E 10 2A 94 16 BC 8E +Manufacture parameter: 00 F1 00 00 00 01 43 00 Blocks total: 28 Blocks read: 28 Block 0: 00 00 DE AD BE AF 00 00 00 00 00 00 00 00 DE AD BE AF diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica.c b/applications/main/nfc/helpers/protocol_support/felica/felica.c index 561cd4d2e..0996ca7a1 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica.c +++ b/applications/main/nfc/helpers/protocol_support/felica/felica.c @@ -39,17 +39,8 @@ static bool nfc_scene_info_on_event_felica(NfcApp* instance, SceneManagerEvent e } static void nfc_scene_more_info_on_enter_felica(NfcApp* instance) { - const NfcDevice* device = instance->nfc_device; - const FelicaData* data = nfc_device_get_data(device, NfcProtocolFelica); - - FuriString* temp_str = furi_string_alloc(); - - nfc_render_felica_dump(data, temp_str); - - widget_add_text_scroll_element( - instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); - - furi_string_free(temp_str); + // Jump to advanced scene right away + scene_manager_next_scene(instance->scene_manager, NfcSceneFelicaMoreInfo); } static NfcCommand nfc_scene_read_poller_callback_felica(NfcGenericEvent event, void* context) { diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica_render.c b/applications/main/nfc/helpers/protocol_support/felica/felica_render.c index 6c57fb24b..1ca992bcd 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica_render.c +++ b/applications/main/nfc/helpers/protocol_support/felica/felica_render.c @@ -4,9 +4,16 @@ void nfc_render_felica_blocks_count( const FelicaData* data, FuriString* str, bool render_auth_notification) { - furi_string_cat_printf(str, "\nBlocks Read: %u/%u", data->blocks_read, data->blocks_total); - if(render_auth_notification && data->blocks_read != data->blocks_total) { - furi_string_cat_printf(str, "\nAuth-protected blocks!"); + if(data->workflow_type == FelicaLite) { + furi_string_cat_printf(str, "Blocks: %u\n", data->blocks_total); + + furi_string_cat_printf(str, "\nBlocks Read: %u/%u", data->blocks_read, data->blocks_total); + if(render_auth_notification && data->blocks_read != data->blocks_total) { + furi_string_cat_printf(str, "\nAuth-protected blocks!"); + } + } else if(data->workflow_type == FelicaStandard) { + furi_string_cat_printf( + str, "Public blocks Read: %lu", simple_array_get_count(data->public_blocks)); } } @@ -32,6 +39,11 @@ void nfc_render_felica_info( furi_string_cat_printf(str, "Tech: JIS X 6319-4,\nISO 18092 [NFC-F]\n"); } + FuriString* ic_type_str = furi_string_alloc(); + felica_get_ic_name(data, ic_type_str); + furi_string_cat_printf(str, "IC Type:\n%s\n", furi_string_get_cstr(ic_type_str)); + furi_string_free(ic_type_str); + nfc_render_felica_idm(data, format_type, str); if(format_type == NfcProtocolFormatTypeFull) { @@ -40,6 +52,14 @@ void nfc_render_felica_info( furi_string_cat_printf(str, "%02X ", data->pmm.data[i]); } } + + furi_string_cat_printf(str, "\n"); + furi_string_cat_printf( + str, + "Services found: %lu \nAreas found: %lu\n", + simple_array_get_count(data->services), + simple_array_get_count(data->areas)); + nfc_render_felica_blocks_count(data, str, true); } @@ -59,13 +79,18 @@ static void nfc_render_felica_block_name( static void nfc_render_felica_block_data(const FelicaBlock* block, FuriString* str) { furi_string_cat_printf(str, "\nSF1=%02X; SF2=%02X\n", block->SF1, block->SF2); - for(size_t j = 0; j < FELICA_DATA_BLOCK_SIZE; j++) { - if((j != 0) && (j % 8 == 0)) furi_string_cat_printf(str, "\n"); - furi_string_cat_printf(str, "%02X ", block->data[j]); + for(size_t i = 0; i < FELICA_DATA_BLOCK_SIZE; i += 2) { + furi_string_cat_printf(str, "%02X%02X ", block->data[i], block->data[i + 1]); } furi_string_cat_printf(str, "\n"); } +static void nfc_render_felica_block_data_simple(const FelicaBlock* block, FuriString* str) { + for(size_t i = 0; i < FELICA_DATA_BLOCK_SIZE; i += 2) { + furi_string_cat_printf(str, "%02X%02X ", block->data[i], block->data[i + 1]); + } +} + static void nfc_render_felica_block( const FelicaBlock* block, FuriString* str, @@ -76,8 +101,13 @@ static void nfc_render_felica_block( nfc_render_felica_block_data(block, str); } -void nfc_render_felica_dump(const FelicaData* data, FuriString* str) { +void nfc_more_info_render_felica_lite_dump(const FelicaData* data, FuriString* str) { FuriString* name = furi_string_alloc(); + + furi_string_cat_printf(str, "\e#Blocks read:\n"); + + furi_string_cat_printf(str, "Blocks: %u\n", data->blocks_total); + for(size_t i = 0; i < 14; i++) { furi_string_printf(name, "S_PAD%d", i); uint8_t suf_cnt = 18; @@ -105,3 +135,70 @@ void nfc_render_felica_dump(const FelicaData* data, FuriString* str) { nfc_render_felica_block(&data->data.fs.state, str, "STATE", 20, 21); nfc_render_felica_block(&data->data.fs.crc_check, str, "CRC_CHCK", 15, 17); } + +void nfc_more_info_render_felica_dir(const FelicaData* data, FuriString* str) { + const size_t area_count = simple_array_get_count(data->areas); + const size_t service_count = simple_array_get_count(data->services); + + furi_string_cat_printf(str, "\e#Directory Tree:\n"); + + if(area_count == 0 || service_count == 0) { + furi_string_cat_printf(str, "No services or areas found.\n"); + } else { + furi_string_cat_printf( + str, "%zu areas found.\n%zu services found.\n\n", area_count, service_count); + furi_string_cat_printf( + str, "::: ... are readable services\n||| ... are locked services\n"); + } + felica_write_directory_tree(data, str); +} + +void nfc_more_info_render_felica_blocks( + const FelicaData* data, + FuriString* str, + const uint16_t service_code_key) { + furi_string_cat_printf(str, "\n"); + if(data->workflow_type == FelicaLite) { + furi_string_cat_printf(str, "Blocks: %u\n", data->blocks_total); + FuriString* name = furi_string_alloc(); + + for(size_t i = 0; i < 14; i++) { + furi_string_printf(name, "S_PAD%d", i); + uint8_t suf_cnt = 18; + if(i == 1) { + suf_cnt = 19; + } else if((i == 10) || (i == 12) || (i == 13)) { + suf_cnt = 16; + } + nfc_render_felica_block( + &data->data.fs.spad[i], str, furi_string_get_cstr(name), 20, suf_cnt); + } + furi_string_free(name); + nfc_render_felica_block(&data->data.fs.reg, str, "REG", 23, 23); + nfc_render_felica_block(&data->data.fs.rc, str, "RC", 25, 25); + nfc_render_felica_block(&data->data.fs.mac, str, "MAC", 23, 23); + nfc_render_felica_block(&data->data.fs.id, str, "ID", 25, 25); + nfc_render_felica_block(&data->data.fs.d_id, str, "D_ID", 22, 24); + nfc_render_felica_block(&data->data.fs.ser_c, str, "SER_C", 20, 21); + nfc_render_felica_block(&data->data.fs.sys_c, str, "SYS_C", 20, 21); + nfc_render_felica_block(&data->data.fs.ckv, str, "CKV", 23, 23); + nfc_render_felica_block(&data->data.fs.ck, str, "CK", 25, 25); + nfc_render_felica_block(&data->data.fs.mc, str, "MC", 25, 24); + nfc_render_felica_block(&data->data.fs.wcnt, str, "WCNT", 22, 20); + nfc_render_felica_block(&data->data.fs.mac_a, str, "MAC_A", 20, 20); + nfc_render_felica_block(&data->data.fs.state, str, "STATE", 20, 21); + nfc_render_felica_block(&data->data.fs.crc_check, str, "CRC_CHCK", 15, 17); + + } else if(data->workflow_type == FelicaStandard) { + uint32_t public_blocks_count = simple_array_get_count(data->public_blocks); + for(size_t i = 0; i < public_blocks_count; i++) { + FelicaPublicBlock* public_block = simple_array_get(data->public_blocks, i); + if(public_block->service_code != service_code_key) { + continue; // Skip blocks not matching the requested service code + } + furi_string_cat_printf(str, "-----Block 0x%02X-----\n", public_block->block_idx); + nfc_render_felica_block_data_simple(&public_block->block, str); + furi_string_cat_printf(str, "\n"); + } + } +} diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica_render.h b/applications/main/nfc/helpers/protocol_support/felica/felica_render.h index 48bea4433..5c7c6d036 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica_render.h +++ b/applications/main/nfc/helpers/protocol_support/felica/felica_render.h @@ -14,9 +14,16 @@ void nfc_render_felica_info( NfcProtocolFormatType format_type, FuriString* str); -void nfc_render_felica_dump(const FelicaData* data, FuriString* str); +void nfc_more_info_render_felica_lite_dump(const FelicaData* data, FuriString* str); void nfc_render_felica_idm( const FelicaData* data, NfcProtocolFormatType format_type, FuriString* str); + +void nfc_more_info_render_felica_dir(const FelicaData* data, FuriString* str); + +void nfc_more_info_render_felica_blocks( + const FelicaData* data, + FuriString* str, + const uint16_t service_code_key); diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index 399d59b92..1e3fd506f 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -74,4 +74,6 @@ ADD_SCENE(nfc, slix_key_input, SlixKeyInput) ADD_SCENE(nfc, slix_unlock, SlixUnlock) ADD_SCENE(nfc, slix_unlock_success, SlixUnlockSuccess) +ADD_SCENE(nfc, felica_more_info, FelicaMoreInfo) + ADD_SCENE(nfc, generate_info, GenerateInfo) diff --git a/applications/main/nfc/scenes/nfc_scene_felica_more_info.c b/applications/main/nfc/scenes/nfc_scene_felica_more_info.c new file mode 100644 index 000000000..7eee3d7b3 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_felica_more_info.c @@ -0,0 +1,151 @@ +#include "../nfc_app_i.h" + +#include "../helpers/protocol_support/nfc_protocol_support_gui_common.h" +#include "../helpers/protocol_support/felica/felica_render.h" + +enum { + FelicaMoreInfoStateMenu, + FelicaMoreInfoStateItem, // MUST be last, states >= this correspond with submenu index +}; + +enum SubmenuIndex { + SubmenuIndexDirectory, + SubmenuIndexDynamic, // dynamic indices start here +}; + +void nfc_scene_felica_more_info_on_enter(void* context) { + NfcApp* nfc = context; + Submenu* submenu = nfc->submenu; + + const uint32_t state = + scene_manager_get_scene_state(nfc->scene_manager, NfcSceneFelicaMoreInfo); + const FelicaData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolFelica); + + submenu_add_item( + submenu, + "Directory", + SubmenuIndexDirectory, + nfc_protocol_support_common_submenu_callback, + nfc); + + FuriString* label = furi_string_alloc(); + + switch(data->workflow_type) { + case FelicaLite: + furi_string_printf(label, "All blocks"); + submenu_add_item( + submenu, + furi_string_get_cstr(label), + SubmenuIndexDynamic, + nfc_protocol_support_common_submenu_callback, + nfc); + break; + case FelicaStandard: + for(uint32_t i = 0; i < simple_array_get_count(data->services); ++i) { + const FelicaService* service = simple_array_cget(data->services, i); + bool is_public = (service->attr & FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ) == 1; + if(!is_public) { + continue; + } + furi_string_printf(label, "Readable serv %04X", service->code); + submenu_add_item( + submenu, + furi_string_get_cstr(label), + i + SubmenuIndexDynamic, + nfc_protocol_support_common_submenu_callback, + nfc); + } + break; + default: + break; + } + + furi_string_free(label); + + if(state >= FelicaMoreInfoStateItem) { + submenu_set_selected_item( + nfc->submenu, state - FelicaMoreInfoStateItem + SubmenuIndexDynamic); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneFelicaMoreInfo, FelicaMoreInfoStateMenu); + } + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_felica_more_info_on_event(void* context, SceneManagerEvent event) { + NfcApp* nfc = context; + bool consumed = false; + + const uint32_t state = + scene_manager_get_scene_state(nfc->scene_manager, NfcSceneFelicaMoreInfo); + const FelicaData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolFelica); + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexDirectory) { + FuriString* temp_str = furi_string_alloc(); + nfc_more_info_render_felica_dir(data, temp_str); + + widget_add_text_scroll_element( + nfc->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + scene_manager_set_scene_state( + nfc->scene_manager, + NfcSceneFelicaMoreInfo, + FelicaMoreInfoStateItem + SubmenuIndexDirectory); + consumed = true; + } else { + const uint16_t service_ind = event.event - 1; // offset the three enums above + + text_box_reset(nfc->text_box); + furi_string_reset(nfc->text_box_store); + + switch(data->workflow_type) { + case FelicaLite: + nfc_more_info_render_felica_lite_dump(data, nfc->text_box_store); + break; + case FelicaStandard: + const FelicaService* service = simple_array_cget(data->services, service_ind); + furi_string_cat_printf(nfc->text_box_store, "Service 0x%04X\n", service->code); + nfc_more_info_render_felica_blocks(data, nfc->text_box_store, service->code); + break; + default: + furi_string_set_str(nfc->text_box_store, "IC type not implemented yet"); + break; + } + text_box_set_font(nfc->text_box, TextBoxFontHex); + text_box_set_text(nfc->text_box, furi_string_get_cstr(nfc->text_box_store)); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneFelicaMoreInfo, FelicaMoreInfoStateItem + event.event); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + if(state >= FelicaMoreInfoStateItem) { + widget_reset(nfc->widget); + text_box_reset(nfc->text_box); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneFelicaMoreInfo, FelicaMoreInfoStateMenu); + } else { + widget_reset(nfc->widget); + text_box_reset(nfc->text_box); + // Return directly to the Info scene + scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, NfcSceneInfo); + } + consumed = true; + } + + return consumed; +} + +void nfc_scene_felica_more_info_on_exit(void* context) { + NfcApp* nfc = context; + + // Clear views + widget_reset(nfc->widget); + text_box_reset(nfc->text_box); + furi_string_reset(nfc->text_box_store); + submenu_reset(nfc->submenu); +} diff --git a/lib/nfc/protocols/felica/felica.c b/lib/nfc/protocols/felica/felica.c index a298f37a9..4e0ff7472 100644 --- a/lib/nfc/protocols/felica/felica.c +++ b/lib/nfc/protocols/felica/felica.c @@ -1,4 +1,5 @@ -#include "felica.h" +#include "felica_i.h" +#include #include @@ -11,7 +12,7 @@ #define FELICA_MANUFACTURE_ID "Manufacture id" #define FELICA_MANUFACTURE_PARAMETER "Manufacture parameter" -static const uint32_t felica_data_format_version = 1; +static const uint32_t felica_data_format_version = 2; /** @brief This is used in felica_prepare_first_block to define which * type of block needs to be prepared. @@ -39,24 +40,71 @@ const NfcDeviceBase nfc_device_felica = { FelicaData* felica_alloc(void) { FelicaData* data = malloc(sizeof(FelicaData)); + furi_check(data); + + data->services = simple_array_alloc(&felica_service_array_cfg); + data->areas = simple_array_alloc(&felica_area_array_cfg); + data->public_blocks = simple_array_alloc(&felica_public_block_array_cfg); + furi_check(data->services); + furi_check(data->areas); + furi_check(data->public_blocks); return data; } void felica_free(FelicaData* data) { furi_check(data); + + furi_check(data->services); + simple_array_free(data->services); + + furi_check(data->areas); + simple_array_free(data->areas); + + furi_check(data->public_blocks); + simple_array_free(data->public_blocks); + free(data); } void felica_reset(FelicaData* data) { furi_check(data); - memset(data, 0, sizeof(FelicaData)); + + if(data->services) { + simple_array_reset(data->services); + } + + if(data->areas) { + simple_array_reset(data->areas); + } + + if(data->public_blocks) { + simple_array_reset(data->public_blocks); + } + + data->blocks_read = 0; + data->blocks_total = 0; + data->workflow_type = FelicaUnknown; + memset(&data->idm, 0, sizeof(data->idm)); + memset(&data->pmm, 0, sizeof(data->pmm)); + memset(&data->data, 0, sizeof(data->data)); } void felica_copy(FelicaData* data, const FelicaData* other) { furi_check(data); furi_check(other); - *data = *other; + felica_reset(data); + + data->idm = other->idm; + data->pmm = other->pmm; + data->blocks_total = other->blocks_total; + data->blocks_read = other->blocks_read; + data->data = other->data; + data->workflow_type = other->workflow_type; + + simple_array_copy(data->services, other->services); + simple_array_copy(data->areas, other->areas); + simple_array_copy(data->public_blocks, other->public_blocks); } bool felica_verify(FelicaData* data, const FuriString* device_type) { @@ -70,43 +118,164 @@ bool felica_load(FelicaData* data, FlipperFormat* ff, uint32_t version) { furi_check(data); bool parsed = false; + FuriString* str_key_buffer = furi_string_alloc(); + FuriString* str_data_buffer = furi_string_alloc(); + // Header do { if(version < NFC_UNIFIED_FORMAT_VERSION) break; uint32_t data_format_version = 0; if(!flipper_format_read_uint32(ff, FELICA_DATA_FORMAT_VERSION, &data_format_version, 1)) break; - if(data_format_version != felica_data_format_version) break; + + // V1 saving function always treated everything as Felica Lite + // So we load the blocks as if everything is Felica Lite + if(!flipper_format_read_hex(ff, FELICA_MANUFACTURE_ID, data->idm.data, FELICA_IDM_SIZE)) break; if(!flipper_format_read_hex( ff, FELICA_MANUFACTURE_PARAMETER, data->pmm.data, FELICA_PMM_SIZE)) break; - parsed = true; - uint32_t blocks_total = 0; - uint32_t blocks_read = 0; - if(!flipper_format_read_uint32(ff, "Blocks total", &blocks_total, 1)) break; - if(!flipper_format_read_uint32(ff, "Blocks read", &blocks_read, 1)) break; - data->blocks_total = (uint8_t)blocks_total; - data->blocks_read = (uint8_t)blocks_read; - - FuriString* temp_str = furi_string_alloc(); - for(uint8_t i = 0; i < data->blocks_total; i++) { - furi_string_printf(temp_str, "Block %d", i); - if(!flipper_format_read_hex( - ff, - furi_string_get_cstr(temp_str), - (&data->data.dump[i * sizeof(FelicaBlock)]), - sizeof(FelicaBlock))) { - parsed = false; - break; - } + felica_get_workflow_type(data); + if(data_format_version == 1) { + data->workflow_type = FelicaLite; } - furi_string_free(temp_str); + parsed = true; } while(false); + if(!parsed) { + furi_string_free(str_key_buffer); + furi_string_free(str_data_buffer); + return false; + } + + switch(data->workflow_type) { + case FelicaLite: + // Blocks data + do { + uint32_t blocks_total = 0; + uint32_t blocks_read = 0; + if(!flipper_format_read_uint32(ff, "Blocks total", &blocks_total, 1)) break; + if(!flipper_format_read_uint32(ff, "Blocks read", &blocks_read, 1)) break; + data->blocks_total = (uint8_t)blocks_total; + data->blocks_read = (uint8_t)blocks_read; + + for(uint8_t i = 0; i < data->blocks_total; i++) { + furi_string_printf(str_data_buffer, "Block %d", i); + if(!flipper_format_read_hex( + ff, + furi_string_get_cstr(str_data_buffer), + (&data->data.dump[i * sizeof(FelicaBlock)]), + sizeof(FelicaBlock))) { + break; + } + } + } while(false); + break; + case FelicaStandard: + // Areas + do { + uint32_t area_count = 0; + if(!flipper_format_read_uint32(ff, "Area found", &area_count, 1)) break; + simple_array_init(data->areas, area_count); + + furi_string_reset(str_key_buffer); + furi_string_reset(str_data_buffer); + for(uint16_t i = 0; i < area_count; i++) { + furi_string_printf(str_key_buffer, "Area %03X", i); + if(!flipper_format_read_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) { + break; + } + FelicaArea* area = simple_array_get(data->areas, i); + if(sscanf( + furi_string_get_cstr(str_data_buffer), + "| Code %04hX | Services #%03hX-#%03hX |", + &area->code, + &area->first_idx, + &area->last_idx) != 3) { + break; + } + } + } while(false); + + // Services + do { + uint32_t service_count = 0; + if(!flipper_format_read_uint32(ff, "Service found", &service_count, 1)) break; + simple_array_init(data->services, service_count); + + furi_string_reset(str_key_buffer); + furi_string_reset(str_data_buffer); + for(uint16_t i = 0; i < service_count; i++) { + furi_string_printf(str_key_buffer, "Service %03X", i); + if(!flipper_format_read_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) { + break; + } + FelicaService* service = simple_array_get(data->services, i); + + // all unread in the beginning. reserved for future block load + if(!sscanf( + furi_string_get_cstr(str_data_buffer), "| Code %04hX |", &service->code)) { + break; + } + service->attr = service->code & 0x3F; + } + } while(false); + + // Public blocks + do { + furi_string_reset(str_data_buffer); + furi_string_reset(str_key_buffer); + uint32_t public_block_count = 0; + if(!flipper_format_read_uint32(ff, "Public blocks read", &public_block_count, 1)) + break; + simple_array_init(data->public_blocks, public_block_count); + for(uint16_t i = 0; i < public_block_count; i++) { + furi_string_printf(str_key_buffer, "Block %04X", i); + if(!flipper_format_read_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) { + break; + } + + FelicaPublicBlock* public_block = simple_array_get(data->public_blocks, i); + if(sscanf( + furi_string_get_cstr(str_data_buffer), + "| Service code %04hX | Block index %02hhX |", + &public_block->service_code, + &public_block->block_idx) != 2) { + break; + } + + size_t needle = furi_string_search_str(str_data_buffer, "Data: "); + if(needle == FURI_STRING_FAILURE) { + break; + } + needle += 6; // length of "Data: " = 6 + furi_string_mid(str_data_buffer, needle, 3 * FELICA_DATA_BLOCK_SIZE); + furi_string_replace_all(str_data_buffer, " ", ""); + if(!hex_chars_to_uint8( + furi_string_get_cstr(str_data_buffer), public_block->block.data)) { + break; + } + + furi_string_reset(str_data_buffer); + for(size_t j = 0; j < FELICA_DATA_BLOCK_SIZE; j++) { + furi_string_cat_printf(str_data_buffer, "%02X ", public_block->block.data[j]); + } + } + } while(false); + break; + default: + break; + } + + furi_string_free(str_key_buffer); + furi_string_free(str_data_buffer); + return parsed; } @@ -114,8 +283,10 @@ bool felica_save(const FelicaData* data, FlipperFormat* ff) { furi_check(data); bool saved = false; - + FuriString* str_data_buffer = furi_string_alloc(); + FuriString* str_key_buffer = furi_string_alloc(); do { + // Header if(!flipper_format_write_comment_cstr(ff, FELICA_PROTOCOL_NAME " specific data")) break; if(!flipper_format_write_uint32( ff, FELICA_DATA_FORMAT_VERSION, &felica_data_format_version, 1)) @@ -126,27 +297,134 @@ bool felica_save(const FelicaData* data, FlipperFormat* ff) { ff, FELICA_MANUFACTURE_PARAMETER, data->pmm.data, FELICA_PMM_SIZE)) break; - uint32_t blocks_total = data->blocks_total; - uint32_t blocks_read = data->blocks_read; - if(!flipper_format_write_uint32(ff, "Blocks total", &blocks_total, 1)) break; - if(!flipper_format_write_uint32(ff, "Blocks read", &blocks_read, 1)) break; - saved = true; - FuriString* temp_str = furi_string_alloc(); - for(uint8_t i = 0; i < blocks_total; i++) { - furi_string_printf(temp_str, "Block %d", i); - if(!flipper_format_write_hex( - ff, - furi_string_get_cstr(temp_str), - (&data->data.dump[i * sizeof(FelicaBlock)]), - sizeof(FelicaBlock))) { - saved = false; - break; - } - } - furi_string_free(temp_str); + + felica_get_ic_name(data, str_data_buffer); + furi_string_replace_all(str_data_buffer, "\n", " "); + if(!flipper_format_write_string(ff, "IC Type", str_data_buffer)) break; + if(!flipper_format_write_empty_line(ff)) break; } while(false); + switch(data->workflow_type) { + case FelicaLite: + if(!flipper_format_write_comment_cstr(ff, "Felica Lite specific data")) break; + // Blocks count + do { + uint32_t blocks_total = data->blocks_total; + uint32_t blocks_read = data->blocks_read; + if(!flipper_format_write_uint32(ff, "Blocks total", &blocks_total, 1)) break; + if(!flipper_format_write_uint32(ff, "Blocks read", &blocks_read, 1)) break; + + // Blocks data + furi_string_reset(str_data_buffer); + furi_string_reset(str_key_buffer); + for(uint8_t i = 0; i < blocks_total; i++) { + furi_string_printf(str_key_buffer, "Block %d", i); + if(!flipper_format_write_hex( + ff, + furi_string_get_cstr(str_key_buffer), + (&data->data.dump[i * sizeof(FelicaBlock)]), + sizeof(FelicaBlock))) { + saved = false; + break; + } + } + } while(false); + break; + + case FelicaStandard: + if(!flipper_format_write_comment_cstr(ff, "Felica Standard specific data")) break; + + do { + uint32_t area_count = simple_array_get_count(data->areas); + uint32_t service_count = simple_array_get_count(data->services); + // Note: The theoretical max area/service count is 2^10 + // So uint16_t is already enough for practical usage + // The following key index print will use %03X because 12 bits are enough to cover 0-1023 + + // Area count + if(!flipper_format_write_uint32(ff, "Area found", &area_count, 1)) break; + + // Area data + furi_string_reset(str_data_buffer); + furi_string_reset(str_key_buffer); + for(uint16_t i = 0; i < area_count; i++) { + FelicaArea* area = simple_array_get(data->areas, i); + furi_string_printf(str_key_buffer, "Area %03X", i); + furi_string_printf( + str_data_buffer, + "| Code %04X | Services #%03X-#%03X |", + area->code, + area->first_idx, + area->last_idx); + if(!flipper_format_write_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) + break; + } + if(!flipper_format_write_empty_line(ff)) break; + + // Service count + if(!flipper_format_write_uint32(ff, "Service found", &service_count, 1)) break; + + // Service data + furi_string_reset(str_data_buffer); + furi_string_reset(str_key_buffer); + for(uint16_t i = 0; i < service_count; i++) { + FelicaService* service = simple_array_get(data->services, i); + furi_string_printf(str_key_buffer, "Service %03X", i); + furi_string_printf( + str_data_buffer, "| Code %04X | Attrib. %02X ", service->code, service->attr); + felica_service_get_attribute_string(service, str_data_buffer); + if(!flipper_format_write_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) + break; + } + if(!flipper_format_write_empty_line(ff)) break; + + // Directory tree + furi_string_reset(str_data_buffer); + furi_string_reset(str_key_buffer); + furi_string_printf( + str_data_buffer, "\n::: ... are public services\n||| ... are private services"); + felica_write_directory_tree(data, str_data_buffer); + furi_string_replace_all(str_data_buffer, ":", "+"); + // We use a clearer marker in saved text files + if(!flipper_format_write_string(ff, "Directory Tree", str_data_buffer)) break; + } while(false); + + // Public blocks + do { + uint32_t public_block_count = simple_array_get_count(data->public_blocks); + if(!flipper_format_write_uint32(ff, "Public blocks read", &public_block_count, 1)) + break; + furi_string_reset(str_data_buffer); + furi_string_reset(str_key_buffer); + for(uint16_t i = 0; i < public_block_count; i++) { + FelicaPublicBlock* public_block = simple_array_get(data->public_blocks, i); + furi_string_printf(str_key_buffer, "Block %04X", i); + furi_string_printf( + str_data_buffer, + "| Service code %04X | Block index %02X | Data: ", + public_block->service_code, + public_block->block_idx); + for(uint8_t j = 0; j < FELICA_DATA_BLOCK_SIZE; j++) { + furi_string_cat_printf(str_data_buffer, "%02X ", public_block->block.data[j]); + } + furi_string_cat_printf(str_data_buffer, "|"); + if(!flipper_format_write_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) + break; + } + } while(false); + break; + default: + break; + } + + // Clean up + furi_string_free(str_data_buffer); + furi_string_free(str_key_buffer); + return saved; } @@ -154,7 +432,13 @@ bool felica_is_equal(const FelicaData* data, const FelicaData* other) { furi_check(data); furi_check(other); - return memcmp(data, other, sizeof(FelicaData)) == 0; + return memcmp(data->idm.data, other->idm.data, sizeof(FelicaIDm)) == 0 && + memcmp(data->pmm.data, other->pmm.data, sizeof(FelicaPMm)) == 0 && + data->blocks_total == other->blocks_total && data->blocks_read == other->blocks_read && + memcmp(&data->data, &other->data, sizeof(data->data)) == 0 && + simple_array_is_equal(data->services, other->services) && + simple_array_is_equal(data->areas, other->areas) && + simple_array_is_equal(data->public_blocks, other->public_blocks); } const char* felica_get_device_name(const FelicaData* data, NfcDeviceNameType name_type) { @@ -355,3 +639,251 @@ void felica_calculate_mac_write( memcpy(session_swapped + 8, session_key, 8); felica_calculate_mac(ctx, session_swapped, rc, first_block, data, FELICA_DATA_BLOCK_SIZE, mac); } + +void felica_write_directory_tree(const FelicaData* data, FuriString* str) { + furi_check(data); + furi_check(str); + + furi_string_cat_str(str, "\n"); + + uint16_t area_last_stack[8]; + uint8_t depth = 0; + + size_t area_iter = 0; + const size_t area_count = simple_array_get_count(data->areas); + const size_t service_count = simple_array_get_count(data->services); + + for(size_t svc_idx = 0; svc_idx < service_count; ++svc_idx) { + while(area_iter < area_count) { + const FelicaArea* next_area = simple_array_get(data->areas, area_iter); + if(next_area->first_idx != svc_idx) break; + + for(uint8_t i = 0; i < depth - 1; ++i) + furi_string_cat_printf(str, "| "); + furi_string_cat_printf(str, depth ? "|" : ""); + furi_string_cat_printf(str, "- AREA_%04X/\n", next_area->code >> 6); + + area_last_stack[depth++] = next_area->last_idx; + area_iter++; + } + + const FelicaService* service = simple_array_get(data->services, svc_idx); + bool is_public = (service->attr & FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ) != 0; + + for(uint8_t i = 0; i < depth - 1; ++i) + furi_string_cat_printf(str, is_public ? ": " : "| "); + furi_string_cat_printf(str, is_public ? ":" : "|"); + furi_string_cat_printf(str, "- serv_%04X\n", service->code); + + if(depth && svc_idx >= area_last_stack[depth - 1]) depth--; + } +} + +void felica_get_workflow_type(FelicaData* data) { + // Reference: Proxmark3 repo + uint8_t rom_type = data->pmm.data[0]; + uint8_t workflow_type = data->pmm.data[1]; + if(workflow_type <= 0x48) { + // More liberal check because most of these should be treated as FeliCa Standard, regardless of mobile or not. + data->workflow_type = FelicaStandard; + } else { + switch(workflow_type) { + case 0xA2: + data->workflow_type = FelicaStandard; + break; + case 0xF0: + case 0xF1: + case 0xF2: // 0xF2 => FeliCa Link RC-S967 in Lite-S Mode or Lite-S HT Mode + data->workflow_type = FelicaLite; + break; + case 0xE1: // Felica Link + case 0xE0: // Felica Plug + data->workflow_type = FelicaUnknown; + break; + case 0xFF: + if(rom_type == 0xFF) { + data->workflow_type = FelicaUnknown; // Felica Link + } + break; + default: + data->workflow_type = FelicaUnknown; + break; + } + } +} + +void felica_get_ic_name(const FelicaData* data, FuriString* ic_name) { + // Reference: Proxmark3 repo + uint8_t rom_type = data->pmm.data[0]; + uint8_t ic_type = data->pmm.data[1]; + + switch(ic_type) { + // FeliCa Standard Products: + // odd findings + case 0x00: + furi_string_set_str(ic_name, "FeliCa Standard RC-S830"); + break; + case 0x01: + furi_string_set_str(ic_name, "FeliCa Standard RC-S915"); + break; + case 0x02: + furi_string_set_str(ic_name, "FeliCa Standard RC-S919"); + break; + case 0x06: + case 0x07: + furi_string_set_str(ic_name, "FeliCa Mobile IC,\nChip V1.0"); + break; + case 0x08: + furi_string_set_str(ic_name, "FeliCa Standard RC-S952"); + break; + case 0x09: + furi_string_set_str(ic_name, "FeliCa Standard RC-S953"); + break; + case 0x0B: + furi_string_set_str(ic_name, "FeliCa Standard RC-S9X4,\nJapan Transit IC"); + break; + case 0x0C: + furi_string_set_str(ic_name, "FeliCa Standard RC-S954"); + break; + case 0x0D: + furi_string_set_str(ic_name, "FeliCa Standard RC-S960"); + break; + case 0x10: + case 0x11: + case 0x12: + case 0x13: + furi_string_set_str(ic_name, "FeliCa Mobile IC,\nChip V2.0"); + break; + case 0x14: + case 0x15: + furi_string_set_str(ic_name, "FeliCa Mobile IC,\nChip V3.0"); + break; + case 0x16: + furi_string_set_str(ic_name, "FeliCa Mobile IC,\nJapan Transit IC"); + break; + case 0x17: + furi_string_set_str(ic_name, "FeliCa Mobile IC,\nChip V4.0"); + break; + case 0x18: + case 0x19: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: + furi_string_set_str(ic_name, "FeliCa Mobile IC,\nChip V4.1"); + break; + case 0x20: + furi_string_set_str(ic_name, "FeliCa Standard RC-S962"); + // RC-S962 has been extensively found in Japan Transit ICs, despite model number not ending in 4 + break; + case 0x31: + furi_string_set_str(ic_name, "FeliCa Standard RC-S104,\nJapan Transit IC"); + break; + case 0x32: + furi_string_set_str(ic_name, "FeliCa Standard RC-SA00/1"); + break; + case 0x33: + furi_string_set_str(ic_name, "FeliCa Standard RC-SA00/2"); + break; + case 0x34: + furi_string_set_str(ic_name, "FeliCa Standard RC-SA01/1"); + break; + case 0x35: + furi_string_set_str(ic_name, "FeliCa Standard RC-SA01/2"); + break; + case 0x36: + furi_string_set_str(ic_name, "FeliCa Standard RC-SA04/1,\nJapan Transit IC"); + break; + case 0x3E: + furi_string_set_str(ic_name, "FeliCa Standard RC-SA08/1"); + break; + case 0x43: + furi_string_set_str(ic_name, "FeliCa Standard RC-SA24/1"); + break; + case 0x44: + furi_string_set_str(ic_name, "FeliCa Standard RC-SA20/1"); + break; + case 0x45: + furi_string_set_str(ic_name, "FeliCa Standard RC-SA20/2"); + break; + case 0x46: + furi_string_set_str(ic_name, "FeliCa Standard RC-SA21/2"); + break; + case 0x47: + furi_string_set_str(ic_name, "FeliCa Standard RC-SA24/1x1"); + break; + case 0x48: + furi_string_set_str(ic_name, "FeliCa Standard RC-SA21/2x1"); + break; + case 0xA2: + furi_string_set_str(ic_name, "FeliCa Standard RC-SA14"); + break; + // NFC Dynamic Tag (FeliCa Plug) Products: + case 0xE0: + furi_string_set_str(ic_name, "FeliCa Plug RC-S926,\nNFC Dynamic Tag"); + break; + case 0xE1: + furi_string_set_str(ic_name, "FeliCa Link RC-S967,\nPlug Mode"); + break; + case 0xF0: + furi_string_set_str(ic_name, "FeliCa Lite RC-S965"); + break; + case 0xF1: + furi_string_set_str(ic_name, "FeliCa Lite-S RC-S966"); + break; + case 0xF2: + furi_string_set_str(ic_name, "FeliCa Link RC-S967,\nLite-S Mode or Lite-S HT Mode"); + break; + case 0xFF: + if(rom_type == 0xFF) { // from FeliCa Link User's Manual + furi_string_set_str(ic_name, "FeliCa Link RC-S967,\nNFC-DEP Mode"); + } + break; + default: + furi_string_printf( + ic_name, + "Unknown IC %02X ROM %02X:\nPlease submit an issue on\nGitHub and help us identify.", + ic_type, + rom_type); + break; + } +} + +void felica_service_get_attribute_string(const FelicaService* service, FuriString* str) { + furi_check(service); + furi_check(str); + + bool is_public = (service->attr & FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ) != 0; + furi_string_cat_str(str, is_public ? "| Public " : "| Private "); + + bool is_purse = (service->attr & FELICA_SERVICE_ATTRIBUTE_PURSE) != 0; + // Subfield bitwise attributes are applicable depending on is PURSE or not + + if(is_purse) { + furi_string_cat_str(str, "| Purse |"); + switch((service->attr & FELICA_SERVICE_ATTRIBUTE_PURSE_SUBFIELD) >> 1) { + case 0: + furi_string_cat_str(str, " Direct |"); + break; + case 1: + furi_string_cat_str(str, " Cashback |"); + break; + case 2: + furi_string_cat_str(str, " Decrement |"); + break; + case 3: + furi_string_cat_str(str, " Read Only |"); + break; + default: + furi_string_cat_str(str, " Unknown |"); + break; + } + } else { + bool is_random = (service->attr & FELICA_SERVICE_ATTRIBUTE_RANDOM_ACCESS) != 0; + furi_string_cat_str(str, is_random ? "| Random |" : "| Cyclic |"); + bool is_readonly = (service->attr & FELICA_SERVICE_ATTRIBUTE_READ_ONLY) != 0; + furi_string_cat_str(str, is_readonly ? " Read Only |" : " Read/Write |"); + } +} diff --git a/lib/nfc/protocols/felica/felica.h b/lib/nfc/protocols/felica/felica.h index 1fcaffc53..5a5a75215 100644 --- a/lib/nfc/protocols/felica/felica.h +++ b/lib/nfc/protocols/felica/felica.h @@ -3,6 +3,7 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { @@ -34,6 +35,8 @@ extern "C" { #define FELICA_BLOCK_INDEX_STATE (0x92U) #define FELICA_BLOCK_INDEX_CRC_CHECK (0xA0U) +#define FELICA_STANDARD_MAX_BLOCK_COUNT (0xFFU) + #define FELICA_GUARD_TIME_US (20000U) #define FELICA_FDT_POLL_FC (10000U) #define FELICA_POLL_POLL_MIN_US (1280U) @@ -47,6 +50,16 @@ extern "C" { #define FELICA_TIME_SLOT_8 (0x07U) #define FELICA_TIME_SLOT_16 (0x0FU) +#define FELICA_CMD_LIST_SERVICE_CODE 0x0A +#define FELICA_CMD_LIST_SERVICE_CODE_RESP 0x0B + +#define FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ (0b000001) +#define FELICA_SERVICE_ATTRIBUTE_READ_ONLY (0b000010) +#define FELICA_SERVICE_ATTRIBUTE_RANDOM_ACCESS (0b001000) +#define FELICA_SERVICE_ATTRIBUTE_CYCLIC (0b001100) +#define FELICA_SERVICE_ATTRIBUTE_PURSE (0b010000) +#define FELICA_SERVICE_ATTRIBUTE_PURSE_SUBFIELD (0b000110) + /** @brief Type of possible Felica errors */ typedef enum { FelicaErrorNone, @@ -61,6 +74,12 @@ typedef enum { FelicaErrorFeatureUnsupported, } FelicaError; +typedef enum { + FelicaUnknown, + FelicaStandard, + FelicaLite, +} FelicaWorkflowType; + typedef struct { uint8_t data[FELICA_DATA_BLOCK_SIZE]; } FelicaBlockData; @@ -146,6 +165,23 @@ typedef union { uint8_t dump[sizeof(FelicaFileSystem)]; } FelicaFSUnion; +typedef struct { + uint16_t code; + uint8_t attr; +} FelicaService; + +typedef struct { + uint16_t code; + uint16_t first_idx; + uint16_t last_idx; +} FelicaArea; + +typedef struct { + FelicaBlock block; + uint16_t service_code; + uint8_t block_idx; +} FelicaPublicBlock; + /** @brief Structure used to store Felica data and additional values about reading */ typedef struct { FelicaIDm idm; @@ -153,6 +189,11 @@ typedef struct { uint8_t blocks_total; uint8_t blocks_read; FelicaFSUnion data; + + SimpleArray* services; + SimpleArray* areas; + SimpleArray* public_blocks; + FelicaWorkflowType workflow_type; } FelicaData; typedef struct FURI_PACKED { @@ -171,6 +212,14 @@ typedef struct { uint8_t SF2; } FelicaCommandResponseHeader; +#pragma pack(push, 1) +typedef struct { + uint8_t length; + uint8_t command; + FelicaIDm idm; +} FelicaCommandHeaderRaw; +#pragma pack(pop) + typedef struct { uint8_t service_code : 4; uint8_t access_mode : 3; @@ -194,6 +243,11 @@ typedef struct { uint8_t data[]; } FelicaListenerReadCommandResponse; +typedef struct { + FelicaCommandHeaderRaw header; + uint8_t data[]; +} FelicaListServiceCommandResponse; + typedef FelicaCommandResponseHeader FelicaListenerWriteCommandResponse; typedef FelicaCommandResponseHeader FelicaPollerWriteCommandResponse; @@ -254,6 +308,15 @@ void felica_calculate_mac_write( const uint8_t* wcnt, const uint8_t* data, uint8_t* mac); + +void felica_write_directory_tree(const FelicaData* data, FuriString* str); + +void felica_get_workflow_type(FelicaData* data); + +void felica_get_ic_name(const FelicaData* data, FuriString* ic_name); + +void felica_service_get_attribute_string(const FelicaService* service, FuriString* str); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/felica/felica_i.c b/lib/nfc/protocols/felica/felica_i.c new file mode 100644 index 000000000..e265ea862 --- /dev/null +++ b/lib/nfc/protocols/felica/felica_i.c @@ -0,0 +1,22 @@ +#include "felica_i.h" + +const SimpleArrayConfig felica_service_array_cfg = { + .init = NULL, + .copy = NULL, + .reset = NULL, + .type_size = sizeof(FelicaService), +}; + +const SimpleArrayConfig felica_area_array_cfg = { + .init = NULL, + .copy = NULL, + .reset = NULL, + .type_size = sizeof(FelicaArea), +}; + +const SimpleArrayConfig felica_public_block_array_cfg = { + .init = NULL, + .copy = NULL, + .reset = NULL, + .type_size = sizeof(FelicaPublicBlock), +}; diff --git a/lib/nfc/protocols/felica/felica_i.h b/lib/nfc/protocols/felica/felica_i.h new file mode 100644 index 000000000..a708aa729 --- /dev/null +++ b/lib/nfc/protocols/felica/felica_i.h @@ -0,0 +1,10 @@ +#pragma once +#include +#include +#include "felica.h" + +#include + +extern const SimpleArrayConfig felica_service_array_cfg; +extern const SimpleArrayConfig felica_area_array_cfg; +extern const SimpleArrayConfig felica_public_block_array_cfg; diff --git a/lib/nfc/protocols/felica/felica_poller.c b/lib/nfc/protocols/felica/felica_poller.c index cf00cbc09..a6964c685 100644 --- a/lib/nfc/protocols/felica/felica_poller.c +++ b/lib/nfc/protocols/felica/felica_poller.c @@ -1,4 +1,6 @@ #include "felica_poller_i.h" +#include +#include #include @@ -7,6 +9,10 @@ #define TAG "FelicaPoller" +ARRAY_DEF(felica_service_array, FelicaService, M_POD_OPLIST); // -V658 +ARRAY_DEF(felica_area_array, FelicaArea, M_POD_OPLIST); // -V658 +ARRAY_DEF(felica_public_block_array, FelicaPublicBlock, M_POD_OPLIST); // -V658 + typedef NfcCommand (*FelicaPollerReadHandler)(FelicaPoller* instance); const FelicaData* felica_poller_get_data(FelicaPoller* instance) { @@ -79,15 +85,30 @@ NfcCommand felica_poller_state_handler_activate(FelicaPoller* instance) { FelicaError error = felica_poller_activate(instance, instance->data); if(error == FelicaErrorNone) { furi_hal_random_fill_buf(instance->data->data.fs.rc.data, FELICA_DATA_BLOCK_SIZE); + felica_get_workflow_type(instance->data); instance->felica_event.type = FelicaPollerEventTypeRequestAuthContext; instance->felica_event_data.auth_context = &instance->auth.context; instance->callback(instance->general_event, instance->context); + switch(instance->data->workflow_type) { + case FelicaStandard: + instance->state = FelicaPollerStateTraverseStandardSystem; + break; + case FelicaLite: + instance->state = FelicaPollerStateReadLiteBlocks; + break; + default: + // Unimplemented + instance->state = FelicaPollerStateReadSuccess; + break; + } + bool skip_auth = instance->auth.context.skip_auth; - instance->state = skip_auth ? FelicaPollerStateReadBlocks : - FelicaPollerStateAuthenticateInternal; + if(!skip_auth) { + instance->state = FelicaPollerStateAuthenticateInternal; + } } else if(error != FelicaErrorTimeout) { instance->felica_event.type = FelicaPollerEventTypeError; instance->felica_event_data.error = error; @@ -105,7 +126,18 @@ NfcCommand felica_poller_state_handler_auth_internal(FelicaPoller* instance) { instance->data->data.fs.rc.data, instance->auth.session_key.data); - instance->state = FelicaPollerStateReadBlocks; + switch(instance->data->workflow_type) { + case FelicaStandard: + instance->state = FelicaPollerStateTraverseStandardSystem; + break; + case FelicaLite: + instance->state = FelicaPollerStateReadLiteBlocks; + break; + default: + // Unimplemented + instance->state = FelicaPollerStateReadSuccess; + break; + } uint8_t blocks[3] = {FELICA_BLOCK_INDEX_RC, 0, 0}; FelicaPollerWriteCommandResponse* tx_resp; @@ -145,7 +177,6 @@ NfcCommand felica_poller_state_handler_auth_internal(FelicaPoller* instance) { NfcCommand felica_poller_state_handler_auth_external(FelicaPoller* instance) { FURI_LOG_D(TAG, "Auth External"); - instance->state = FelicaPollerStateReadBlocks; uint8_t blocks[2]; instance->data->data.fs.state.data[0] = 1; @@ -183,12 +214,177 @@ NfcCommand felica_poller_state_handler_auth_external(FelicaPoller* instance) { memcpy(instance->data->data.fs.state.data, rx_resp->data, FELICA_DATA_BLOCK_SIZE); instance->auth.context.auth_status.external = instance->data->data.fs.state.data[0]; } while(false); - instance->state = FelicaPollerStateReadBlocks; + + switch(instance->data->workflow_type) { + case FelicaStandard: + instance->state = FelicaPollerStateTraverseStandardSystem; + break; + case FelicaLite: + instance->state = FelicaPollerStateReadLiteBlocks; + break; + default: + // Unimplemented + instance->state = FelicaPollerStateReadSuccess; + break; + } + return NfcCommandContinue; } -NfcCommand felica_poller_state_handler_read_blocks(FelicaPoller* instance) { - FURI_LOG_D(TAG, "Read Blocks"); +NfcCommand felica_poller_state_handler_traverse_standard_system(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Traverse Standard System"); + + FelicaListServiceCommandResponse* response; + + felica_service_array_t service_buffer; + felica_service_array_init(service_buffer); + felica_area_array_t area_buffer; + felica_area_array_init(area_buffer); + + for(uint16_t cursor = 0; cursor < 0xFFFF; cursor++) { + FelicaError error = felica_poller_list_service_by_cursor(instance, cursor, &response); + if(error != FelicaErrorNone) { + FURI_LOG_E(TAG, "Error %d at cursor %04X", error, cursor); + break; + } + + uint8_t len = response->header.length; + const uint8_t* list_service_payload = response->data; + uint16_t code_begin = (uint16_t)(list_service_payload[0] | (list_service_payload[1] << 8)); + + if(len != 0x0C && len != 0x0E) { + FURI_LOG_E(TAG, "Bad command resp length 0x%02X at cursor 0x%04X", len, cursor); + break; + } + + if(code_begin == 0xFFFF) { + FURI_LOG_D(TAG, "Traverse complete"); + break; + } + + if(len == 0x0E) { + FelicaArea* area = felica_area_array_push_raw(area_buffer); + memset(area, 0, sizeof *area); + area->code = code_begin; + area->first_idx = (uint16_t)felica_service_array_size(service_buffer); + area->last_idx = 0; + } else { + FelicaService* service = felica_service_array_push_raw(service_buffer); + memset(service, 0, sizeof *service); + service->code = code_begin; + service->attr = (uint8_t)(code_begin & 0x3F); + + if(felica_area_array_size(area_buffer)) { + FelicaArea* current_area = felica_area_array_back(area_buffer); + current_area->last_idx = (uint16_t)(felica_service_array_size(service_buffer) - 1); + } + } + } + + const size_t service_num = felica_service_array_size(service_buffer); + const size_t area_num = felica_area_array_size(area_buffer); + + if(service_num) { + simple_array_init(instance->data->services, (uint32_t)service_num); + memcpy( + simple_array_get(instance->data->services, 0), + service_buffer->ptr, + service_num * sizeof(FelicaService)); + } else { + simple_array_reset(instance->data->services); + } + + if(area_num) { + simple_array_init(instance->data->areas, (uint32_t)area_num); + memcpy( + simple_array_get(instance->data->areas, 0), + area_buffer->ptr, + area_num * sizeof(FelicaArea)); + } else { + simple_array_reset(instance->data->areas); + } + + FURI_LOG_I( + TAG, + "Services found: %lu, Areas found: %lu", + simple_array_get_count(instance->data->services), + simple_array_get_count(instance->data->areas)); + + felica_service_array_clear(service_buffer); + felica_area_array_clear(area_buffer); + + instance->state = FelicaPollerStateReadStandardBlocks; + return NfcCommandContinue; +} + +NfcCommand felica_poller_state_handler_read_standard_blocks(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Read Standard Blocks"); + + const uint32_t service_count = simple_array_get_count(instance->data->services); + + felica_public_block_array_t public_block_buffer; + felica_public_block_array_init(public_block_buffer); + + instance->state = FelicaPollerStateReadSuccess; + bool have_read_anything = false; + + for(uint32_t i = 0; i < service_count; i++) { + const FelicaService* service = simple_array_get(instance->data->services, i); + + if((service->attr & FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ) == 0) continue; + + uint8_t block_count = 1; + uint8_t block_list[1] = {0}; + FelicaError error = FelicaErrorNone; + FelicaPollerReadCommandResponse* response; + do { + error = felica_poller_read_blocks( + instance, block_count, block_list, service->code, &response); + + if(error != FelicaErrorNone) { + break; + } + + if(response->SF1 == 0 && response->SF2 == 0) { + FelicaPublicBlock* public_block = + felica_public_block_array_push_raw(public_block_buffer); + memset(public_block, 0, sizeof *public_block); + memcpy(public_block->block.data, response->data, FELICA_DATA_BLOCK_SIZE); + + public_block->service_code = service->code; + public_block->block_idx = block_list[0]; + + have_read_anything = true; + block_list[0]++; + } else { + break; // No more blocks to read in this service, ok to continue for loop + } + } while(block_list[0] < FELICA_STANDARD_MAX_BLOCK_COUNT); + + if(error != FelicaErrorNone) { + instance->felica_event.type = FelicaPollerEventTypeError; + instance->felica_event_data.error = error; + instance->state = FelicaPollerStateReadFailed; + break; + } + } + + if(have_read_anything) { + const size_t n = felica_public_block_array_size(public_block_buffer); + simple_array_init(instance->data->public_blocks, (uint32_t)n); + memcpy( + simple_array_get(instance->data->public_blocks, 0), + public_block_buffer->ptr, + n * sizeof(FelicaPublicBlock)); + } + + felica_public_block_array_clear(public_block_buffer); + + return NfcCommandContinue; +} + +NfcCommand felica_poller_state_handler_read_lite_blocks(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Read Lite Blocks"); uint8_t block_count = 1; uint8_t block_list[4] = {0, 0, 0, 0}; @@ -266,7 +462,10 @@ static const FelicaPollerReadHandler felica_poller_handler[FelicaPollerStateNum] [FelicaPollerStateActivated] = felica_poller_state_handler_activate, [FelicaPollerStateAuthenticateInternal] = felica_poller_state_handler_auth_internal, [FelicaPollerStateAuthenticateExternal] = felica_poller_state_handler_auth_external, - [FelicaPollerStateReadBlocks] = felica_poller_state_handler_read_blocks, + [FelicaPollerStateTraverseStandardSystem] = + felica_poller_state_handler_traverse_standard_system, + [FelicaPollerStateReadStandardBlocks] = felica_poller_state_handler_read_standard_blocks, + [FelicaPollerStateReadLiteBlocks] = felica_poller_state_handler_read_lite_blocks, [FelicaPollerStateReadSuccess] = felica_poller_state_handler_read_success, [FelicaPollerStateReadFailed] = felica_poller_state_handler_read_failed, }; diff --git a/lib/nfc/protocols/felica/felica_poller_i.c b/lib/nfc/protocols/felica/felica_poller_i.c index 49112debd..4ab9a8e4c 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.c +++ b/lib/nfc/protocols/felica/felica_poller_i.c @@ -93,6 +93,7 @@ FelicaError felica_poller_polling( return error; } +// This is in fact a buffer preparer for a specified service. It should be have the _ex suffix. The prepare_tx_buffer_raw should have this name. static void felica_poller_prepare_tx_buffer( const FelicaPoller* instance, const uint8_t command, @@ -221,3 +222,45 @@ FelicaError felica_poller_activate(FelicaPoller* instance, FelicaData* data) { return ret; } + +static void felica_poller_prepare_tx_buffer_raw( + const FelicaPoller* instance, + const uint8_t command, + const uint8_t* data, + const uint8_t data_length) { + FelicaCommandHeaderRaw cmd = {.length = 0x00, .command = command, .idm = instance->data->idm}; + + cmd.length = sizeof(FelicaCommandHeaderRaw) + data_length; + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)&cmd, sizeof(FelicaCommandHeaderRaw)); + bit_buffer_append_bytes(instance->tx_buffer, data, data_length); +} + +FelicaError felica_poller_list_service_by_cursor( + FelicaPoller* instance, + uint16_t cursor, + FelicaListServiceCommandResponse** const response_ptr) { + furi_assert(instance); + furi_assert(response_ptr); + + const uint8_t data[2] = {(uint8_t)(cursor & 0xFF), (uint8_t)((cursor >> 8) & 0xFF)}; + + felica_poller_prepare_tx_buffer_raw( + instance, FELICA_CMD_LIST_SERVICE_CODE, data, sizeof(data)); + + bit_buffer_reset(instance->rx_buffer); + + FelicaError error = felica_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, FELICA_POLLER_POLLING_FWT); + if(error != FelicaErrorNone) { + FURI_LOG_E(TAG, "List service by cursor failed with error: %d", error); + return error; + } + + size_t rx_len = bit_buffer_get_size_bytes(instance->rx_buffer); + if(rx_len < sizeof(FelicaCommandHeaderRaw) + 2) return FelicaErrorProtocol; + + // error is known to be FelicaErrorNone here + *response_ptr = (FelicaListServiceCommandResponse*)bit_buffer_get_data(instance->rx_buffer); + return error; +} diff --git a/lib/nfc/protocols/felica/felica_poller_i.h b/lib/nfc/protocols/felica/felica_poller_i.h index df4990a4e..98948db86 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.h +++ b/lib/nfc/protocols/felica/felica_poller_i.h @@ -19,7 +19,9 @@ typedef enum { FelicaPollerStateActivated, FelicaPollerStateAuthenticateInternal, FelicaPollerStateAuthenticateExternal, - FelicaPollerStateReadBlocks, + FelicaPollerStateTraverseStandardSystem, + FelicaPollerStateReadStandardBlocks, + FelicaPollerStateReadLiteBlocks, FelicaPollerStateReadSuccess, FelicaPollerStateReadFailed, @@ -55,6 +57,10 @@ typedef struct { uint8_t request_data[2]; } FelicaPollerPollingResponse; +typedef union { + FelicaData* data; +} FelicaPollerContextData; + const FelicaData* felica_poller_get_data(FelicaPoller* instance); /** @@ -105,6 +111,11 @@ FelicaError felica_poller_frame_exchange( BitBuffer* rx_buffer, uint32_t fwt); +FelicaError felica_poller_list_service_by_cursor( + FelicaPoller* instance, + uint16_t cursor, + FelicaListServiceCommandResponse** response_ptr); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/felica/felica_poller_sync.c b/lib/nfc/protocols/felica/felica_poller_sync.c index ec73227f5..707a0cbf3 100644 --- a/lib/nfc/protocols/felica/felica_poller_sync.c +++ b/lib/nfc/protocols/felica/felica_poller_sync.c @@ -11,8 +11,8 @@ typedef struct { FelicaAuthenticationContext auth_ctx; FuriThreadId thread_id; FelicaError error; - FelicaData data; -} Felica_PollerContext; + FelicaPollerContextData data; +} FelicaPollerContext; NfcCommand felica_poller_read_callback(NfcGenericEvent event, void* context) { furi_assert(context); @@ -20,14 +20,14 @@ NfcCommand felica_poller_read_callback(NfcGenericEvent event, void* context) { furi_assert(event.instance); furi_assert(event.protocol == NfcProtocolFelica); - Felica_PollerContext* poller_context = context; + FelicaPollerContext* poller_context = context; FelicaPoller* felica_poller = event.instance; FelicaPollerEvent* felica_event = event.event_data; if(felica_event->type == FelicaPollerEventTypeReady || felica_event->type == FelicaPollerEventTypeIncomplete) { - felica_copy(&poller_context->data, felica_poller->data); + felica_copy(poller_context->data.data, felica_poller->data); } else if(felica_event->type == FelicaPollerEventTypeRequestAuthContext) { felica_event->data->auth_context->skip_auth = poller_context->auth_ctx.skip_auth; memcpy( @@ -45,7 +45,7 @@ FelicaError felica_poller_sync_read(Nfc* nfc, FelicaData* data, const FelicaCard furi_check(nfc); furi_check(data); - Felica_PollerContext poller_context = {}; + FelicaPollerContext poller_context = {}; if(card_key == NULL) { poller_context.auth_ctx.skip_auth = true; } else { @@ -54,6 +54,7 @@ FelicaError felica_poller_sync_read(Nfc* nfc, FelicaData* data, const FelicaCard } poller_context.thread_id = furi_thread_get_current_id(); + poller_context.data.data = felica_alloc(); NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolFelica); nfc_poller_start(poller, felica_poller_read_callback, &poller_context); furi_thread_flags_wait(FELICA_POLLER_FLAG_COMMAND_COMPLETE, FuriFlagWaitAny, FuriWaitForever); @@ -63,8 +64,10 @@ FelicaError felica_poller_sync_read(Nfc* nfc, FelicaData* data, const FelicaCard nfc_poller_free(poller); if(poller_context.error == FelicaErrorNone) { - *data = poller_context.data; + felica_copy(data, poller_context.data.data); } + felica_free(poller_context.data.data); + return poller_context.error; } diff --git a/lib/subghz/protocols/gangqi.c b/lib/subghz/protocols/gangqi.c index c8a241337..b27c55c8f 100644 --- a/lib/subghz/protocols/gangqi.c +++ b/lib/subghz/protocols/gangqi.c @@ -192,7 +192,7 @@ SubGhzProtocolStatus } uint8_t key_data[sizeof(uint64_t)] = {0}; for(size_t i = 0; i < sizeof(uint64_t); i++) { - key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF; + key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> (i * 8)) & 0xFF; } if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { FURI_LOG_E(TAG, "Unable to add Key"); diff --git a/lib/subghz/protocols/hollarm.c b/lib/subghz/protocols/hollarm.c index 70ba6c586..512dd4744 100644 --- a/lib/subghz/protocols/hollarm.c +++ b/lib/subghz/protocols/hollarm.c @@ -189,7 +189,7 @@ SubGhzProtocolStatus } uint8_t key_data[sizeof(uint64_t)] = {0}; for(size_t i = 0; i < sizeof(uint64_t); i++) { - key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF; + key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> (i * 8)) & 0xFF; } if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { FURI_LOG_E(TAG, "Unable to add Key"); diff --git a/lib/subghz/protocols/scher_khan.c b/lib/subghz/protocols/scher_khan.c index f1c41bd1e..60599ca23 100644 --- a/lib/subghz/protocols/scher_khan.c +++ b/lib/subghz/protocols/scher_khan.c @@ -249,13 +249,8 @@ static void subghz_protocol_scher_khan_check_remote_controller( instance->btn = 0; instance->cnt = 0; break; - case 81: //MAGIC CODE PRO / PRO2 Response ??? - *protocol_name = "MAGIC CODE PRO,\n Response"; - instance->serial = 0; - instance->btn = 0; - instance->cnt = 0; - break; - case 82: //MAGIC CODE PRO / PRO2 Response ??? + case 81: // MAGIC CODE PRO / PRO2 Response ??? + case 82: // MAGIC CODE PRO / PRO2 Response ??? *protocol_name = "MAGIC CODE PRO,\n Response"; instance->serial = 0; instance->btn = 0; diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index de385684d..15a3d360b 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1070,7 +1070,9 @@ Function,+,felica_crc_trim,void,BitBuffer* Function,+,felica_free,void,FelicaData* Function,+,felica_get_base_data,FelicaData*,const FelicaData* Function,+,felica_get_device_name,const char*,"const FelicaData*, NfcDeviceNameType" +Function,+,felica_get_ic_name,void,"const FelicaData*, FuriString*" Function,+,felica_get_uid,const uint8_t*,"const FelicaData*, size_t*" +Function,+,felica_get_workflow_type,void,FelicaData* Function,+,felica_is_equal,_Bool,"const FelicaData*, const FelicaData*" Function,+,felica_load,_Bool,"FelicaData*, FlipperFormat*, uint32_t" Function,+,felica_poller_activate,FelicaError,"FelicaPoller*, FelicaData*" @@ -1078,8 +1080,10 @@ Function,+,felica_poller_read_blocks,FelicaError,"FelicaPoller*, const uint8_t, Function,+,felica_poller_sync_read,FelicaError,"Nfc*, FelicaData*, const FelicaCardKey*" Function,+,felica_reset,void,FelicaData* Function,+,felica_save,_Bool,"const FelicaData*, FlipperFormat*" +Function,+,felica_service_get_attribute_string,void,"const FelicaService*, FuriString*" Function,+,felica_set_uid,_Bool,"FelicaData*, const uint8_t*, size_t" Function,+,felica_verify,_Bool,"FelicaData*, const FuriString*" +Function,+,felica_write_directory_tree,void,"const FelicaData*, FuriString*" Function,-,feof,int,FILE* Function,-,feof_unlocked,int,FILE* Function,-,ferror,int,FILE* From d79539c75c81c3bbca10dbb037096a8adf5233ec Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 1 Oct 2025 19:48:57 +0300 Subject: [PATCH 102/192] upd changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6654804a..705689ae0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * SubGHz: Add **ZKTeco 430.5 MHz** add manually support * SubGHz: Add variant of 'Add Manually' menu with manual editing for each value (PR #909 #911 #914 | by @MrLego8-9) * SubGHz: Temporarily remove HoneywellSec protocol due to unstable decoding and incorrect encoding +* OFW: NFC FeliCa: Service Directory Traverse + Dump All Unencrypted-Readable Services' Blocks * OFW: **NFC CLI commands** * OFW: LFRFID: **Show ISO-3166 Country Names For Pet Chips** * OFW: **JS views finished** From fe52de2331891d77ce02103e1aade3bb74718688 Mon Sep 17 00:00:00 2001 From: noproto Date: Sat, 4 Oct 2025 10:53:42 -0400 Subject: [PATCH 103/192] Fix EV1 regression introduced in e7634d7 --- .../mf_ultralight/mf_ultralight.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index 5ed564897..f585d6e4d 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -152,13 +152,15 @@ static NfcCommand if(!mf_ultralight_event->data->auth_context.skip_auth) { mf_ultralight_event->data->auth_context.password = instance->mf_ul_auth->password; - // Only set tdes_key for Manual/Reader auth types, not for dictionary attacks - if(instance->mf_ul_auth->type == MfUltralightAuthTypeManual || - instance->mf_ul_auth->type == MfUltralightAuthTypeReader) { - mf_ultralight_event->data->key_request_data.key = instance->mf_ul_auth->tdes_key; - mf_ultralight_event->data->key_request_data.key_provided = true; - } else { - mf_ultralight_event->data->key_request_data.key_provided = false; + if(data->type == MfUltralightTypeMfulC) { + // Only set tdes_key for Manual/Reader auth types, not for dictionary attacks + if(instance->mf_ul_auth->type == MfUltralightAuthTypeManual || + instance->mf_ul_auth->type == MfUltralightAuthTypeReader) { + mf_ultralight_event->data->key_request_data.key = instance->mf_ul_auth->tdes_key; + mf_ultralight_event->data->key_request_data.key_provided = true; + } else { + mf_ultralight_event->data->key_request_data.key_provided = false; + } } } } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthSuccess) { From 4d1a951d6dd34953986d8bf03c24dec9ae0269af Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sun, 5 Oct 2025 22:26:04 +0200 Subject: [PATCH 104/192] ESP Flasher: More reliable bootloader mode on SWCLK --nobuild --- CHANGELOG.md | 2 +- applications/external | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae877426e..1d2a50e6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,7 +76,7 @@ - UL: Sub-GHz Remote: Add possibility to use custom buttons (by @MrLego8-9) - Asteroids: Bugfixes, title screen, Drone Buddy power-up (by @SimplyMinimal) - Combo Cracker: Allow press and hold to change values, add tutorial (by @TAxelAnderson), support alphabetic combination locks (by @henrygab) - - ESP Flasher: Bump Marauder 1.8.4 (by @justcallmekoko), add C5 support (by @Play2BReal) + - ESP Flasher: Bump Marauder 1.8.4 (by @justcallmekoko), add C5 support (by @Play2BReal), more reliable bootloader mode on SWCLK (by @WillyJL) - FlipDownloader: Added a new option to download GitHub repositories with dedicated keyboard, add auto updating (by @jblanked) - FlipSocial: C++ rewrite, comments on feed posts, simpler logic and registration (by @jblanked) - FlipWiFi: Minor bugfixes (by @jblanked) diff --git a/applications/external b/applications/external index 48bc1876e..89d493daa 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit 48bc1876e1981e123a378466a8958933220829b8 +Subproject commit 89d493daa1ea0cb86906fa0042c41dd4edc22a6a From c4fcda770ee32a51e9576617104cbd457a95e0ee Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sun, 5 Oct 2025 22:34:04 +0200 Subject: [PATCH 105/192] Fix merge --- targets/f7/api_symbols.csv | 3 --- 1 file changed, 3 deletions(-) diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 77be26224..f2ae0c90d 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -154,11 +154,8 @@ Header,+,lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h,, Header,+,lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h,, Header,+,lib/nfc/protocols/iso14443_4b/iso14443_4b.h,, Header,+,lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h,, -<<<<<<< HEAD Header,+,lib/nfc/protocols/iso15693_3/iso15693_3.h,, Header,+,lib/nfc/protocols/iso15693_3/iso15693_3_listener.h,, -======= ->>>>>>> ofw/dev Header,+,lib/nfc/protocols/iso15693_3/iso15693_3_poller.h,, Header,+,lib/nfc/protocols/mf_classic/mf_classic.h,, Header,+,lib/nfc/protocols/mf_classic/mf_classic_listener.h,, From 9e50da928ee26c1da776f52a4f6dac9780605fe3 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Mon, 6 Oct 2025 01:32:36 +0200 Subject: [PATCH 106/192] Make it fit --- CHANGELOG.md | 2 +- furi/core/check.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c253bff6d..66da20294 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -151,4 +151,4 @@ - OFW: Sub-GHz: Fix TIM17 config not applied immediately (by @Aerosnail) ### Removed: -- Nothing +- Disabled FURI_TRACE due to flash space constraints, `furi_check failed` crashes will no longer show the file path of the error for now diff --git a/furi/core/check.h b/furi/core/check.h index f64bf17d4..dfac95754 100644 --- a/furi/core/check.h +++ b/furi/core/check.h @@ -22,7 +22,7 @@ extern "C" { // When enabled will use file paths instead of "furi_check failed" #if !defined(FURI_RAM_EXEC) && !defined(FURI_DEBUG) -#define __FURI_TRACE +// #define __FURI_TRACE #endif // Flags instead of pointers will save ~4 bytes on furi_assert and furi_check calls. From 7db15e838b087d8b778a88b5b34ad890e09e9708 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Mon, 6 Oct 2025 02:53:30 +0200 Subject: [PATCH 107/192] Storage: Dont send mount event if SD mounted at boot --- applications/services/storage/storage.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/services/storage/storage.c b/applications/services/storage/storage.c index d05ffb635..61851e429 100644 --- a/applications/services/storage/storage.c +++ b/applications/services/storage/storage.c @@ -44,11 +44,11 @@ Storage* storage_app_alloc(void) { storage_ext_init(&app->storage[ST_EXT]); // sd icon gui - app->sd_gui.enabled = false; + app->sd_gui.enabled = (app->storage[ST_EXT].status != StorageStatusNotReady); app->sd_gui.view_port = view_port_alloc(); view_port_set_width(app->sd_gui.view_port, icon_get_width(ICON_SD_MOUNTED)); view_port_draw_callback_set(app->sd_gui.view_port, storage_app_sd_icon_draw_callback, app); - view_port_enabled_set(app->sd_gui.view_port, false); + view_port_enabled_set(app->sd_gui.view_port, app->sd_gui.enabled); Gui* gui = furi_record_open(RECORD_GUI); gui_add_view_port(gui, app->sd_gui.view_port, GuiLayerStatusBarLeft); From 8876423e5dced414899af62d9777de4cd25585fe Mon Sep 17 00:00:00 2001 From: WillyJL Date: Mon, 6 Oct 2025 05:34:35 +0200 Subject: [PATCH 108/192] Format --nobuild --- .../nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index 917142787..5a0c88cf7 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -155,7 +155,8 @@ static NfcCommand // Only set tdes_key for Manual/Reader auth types, not for dictionary attacks if(instance->mf_ul_auth->type == MfUltralightAuthTypeManual || instance->mf_ul_auth->type == MfUltralightAuthTypeReader) { - mf_ultralight_event->data->key_request_data.key = instance->mf_ul_auth->tdes_key; + mf_ultralight_event->data->key_request_data.key = + instance->mf_ul_auth->tdes_key; mf_ultralight_event->data->key_request_data.key_provided = true; } else { mf_ultralight_event->data->key_request_data.key_provided = false; From 93ac669b41794bd2be0932c13f1fa4b7777d6cf6 Mon Sep 17 00:00:00 2001 From: Aaron Tulino Date: Sun, 5 Oct 2025 20:35:29 -0700 Subject: [PATCH 109/192] NFC: Add MFC "Show Keys" UI (#473) * Add MFC "Show Keys" UI * Show Keys button on unlocked cards * Some pzazz * Update changelog --------- Co-authored-by: WillyJL --- CHANGELOG.md | 1 + .../protocol_support/mf_classic/mf_classic.c | 23 ++++- .../main/nfc/scenes/nfc_scene_config.h | 1 + .../scenes/nfc_scene_mf_classic_show_keys.c | 85 +++++++++++++++++++ 4 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_show_keys.c diff --git a/CHANGELOG.md b/CHANGELOG.md index ce868c071..0e11de350 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,7 @@ - OFW: FeliCa Emulation Handle certain Polling commands in firmware (by @dogtopus) - OFW: Amusement IC Card Parser for FeliCa Lite & Lite-S (by @zinongli) - OFW: MFC 1k Banapass Parser (by @zinongli) + - Add MIFARE Classic "Show Keys" UI (#473 by @aaronjamt) - SubGHz: - UL: Roger (static 28 bit) with add manually support (by @xMasterX & @mishamyte) - UL: V2 Phoenix full support (button switch, add manually, counter decrypt/encrypt) (by @xMasterX & @RocketGod-git, original code by @Skorpionm) diff --git a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c index fdf7fb35a..ba5d7c30f 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c +++ b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c @@ -15,6 +15,7 @@ enum { SubmenuIndexDictAttack, SubmenuIndexCrackNonces, SubmenuIndexUpdate, + SubmenuIndexShowKeys, }; static void nfc_scene_info_on_enter_mf_classic(NfcApp* instance) { @@ -138,7 +139,14 @@ static void nfc_scene_read_menu_on_enter_mf_classic(NfcApp* instance) { SubmenuIndexCrackNonces, nfc_protocol_support_common_submenu_callback, instance); - } + } + + submenu_add_item( + submenu, + "Show Keys", + SubmenuIndexShowKeys, + nfc_protocol_support_common_submenu_callback, + instance); } static void nfc_scene_read_success_on_enter_mf_classic(NfcApp* instance) { //-V524 @@ -186,6 +194,13 @@ static void nfc_scene_saved_menu_on_enter_mf_classic(NfcApp* instance) { SubmenuIndexUpdate, nfc_protocol_support_common_submenu_callback, instance); + + submenu_add_item( + submenu, + "Show Keys", + SubmenuIndexShowKeys, + nfc_protocol_support_common_submenu_callback, + instance); } static void nfc_scene_emulate_on_enter_mf_classic(NfcApp* instance) { @@ -218,6 +233,9 @@ static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, SceneManag instance->scene_manager, NfcSceneSaveConfirm, NfcSceneSaveConfirmStateCrackNonces); scene_manager_next_scene(instance->scene_manager, NfcSceneSaveConfirm); consumed = true; + } else if(event.event == SubmenuIndexShowKeys) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicShowKeys); + consumed = true; } } @@ -240,6 +258,9 @@ static bool nfc_scene_saved_menu_on_event_mf_classic(NfcApp* instance, SceneMana } else if(event.event == SubmenuIndexUpdate) { scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicUpdateInitial); consumed = true; + } else if(event.event == SubmenuIndexShowKeys) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicShowKeys); + consumed = true; } } diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index 9761fa9fe..3f4c78598 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -46,6 +46,7 @@ ADD_SCENE(nfc, mf_classic_mfkey_complete, MfClassicMfkeyComplete) ADD_SCENE(nfc, mf_classic_update_initial, MfClassicUpdateInitial) ADD_SCENE(nfc, mf_classic_update_initial_success, MfClassicUpdateInitialSuccess) ADD_SCENE(nfc, mf_classic_update_initial_wrong_card, MfClassicUpdateInitialWrongCard) +ADD_SCENE(nfc, mf_classic_show_keys, MfClassicShowKeys) ADD_SCENE(nfc, mf_classic_keys, MfClassicKeys) ADD_SCENE(nfc, mf_classic_keys_list, MfClassicKeysList) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_show_keys.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_show_keys.c new file mode 100644 index 000000000..9493f2e7a --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_show_keys.c @@ -0,0 +1,85 @@ +#include "../nfc_app_i.h" + +#include +#include + +#define TAG "NfcMfClassicShowKeys" + +void nfc_scene_mf_classic_show_keys_callback(GuiButtonType button, InputType type, void* context) { + NfcApp* instance = context; + if(button == GuiButtonTypeLeft && type == InputTypeShort) { + scene_manager_previous_scene(instance->scene_manager); + } +} + +void nfc_scene_mf_classic_show_keys_on_enter(void* context) { + NfcApp* instance = context; + + const MfClassicData* mfc_data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); + + furi_string_reset(instance->text_box_store); + nfc_append_filename_string_when_present(instance, instance->text_box_store); + + furi_string_cat_printf(instance->text_box_store, "\e#Found MFC Keys:"); + + uint8_t num_sectors = mf_classic_get_total_sectors_num(mfc_data->type); + uint8_t found_keys_a = 0, found_keys_b = 0; + for(uint8_t i = 0; i < num_sectors; i++) { + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(mfc_data, i); + + bool key_a = FURI_BIT(mfc_data->key_a_mask, i); + bool key_b = FURI_BIT(mfc_data->key_b_mask, i); + + if(key_a || key_b) { + furi_string_cat_printf(instance->text_box_store, "\n -> Sector %d\n\e*AccBits:", i); + for(uint8_t j = 0; j < MF_CLASSIC_ACCESS_BYTES_SIZE; j++) { + furi_string_cat_printf( + instance->text_box_store, " %02X", sec_tr->access_bits.data[j]); + } + } + + if(key_a) { + found_keys_a++; + furi_string_cat_printf(instance->text_box_store, "\n\e*A:"); + for(uint8_t j = 0; j < MF_CLASSIC_KEY_SIZE; j++) + furi_string_cat_printf(instance->text_box_store, " %02X", sec_tr->key_a.data[j]); + } + if(key_b) { + found_keys_b++; + furi_string_cat_printf(instance->text_box_store, "\n\e*B:"); + for(uint8_t j = 0; j < MF_CLASSIC_KEY_SIZE; j++) + furi_string_cat_printf(instance->text_box_store, " %02X", sec_tr->key_b.data[j]); + } + } + + furi_string_cat_printf( + instance->text_box_store, + "\nTotal keys found:\n -> %d/%d A keys\n -> %d/%d B keys", + found_keys_a, + num_sectors, + found_keys_b, + num_sectors); + + widget_add_text_scroll_element( + instance->widget, 2, 2, 124, 60, furi_string_get_cstr(instance->text_box_store)); + widget_add_button_element( + instance->widget, + GuiButtonTypeLeft, + "Back", + nfc_scene_mf_classic_show_keys_callback, + instance); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_mf_classic_show_keys_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void nfc_scene_mf_classic_show_keys_on_exit(void* context) { + NfcApp* instance = context; + widget_reset(instance->widget); + furi_string_reset(instance->text_box_store); +} From fe31d0cc12264b750325a39023cf03d8d470080f Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 6 Oct 2025 20:31:39 +0300 Subject: [PATCH 110/192] upd changelog add fix by noproto --- CHANGELOG.md | 1 + .../mf_ultralight/mf_ultralight.c | 17 ++++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 705689ae0..8a74d9b9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * OFW: **JS views finished** * OFW: BLE: improved pairing security * OFW: FeliCa Emulation: Handle certain Polling commands in firmware +* OFW PR 4287: Fix Ultralight EV1 regression (by @noproto) * OFW PR 4271: NFC: **Ultralight C NFC App Key Management, Dictionary Attack** (by @noproto) * OFW PR 4265: NFC: **Fix read crash** with unexpectedly large MFC AUTH(0) response (by @WillyJL) * OFW PR 4251: CLI: **Fix long delay** with quick connect/disconnect (by @WillyJL) diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index aa3cc2e75..5a0c88cf7 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -151,13 +151,16 @@ static NfcCommand if(!mf_ultralight_event->data->auth_context.skip_auth) { mf_ultralight_event->data->auth_context.password = instance->mf_ul_auth->password; - // Only set tdes_key for Manual/Reader auth types, not for dictionary attacks - if(instance->mf_ul_auth->type == MfUltralightAuthTypeManual || - instance->mf_ul_auth->type == MfUltralightAuthTypeReader) { - mf_ultralight_event->data->key_request_data.key = instance->mf_ul_auth->tdes_key; - mf_ultralight_event->data->key_request_data.key_provided = true; - } else { - mf_ultralight_event->data->key_request_data.key_provided = false; + if(data->type == MfUltralightTypeMfulC) { + // Only set tdes_key for Manual/Reader auth types, not for dictionary attacks + if(instance->mf_ul_auth->type == MfUltralightAuthTypeManual || + instance->mf_ul_auth->type == MfUltralightAuthTypeReader) { + mf_ultralight_event->data->key_request_data.key = + instance->mf_ul_auth->tdes_key; + mf_ultralight_event->data->key_request_data.key_provided = true; + } else { + mf_ultralight_event->data->key_request_data.key_provided = false; + } } } } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthSuccess) { From c878aa85b8aa733a93644af4887d47b651ca60f4 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Mon, 6 Oct 2025 19:58:26 +0200 Subject: [PATCH 111/192] API: Catalog now matches release channel not release-candidate --- .github/workflows/build.yml | 6 +++--- targets/f18/api_symbols.csv | 2 +- targets/f7/api_symbols.csv | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ea1ed9549..ac707d41a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -62,14 +62,14 @@ jobs: exit 1 fi - - name: "Check API version for consistency with OFW release-candidate" + - name: "Check API version for consistency with OFW release channel" run: | set -e symbols="targets/$TARGET/api_symbols.csv" - ofw_api="$(curl -s "https://raw.githubusercontent.com/flipperdevices/flipperzero-firmware/release-candidate/${symbols}" | head -n2)" + ofw_api="$(curl -s "https://raw.githubusercontent.com/flipperdevices/flipperzero-firmware/release/${symbols}" | head -n2)" our_api="$(head -n2 "${symbols}")" if [ "$our_api" != "$ofw_api" ] ; then - echo API versions aren\'t matching OFW. Please update! + echo API versions aren\'t matching OFW release channel. Please update! echo API versions are: echo "Official: $(tail -n1 <<< "$ofw_api")" echo "Momentum: $(tail -n1 <<< "$our_api")" diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index e3f5922ff..5f8718b47 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,87.0,, +Version,+,86.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index f77926f0b..d0242cd70 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,87.0,, +Version,+,86.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/main/archive/helpers/archive_helpers_ext.h,, Header,+,applications/main/subghz/subghz_fap.h,, From 930efdbb541bf999b19054ba3c7989353fcf7a23 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Mon, 6 Oct 2025 20:00:24 +0200 Subject: [PATCH 112/192] Format --nobuild --- .../main/nfc/helpers/protocol_support/mf_classic/mf_classic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c index ba5d7c30f..53218bb9f 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c +++ b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c @@ -139,7 +139,7 @@ static void nfc_scene_read_menu_on_enter_mf_classic(NfcApp* instance) { SubmenuIndexCrackNonces, nfc_protocol_support_common_submenu_callback, instance); - } + } submenu_add_item( submenu, From 9001aaa4ae7f94f463e6b08a37698a872d6bd6ee Mon Sep 17 00:00:00 2001 From: WillyJL Date: Mon, 6 Oct 2025 21:57:30 +0200 Subject: [PATCH 113/192] VSCode: Reduce file watcher resource usage --- .vscode/example/settings.json.tmpl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.vscode/example/settings.json.tmpl b/.vscode/example/settings.json.tmpl index b8e9f81cd..06528ce8e 100644 --- a/.vscode/example/settings.json.tmpl +++ b/.vscode/example/settings.json.tmpl @@ -19,5 +19,13 @@ "--compile-commands-dir=${workspaceFolder}/build/latest", "--clang-tidy", "--header-insertion=never" - ] + ], + "files.watcherExclude": { + "**/.git/objects/**": true, + "**/.git/**/modules/**/objects/**": true, + "**/.git/**/subtree-cache/**": true, + "**/.git/**/rr-cache/**": true, + "build/**": true, + "toolchain/**": true + } } \ No newline at end of file From 8ae710edecc2538ef5137c132d9123794331eda5 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Wed, 8 Oct 2025 13:13:36 +0200 Subject: [PATCH 114/192] VSCode: Reduce file watcher resource usage (#4292) --- .vscode/example/settings.json.tmpl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.vscode/example/settings.json.tmpl b/.vscode/example/settings.json.tmpl index b8e9f81cd..06528ce8e 100644 --- a/.vscode/example/settings.json.tmpl +++ b/.vscode/example/settings.json.tmpl @@ -19,5 +19,13 @@ "--compile-commands-dir=${workspaceFolder}/build/latest", "--clang-tidy", "--header-insertion=never" - ] + ], + "files.watcherExclude": { + "**/.git/objects/**": true, + "**/.git/**/modules/**/objects/**": true, + "**/.git/**/subtree-cache/**": true, + "**/.git/**/rr-cache/**": true, + "build/**": true, + "toolchain/**": true + } } \ No newline at end of file From 468cc45f90757b15a05ae804699a29b45dd5246f Mon Sep 17 00:00:00 2001 From: RebornedBrain Date: Wed, 8 Oct 2025 16:26:48 +0300 Subject: [PATCH 115/192] Fix wrbl command tooltip (#4291) Co-authored-by: hedger --- applications/main/nfc/cli/commands/mfu/nfc_cli_command_mfu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/nfc/cli/commands/mfu/nfc_cli_command_mfu.c b/applications/main/nfc/cli/commands/mfu/nfc_cli_command_mfu.c index 0bfcffe3c..99e51edcf 100644 --- a/applications/main/nfc/cli/commands/mfu/nfc_cli_command_mfu.c +++ b/applications/main/nfc/cli/commands/mfu/nfc_cli_command_mfu.c @@ -59,7 +59,7 @@ const NfcCliKeyDescriptor wrbl_action_keys[] = { //mfu rdbl --block 0 -- data DEADBEEF const NfcCliActionDescriptor wrbl_action = { .name = "wrbl", - .description = "Read block from ultralight card", + .description = "Write block to ultralight card", .alloc = nfc_cli_mfu_wrbl_alloc_ctx, .free = nfc_cli_mfu_wrbl_free_ctx, .execute = nfc_cli_mfu_wrbl_execute, From 4d2a08cc1126798356d343e18e2b595f19a81a20 Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Sat, 11 Oct 2025 11:27:31 +0300 Subject: [PATCH 116/192] HID PTT: adding global zoom shortcuts for macos --- applications/system/hid_app/views/hid_ptt.c | 57 +++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index 58599eb51..a0a313e75 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -54,6 +54,7 @@ enum HidPushToTalkAppIndex { HidPushToTalkAppIndexTeamSpeak, HidPushToTalkAppIndexWebex, HidPushToTalkAppIndexZoom, + HidPushToTalkAppIndexZoomGlobal, HidPushToTalkAppIndexSize, }; @@ -109,6 +110,40 @@ static void hid_ptt_trigger_hand_zoom(HidPushToTalk* hid_ptt) { hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_Y); } +// zoom global macos +static void hid_ptt_trigger_mute_macos_zoom_global(HidPushToTalk* hid_ptt) { + hid_hal_keyboard_press( + hid_ptt->hid, + KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | + HID_KEYBOARD_M); + hid_hal_keyboard_release( + hid_ptt->hid, + KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | + HID_KEYBOARD_M); +} + +static void hid_ptt_trigger_camera_macos_zoom_global(HidPushToTalk* hid_ptt) { + hid_hal_keyboard_press( + hid_ptt->hid, + KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | + HID_KEYBOARD_U); + hid_hal_keyboard_release( + hid_ptt->hid, + KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | + HID_KEYBOARD_U); +} + +static void hid_ptt_trigger_hand_zoom_global(HidPushToTalk* hid_ptt) { + hid_hal_keyboard_press( + hid_ptt->hid, + KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | + HID_KEYBOARD_Y); + hid_hal_keyboard_release( + hid_ptt->hid, + KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | + HID_KEYBOARD_Y); +} + // this one is widely used across different apps static void hid_ptt_trigger_cmd_shift_m(HidPushToTalk* hid_ptt) { hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); @@ -457,6 +492,13 @@ static void hid_ptt_menu_callback( model->callback_start_ptt = hid_ptt_start_ptt_meet_zoom; model->callback_stop_ptt = hid_ptt_stop_ptt_meet_zoom; break; + case HidPushToTalkAppIndexZoomGlobal: + model->callback_trigger_mute = hid_ptt_trigger_mute_macos_zoom_global; + model->callback_trigger_camera = hid_ptt_trigger_camera_macos_zoom_global; + model->callback_trigger_hand = hid_ptt_trigger_hand_zoom_global; + model->callback_start_ptt = hid_ptt_trigger_mute_macos_zoom_global; + model->callback_stop_ptt = hid_ptt_trigger_mute_macos_zoom_global; + break; } } else if(osIndex == HidPushToTalkLinux) { switch(appIndex) { @@ -569,6 +611,14 @@ static void hid_ptt_menu_callback( "Teams:\n" "Go to Settings > Privacy. Make sure Keyboard shortcut to unmute is toggled on.\n\n"; break; + case HidPushToTalkAppIndexZoomGlobal: + app_specific_help = + "Zoom (Global):\n" + "1. Go to Settings > Keyboard Shortcuts.\n" + "2. Find the 'Mute/Unmute' shortcut and click 'Edit'.\n" + "3. Press the Mute button in the app to bind it.\n" + "4. Check global checkbox.\n" + "5. Repeat for video and hand shortcuts.\n\n"; } FuriString* msg = furi_string_alloc(); @@ -1030,6 +1080,13 @@ HidPushToTalk* hid_ptt_alloc(Hid* hid) { HidPushToTalkAppIndexZoom, hid_ptt_menu_callback, hid_ptt); + ptt_menu_add_item_to_list( + hid->hid_ptt_menu, + HidPushToTalkMacOS, + "Zoom Global", + HidPushToTalkAppIndexZoomGlobal, + hid_ptt_menu_callback, + hid_ptt); ptt_menu_add_item_to_list( hid->hid_ptt_menu, HidPushToTalkLinux, From df035ad51cb09d83b894648f85e000fa08532919 Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Sat, 11 Oct 2025 15:50:05 +0300 Subject: [PATCH 117/192] HID PTT: adding global meet shortcuts for macos --- applications/system/hid_app/views/hid_ptt.c | 50 +++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index a0a313e75..ff57c6bb7 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -44,6 +44,7 @@ enum HidPushToTalkAppIndex { HidPushToTalkAppIndexFaceTime, HidPushToTalkAppIndexGather, HidPushToTalkAppIndexGoogleMeet, + HidPushToTalkAppIndexGoogleMeetGlobal, HidPushToTalkAppIndexGoogleHangouts, HidPushToTalkAppIndexJamulus, HidPushToTalkAppIndexSignal, @@ -89,6 +90,32 @@ static void hid_ptt_trigger_hand_linux_meet(HidPushToTalk* hid_ptt) { hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT | HID_KEYBOARD_H); hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT | HID_KEYBOARD_H); } + +// meet global macos +static void hid_ptt_trigger_mute_macos_meet_global(HidPushToTalk* hid_ptt) { + hid_hal_keyboard_press( + hid_ptt->hid, + KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL | HID_KEYBOARD_7); + hid_hal_keyboard_release( + hid_ptt->hid, + KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL | HID_KEYBOARD_7); +} +static void hid_ptt_trigger_camera_macos_meet_global(HidPushToTalk* hid_ptt) { + hid_hal_keyboard_press( + hid_ptt->hid, + KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL | HID_KEYBOARD_8); + hid_hal_keyboard_release( + hid_ptt->hid, + KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL | HID_KEYBOARD_8); +} +static void hid_ptt_trigger_hand_macos_meet_global(HidPushToTalk* hid_ptt) { + hid_hal_keyboard_press( + hid_ptt->hid, + KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL | HID_KEYBOARD_9); + hid_hal_keyboard_release( + hid_ptt->hid, + KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL | HID_KEYBOARD_9); +} static void hid_ptt_trigger_mute_macos_zoom(HidPushToTalk* hid_ptt) { hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_A); hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_A); @@ -438,6 +465,13 @@ static void hid_ptt_menu_callback( model->callback_start_ptt = hid_ptt_start_ptt_meet_zoom; model->callback_stop_ptt = hid_ptt_stop_ptt_meet_zoom; break; + case HidPushToTalkAppIndexGoogleMeetGlobal: + model->callback_trigger_mute = hid_ptt_trigger_mute_macos_meet_global; + model->callback_trigger_camera = hid_ptt_trigger_camera_macos_meet_global; + model->callback_trigger_hand = hid_ptt_trigger_hand_macos_meet_global; + model->callback_start_ptt = hid_ptt_trigger_mute_macos_meet_global; + model->callback_stop_ptt = hid_ptt_trigger_mute_macos_meet_global; + break; case HidPushToTalkAppIndexJamulus: model->callback_trigger_mute = hid_ptt_trigger_mute_jamulus; model->callback_start_ptt = hid_ptt_trigger_mute_jamulus; @@ -593,6 +627,15 @@ static void hid_ptt_menu_callback( "and may not work for Windows users who use their screen " "reader. In this situation, the spacebar performs a different action.\n\n"; break; + case HidPushToTalkAppIndexGoogleMeetGlobal: + app_specific_help = + "Google Meet (Global):\n" + "1. Install \"Google Meet - Global Shortcuts\" extension.\n" + "2. Open chrome://extensions/shortcuts.\n" + "3. Set 'Toggle microphone' to Cmd+Ctrl+7 and enable Global.\n" + "4. Set 'Toggle camera' to Cmd+Ctrl+8 and enable Global.\n" + "5. Set 'Raise hand' to Cmd+Ctrl+9 and enable Global.\n\n"; + break; case HidPushToTalkAppIndexDiscord: app_specific_help = "Discord:\n" @@ -926,6 +969,13 @@ HidPushToTalk* hid_ptt_alloc(Hid* hid) { HidPushToTalkAppIndexGoogleMeet, hid_ptt_menu_callback, hid_ptt); + ptt_menu_add_item_to_list( + hid->hid_ptt_menu, + HidPushToTalkMacOS, + "Google Meet Global", + HidPushToTalkAppIndexGoogleMeetGlobal, + hid_ptt_menu_callback, + hid_ptt); ptt_menu_add_item_to_list( hid->hid_ptt_menu, HidPushToTalkMacOS, From b9feece2c2d38c9fd8479c0cff8662a3cf3e46eb Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 12 Oct 2025 03:28:58 +0300 Subject: [PATCH 118/192] ofw pr 4283 NFC lib: Expose nfc_common.h by zinongli --- lib/nfc/SConscript | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index 737a356e2..787af9e43 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -14,6 +14,7 @@ env.Append( File("nfc_listener.h"), File("nfc_poller.h"), File("nfc_scanner.h"), + File("nfc_common.h"), # Protocols File("protocols/iso14443_3a/iso14443_3a.h"), File("protocols/iso14443_3b/iso14443_3b.h"), From de35de4e5832eb0166dc3ee35b69c839f7c9c7e2 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 12 Oct 2025 03:30:31 +0300 Subject: [PATCH 119/192] ofw pr 4290 Storage: Dont send mount event if SD mounted at boot by WillyJL --- applications/services/storage/storage.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/services/storage/storage.c b/applications/services/storage/storage.c index bfe2a08b2..9ab81eda6 100644 --- a/applications/services/storage/storage.c +++ b/applications/services/storage/storage.c @@ -44,11 +44,11 @@ Storage* storage_app_alloc(void) { storage_ext_init(&app->storage[ST_EXT]); // sd icon gui - app->sd_gui.enabled = false; + app->sd_gui.enabled = (app->storage[ST_EXT].status != StorageStatusNotReady); app->sd_gui.view_port = view_port_alloc(); view_port_set_width(app->sd_gui.view_port, icon_get_width(ICON_SD_MOUNTED)); view_port_draw_callback_set(app->sd_gui.view_port, storage_app_sd_icon_draw_callback, app); - view_port_enabled_set(app->sd_gui.view_port, false); + view_port_enabled_set(app->sd_gui.view_port, app->sd_gui.enabled); Gui* gui = furi_record_open(RECORD_GUI); gui_add_view_port(gui, app->sd_gui.view_port, GuiLayerStatusBarLeft); From fc34205f97c35079bc0746a0207b538d2afd7441 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 12 Oct 2025 03:34:40 +0300 Subject: [PATCH 120/192] ofw pr 4285 ViewStack: Store View by value to save memory by CookiePLMonster --- applications/services/gui/view.c | 6 ++- applications/services/gui/view_i.h | 3 ++ applications/services/gui/view_stack.c | 51 ++++++++++++-------------- 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/applications/services/gui/view.c b/applications/services/gui/view.c index 3ac85794a..ded1eb8de 100644 --- a/applications/services/gui/view.c +++ b/applications/services/gui/view.c @@ -2,7 +2,7 @@ View* view_alloc(void) { View* view = malloc(sizeof(View)); - view->orientation = ViewOrientationHorizontal; + view_init(view); return view; } @@ -12,6 +12,10 @@ void view_free(View* view) { free(view); } +void view_init(View* view) { + view->orientation = ViewOrientationHorizontal; +} + void view_tie_icon_animation(View* view, IconAnimation* icon_animation) { furi_check(view); icon_animation_set_update_callback(icon_animation, view_icon_animation_callback, view); diff --git a/applications/services/gui/view_i.h b/applications/services/gui/view_i.h index 1cc84c745..bd4ea16bd 100644 --- a/applications/services/gui/view_i.h +++ b/applications/services/gui/view_i.h @@ -31,6 +31,9 @@ struct View { void* context; }; +/** Initialize View (for internal use) */ +void view_init(View* view); + /** IconAnimation tie callback */ void view_icon_animation_callback(IconAnimation* instance, void* context); diff --git a/applications/services/gui/view_stack.c b/applications/services/gui/view_stack.c index 0a106f1ba..5359a62b9 100644 --- a/applications/services/gui/view_stack.c +++ b/applications/services/gui/view_stack.c @@ -1,9 +1,6 @@ #include "view_stack.h" #include "view_i.h" -#include -#include - #define MAX_VIEWS 3 typedef struct { @@ -11,7 +8,7 @@ typedef struct { } ViewStackModel; struct ViewStack { - View* view; + View view; }; static void view_stack_draw(Canvas* canvas, void* model); @@ -32,26 +29,26 @@ static void view_stack_enter(void* context) { furi_assert(context); ViewStack* view_stack = context; - ViewStackModel* model = view_get_model(view_stack->view); + ViewStackModel* model = view_get_model(&view_stack->view); /* if more than 1 Stack View hold same view they have to reassign update_callback_context */ for(int i = 0; i < MAX_VIEWS; ++i) { if(model->views[i]) { - view_set_update_callback_context(model->views[i], view_stack->view); + view_set_update_callback_context(model->views[i], &view_stack->view); if(model->views[i]->enter_callback) { model->views[i]->enter_callback(model->views[i]->context); } } } - view_commit_model(view_stack->view, false); + view_commit_model(&view_stack->view, false); } static void view_stack_exit(void* context) { furi_assert(context); ViewStack* view_stack = context; - ViewStackModel* model = view_get_model(view_stack->view); + ViewStackModel* model = view_get_model(&view_stack->view); for(int i = 0; i < MAX_VIEWS; ++i) { if(model->views[i] && model->views[i]->exit_callback) { @@ -59,35 +56,35 @@ static void view_stack_exit(void* context) { } } - view_commit_model(view_stack->view, false); + view_commit_model(&view_stack->view, false); } ViewStack* view_stack_alloc(void) { ViewStack* view_stack = malloc(sizeof(ViewStack)); - view_stack->view = view_alloc(); + view_init(&view_stack->view); - view_allocate_model(view_stack->view, ViewModelTypeLocking, sizeof(ViewStackModel)); - view_set_draw_callback(view_stack->view, view_stack_draw); - view_set_input_callback(view_stack->view, view_stack_input); - view_set_context(view_stack->view, view_stack); - view_set_enter_callback(view_stack->view, view_stack_enter); - view_set_exit_callback(view_stack->view, view_stack_exit); + view_allocate_model(&view_stack->view, ViewModelTypeLocking, sizeof(ViewStackModel)); + view_set_draw_callback(&view_stack->view, view_stack_draw); + view_set_input_callback(&view_stack->view, view_stack_input); + view_set_context(&view_stack->view, view_stack); + view_set_enter_callback(&view_stack->view, view_stack_enter); + view_set_exit_callback(&view_stack->view, view_stack_exit); return view_stack; } void view_stack_free(ViewStack* view_stack) { furi_assert(view_stack); - ViewStackModel* model = view_get_model(view_stack->view); + ViewStackModel* model = view_get_model(&view_stack->view); for(int i = 0; i < MAX_VIEWS; ++i) { if(model->views[i]) { view_set_update_callback(model->views[i], NULL); view_set_update_callback_context(model->views[i], NULL); } } - view_commit_model(view_stack->view, false); + view_commit_model(&view_stack->view, false); - view_free(view_stack->view); + view_free_model(&view_stack->view); free(view_stack); } @@ -109,14 +106,14 @@ static bool view_stack_input(InputEvent* event, void* context) { ViewStack* view_stack = context; bool consumed = false; - ViewStackModel* model = view_get_model(view_stack->view); + ViewStackModel* model = view_get_model(&view_stack->view); for(int i = MAX_VIEWS - 1; i >= 0; i--) { if(model->views[i] && view_input(model->views[i], event)) { consumed = true; break; } } - view_commit_model(view_stack->view, false); + view_commit_model(&view_stack->view, false); return consumed; } @@ -126,12 +123,12 @@ void view_stack_add_view(ViewStack* view_stack, View* view) { furi_assert(view); bool result = false; - ViewStackModel* model = view_get_model(view_stack->view); + ViewStackModel* model = view_get_model(&view_stack->view); for(int i = 0; i < MAX_VIEWS; ++i) { if(!model->views[i]) { model->views[i] = view; view_set_update_callback(model->views[i], view_stack_update_callback); - view_set_update_callback_context(model->views[i], view_stack->view); + view_set_update_callback_context(model->views[i], &view_stack->view); if(view->enter_callback) { view->enter_callback(view->context); } @@ -139,7 +136,7 @@ void view_stack_add_view(ViewStack* view_stack, View* view) { break; } } - view_commit_model(view_stack->view, result); + view_commit_model(&view_stack->view, result); furi_assert(result); } @@ -150,7 +147,7 @@ void view_stack_remove_view(ViewStack* view_stack, View* view) { /* Removing view on-the-go is dangerous, but it is protected with * Locking model, so system is consistent at any time. */ bool result = false; - ViewStackModel* model = view_get_model(view_stack->view); + ViewStackModel* model = view_get_model(&view_stack->view); for(int i = 0; i < MAX_VIEWS; ++i) { if(model->views[i] == view) { if(view->exit_callback) { @@ -163,11 +160,11 @@ void view_stack_remove_view(ViewStack* view_stack, View* view) { break; } } - view_commit_model(view_stack->view, result); + view_commit_model(&view_stack->view, result); furi_assert(result); } View* view_stack_get_view(ViewStack* view_stack) { furi_assert(view_stack); - return view_stack->view; + return &view_stack->view; } From eed1d3367a5058f174614ceea01ee906b58b0795 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 12 Oct 2025 03:39:38 +0300 Subject: [PATCH 121/192] ofw pr 4293 NFC FeliCa Improvement: Dump All Systems by zinongli --- .../helpers/protocol_support/felica/felica.c | 44 +- .../protocol_support/felica/felica_render.c | 23 +- .../protocol_support/felica/felica_render.h | 3 +- .../main/nfc/scenes/nfc_scene_config.h | 1 + .../nfc/scenes/nfc_scene_felica_more_info.c | 84 +--- .../main/nfc/scenes/nfc_scene_felica_system.c | 114 +++++ lib/nfc/protocols/felica/felica.c | 419 +++++++++--------- lib/nfc/protocols/felica/felica.h | 27 +- lib/nfc/protocols/felica/felica_i.c | 38 ++ lib/nfc/protocols/felica/felica_i.h | 5 + lib/nfc/protocols/felica/felica_poller.c | 91 +++- lib/nfc/protocols/felica/felica_poller_i.c | 32 +- lib/nfc/protocols/felica/felica_poller_i.h | 8 + targets/f7/api_symbols.csv | 5 +- 14 files changed, 569 insertions(+), 325 deletions(-) create mode 100644 applications/main/nfc/scenes/nfc_scene_felica_system.c diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica.c b/applications/main/nfc/helpers/protocol_support/felica/felica.c index 87c69722a..1b52b86bd 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica.c +++ b/applications/main/nfc/helpers/protocol_support/felica/felica.c @@ -102,19 +102,21 @@ static void nfc_scene_read_success_on_enter_felica(NfcApp* instance) { temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); nfc_render_felica_info(data, NfcProtocolFormatTypeShort, temp_str); } else { - bool all_unlocked = data->blocks_read == data->blocks_total; - furi_string_cat_printf( - temp_str, - "\e#%s\n", - all_unlocked ? "All Blocks Are Unlocked" : "Some Blocks Are Locked"); - nfc_render_felica_idm(data, NfcProtocolFormatTypeShort, temp_str); - uint8_t* ck_data = instance->felica_auth->card_key.data; - furi_string_cat_printf(temp_str, "Key:"); - for(uint8_t i = 0; i < 7; i++) { - furi_string_cat_printf(temp_str, " %02X", ck_data[i]); - if(i == 6) furi_string_cat_printf(temp_str, "..."); + if(data->workflow_type == FelicaLite) { + bool all_unlocked = data->blocks_read == data->blocks_total; + furi_string_cat_printf( + temp_str, + "\e#%s\n", + all_unlocked ? "All Blocks Are Unlocked" : "Some Blocks Are Locked"); + nfc_render_felica_idm(data, NfcProtocolFormatTypeShort, temp_str); + uint8_t* ck_data = instance->felica_auth->card_key.data; + furi_string_cat_printf(temp_str, "Key:"); + for(uint8_t i = 0; i < 7; i++) { + furi_string_cat_printf(temp_str, " %02X", ck_data[i]); + if(i == 6) furi_string_cat_printf(temp_str, "..."); + } + nfc_render_felica_blocks_count(data, temp_str, false); } - nfc_render_felica_blocks_count(data, temp_str, false); } felica_auth_reset(instance->felica_auth); @@ -124,6 +126,15 @@ static void nfc_scene_read_success_on_enter_felica(NfcApp* instance) { furi_string_free(temp_str); } +static bool nfc_scene_saved_menu_on_event_felica(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); + return true; + } + + return false; +} + static void nfc_scene_emulate_on_enter_felica(NfcApp* instance) { const FelicaData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolFelica); instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolFelica, data); @@ -183,7 +194,7 @@ const NfcProtocolSupportBase nfc_protocol_support_felica = { .scene_saved_menu = { .on_enter = nfc_protocol_support_common_on_enter_empty, - .on_event = nfc_protocol_support_common_on_event_empty, + .on_event = nfc_scene_saved_menu_on_event_felica, }, .scene_save_name = { @@ -195,11 +206,4 @@ const NfcProtocolSupportBase nfc_protocol_support_felica = { .on_enter = nfc_scene_emulate_on_enter_felica, .on_event = nfc_protocol_support_common_on_event_empty, }, - .scene_write = - { - .on_enter = nfc_protocol_support_common_on_enter_empty, - .on_event = nfc_protocol_support_common_on_event_empty, - }, }; - -NFC_PROTOCOL_SUPPORT_PLUGIN(felica, NfcProtocolFelica); diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica_render.c b/applications/main/nfc/helpers/protocol_support/felica/felica_render.c index 1ca992bcd..8773fa1f3 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica_render.c +++ b/applications/main/nfc/helpers/protocol_support/felica/felica_render.c @@ -6,14 +6,10 @@ void nfc_render_felica_blocks_count( bool render_auth_notification) { if(data->workflow_type == FelicaLite) { furi_string_cat_printf(str, "Blocks: %u\n", data->blocks_total); - furi_string_cat_printf(str, "\nBlocks Read: %u/%u", data->blocks_read, data->blocks_total); if(render_auth_notification && data->blocks_read != data->blocks_total) { furi_string_cat_printf(str, "\nAuth-protected blocks!"); } - } else if(data->workflow_type == FelicaStandard) { - furi_string_cat_printf( - str, "Public blocks Read: %lu", simple_array_get_count(data->public_blocks)); } } @@ -54,11 +50,7 @@ void nfc_render_felica_info( } furi_string_cat_printf(str, "\n"); - furi_string_cat_printf( - str, - "Services found: %lu \nAreas found: %lu\n", - simple_array_get_count(data->services), - simple_array_get_count(data->areas)); + furi_string_cat_printf(str, "Systems found: %lu \n", simple_array_get_count(data->systems)); nfc_render_felica_blocks_count(data, str, true); } @@ -136,9 +128,9 @@ void nfc_more_info_render_felica_lite_dump(const FelicaData* data, FuriString* s nfc_render_felica_block(&data->data.fs.crc_check, str, "CRC_CHCK", 15, 17); } -void nfc_more_info_render_felica_dir(const FelicaData* data, FuriString* str) { - const size_t area_count = simple_array_get_count(data->areas); - const size_t service_count = simple_array_get_count(data->services); +void nfc_more_info_render_felica_dir(const FelicaSystem* system, FuriString* str) { + const size_t area_count = simple_array_get_count(system->areas); + const size_t service_count = simple_array_get_count(system->services); furi_string_cat_printf(str, "\e#Directory Tree:\n"); @@ -150,11 +142,12 @@ void nfc_more_info_render_felica_dir(const FelicaData* data, FuriString* str) { furi_string_cat_printf( str, "::: ... are readable services\n||| ... are locked services\n"); } - felica_write_directory_tree(data, str); + felica_write_directory_tree(system, str); } void nfc_more_info_render_felica_blocks( const FelicaData* data, + const FelicaSystem* system, FuriString* str, const uint16_t service_code_key) { furi_string_cat_printf(str, "\n"); @@ -190,9 +183,9 @@ void nfc_more_info_render_felica_blocks( nfc_render_felica_block(&data->data.fs.crc_check, str, "CRC_CHCK", 15, 17); } else if(data->workflow_type == FelicaStandard) { - uint32_t public_blocks_count = simple_array_get_count(data->public_blocks); + uint32_t public_blocks_count = simple_array_get_count(system->public_blocks); for(size_t i = 0; i < public_blocks_count; i++) { - FelicaPublicBlock* public_block = simple_array_get(data->public_blocks, i); + FelicaPublicBlock* public_block = simple_array_get(system->public_blocks, i); if(public_block->service_code != service_code_key) { continue; // Skip blocks not matching the requested service code } diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica_render.h b/applications/main/nfc/helpers/protocol_support/felica/felica_render.h index 5c7c6d036..e0c270a18 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica_render.h +++ b/applications/main/nfc/helpers/protocol_support/felica/felica_render.h @@ -21,9 +21,10 @@ void nfc_render_felica_idm( NfcProtocolFormatType format_type, FuriString* str); -void nfc_more_info_render_felica_dir(const FelicaData* data, FuriString* str); +void nfc_more_info_render_felica_dir(const FelicaSystem* system, FuriString* str); void nfc_more_info_render_felica_blocks( const FelicaData* data, + const FelicaSystem* system, FuriString* str, const uint16_t service_code_key); diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index 9761fa9fe..6634827ed 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -70,5 +70,6 @@ ADD_SCENE(nfc, slix_unlock, SlixUnlock) ADD_SCENE(nfc, slix_unlock_success, SlixUnlockSuccess) ADD_SCENE(nfc, felica_more_info, FelicaMoreInfo) +ADD_SCENE(nfc, felica_system, FelicaSystem) ADD_SCENE(nfc, generate_info, GenerateInfo) diff --git a/applications/main/nfc/scenes/nfc_scene_felica_more_info.c b/applications/main/nfc/scenes/nfc_scene_felica_more_info.c index 7eee3d7b3..1401dcaf7 100644 --- a/applications/main/nfc/scenes/nfc_scene_felica_more_info.c +++ b/applications/main/nfc/scenes/nfc_scene_felica_more_info.c @@ -9,7 +9,6 @@ enum { }; enum SubmenuIndex { - SubmenuIndexDirectory, SubmenuIndexDynamic, // dynamic indices start here }; @@ -21,33 +20,22 @@ void nfc_scene_felica_more_info_on_enter(void* context) { scene_manager_get_scene_state(nfc->scene_manager, NfcSceneFelicaMoreInfo); const FelicaData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolFelica); - submenu_add_item( - submenu, - "Directory", - SubmenuIndexDirectory, - nfc_protocol_support_common_submenu_callback, - nfc); - - FuriString* label = furi_string_alloc(); - switch(data->workflow_type) { case FelicaLite: - furi_string_printf(label, "All blocks"); - submenu_add_item( - submenu, - furi_string_get_cstr(label), - SubmenuIndexDynamic, - nfc_protocol_support_common_submenu_callback, - nfc); + widget_reset(nfc->widget); + FuriString* temp_str = furi_string_alloc(); + nfc_more_info_render_felica_lite_dump(data, temp_str); + widget_add_text_scroll_element(nfc->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + return; break; case FelicaStandard: - for(uint32_t i = 0; i < simple_array_get_count(data->services); ++i) { - const FelicaService* service = simple_array_cget(data->services, i); - bool is_public = (service->attr & FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ) == 1; - if(!is_public) { - continue; - } - furi_string_printf(label, "Readable serv %04X", service->code); + FuriString* label = furi_string_alloc(); + + for(uint32_t i = 0; i < simple_array_get_count(data->systems); ++i) { + const FelicaSystem* system = simple_array_cget(data->systems, i); + furi_string_printf(label, "System %04X", system->system_code); submenu_add_item( submenu, furi_string_get_cstr(label), @@ -55,13 +43,12 @@ void nfc_scene_felica_more_info_on_enter(void* context) { nfc_protocol_support_common_submenu_callback, nfc); } + furi_string_free(label); break; default: break; } - furi_string_free(label); - if(state >= FelicaMoreInfoStateItem) { submenu_set_selected_item( nfc->submenu, state - FelicaMoreInfoStateItem + SubmenuIndexDynamic); @@ -78,49 +65,16 @@ bool nfc_scene_felica_more_info_on_event(void* context, SceneManagerEvent event) const uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneFelicaMoreInfo); - const FelicaData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolFelica); if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexDirectory) { - FuriString* temp_str = furi_string_alloc(); - nfc_more_info_render_felica_dir(data, temp_str); + const uint32_t index = event.event - SubmenuIndexDynamic; - widget_add_text_scroll_element( - nfc->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); - furi_string_free(temp_str); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneFelicaMoreInfo, FelicaMoreInfoStateItem + index); + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneFelicaSystem, index << 4); + scene_manager_next_scene(nfc->scene_manager, NfcSceneFelicaSystem); + consumed = true; - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); - scene_manager_set_scene_state( - nfc->scene_manager, - NfcSceneFelicaMoreInfo, - FelicaMoreInfoStateItem + SubmenuIndexDirectory); - consumed = true; - } else { - const uint16_t service_ind = event.event - 1; // offset the three enums above - - text_box_reset(nfc->text_box); - furi_string_reset(nfc->text_box_store); - - switch(data->workflow_type) { - case FelicaLite: - nfc_more_info_render_felica_lite_dump(data, nfc->text_box_store); - break; - case FelicaStandard: - const FelicaService* service = simple_array_cget(data->services, service_ind); - furi_string_cat_printf(nfc->text_box_store, "Service 0x%04X\n", service->code); - nfc_more_info_render_felica_blocks(data, nfc->text_box_store, service->code); - break; - default: - furi_string_set_str(nfc->text_box_store, "IC type not implemented yet"); - break; - } - text_box_set_font(nfc->text_box, TextBoxFontHex); - text_box_set_text(nfc->text_box, furi_string_get_cstr(nfc->text_box_store)); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneFelicaMoreInfo, FelicaMoreInfoStateItem + event.event); - consumed = true; - } } else if(event.type == SceneManagerEventTypeBack) { if(state >= FelicaMoreInfoStateItem) { widget_reset(nfc->widget); diff --git a/applications/main/nfc/scenes/nfc_scene_felica_system.c b/applications/main/nfc/scenes/nfc_scene_felica_system.c new file mode 100644 index 000000000..87ebbbc36 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_felica_system.c @@ -0,0 +1,114 @@ +#include "../nfc_app_i.h" + +#include "../helpers/protocol_support/nfc_protocol_support_gui_common.h" +#include "../helpers/protocol_support/felica/felica_render.h" + +enum SubmenuIndex { + SubmenuIndexDirectory, + SubmenuIndexDynamic, // dynamic indices start here +}; + +static void nfc_scene_felica_system_submenu_callback(void* context, uint32_t index) { + NfcApp* nfc = context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, index); +} + +void nfc_scene_felica_system_on_enter(void* context) { + NfcApp* nfc = context; + Submenu* submenu = nfc->submenu; + submenu_reset(nfc->submenu); + + const uint32_t system_index = + scene_manager_get_scene_state(nfc->scene_manager, NfcSceneFelicaSystem) >> 4; + const FelicaData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolFelica); + + submenu_add_item( + submenu, "Directory", SubmenuIndexDirectory, nfc_scene_felica_system_submenu_callback, nfc); + + FuriString* label = furi_string_alloc(); + + const FelicaSystem* system = simple_array_cget(data->systems, system_index); + for(uint32_t i = 0; i < simple_array_get_count(system->services); ++i) { + const FelicaService* service = simple_array_cget(system->services, i); + bool is_public = (service->attr & FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ) == 1; + if(!is_public) { + continue; + } + furi_string_printf(label, "Readable serv %04X", service->code); + submenu_add_item( + submenu, + furi_string_get_cstr(label), + i + SubmenuIndexDynamic, + nfc_protocol_support_common_submenu_callback, + nfc); + } + + furi_string_free(label); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_felica_system_on_event(void* context, SceneManagerEvent event) { + NfcApp* nfc = context; + bool consumed = false; + + const uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneFelicaSystem); + const FelicaData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolFelica); + + const uint32_t system_index = state >> 4; + const FelicaSystem* system = simple_array_cget(data->systems, system_index); + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventViewExit) { + consumed = scene_manager_previous_scene(nfc->scene_manager); + } else { + if(event.event == SubmenuIndexDirectory) { + FuriString* temp_str = furi_string_alloc(); + nfc_more_info_render_felica_dir(system, temp_str); + + widget_add_text_scroll_element( + nfc->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + } else { + const uint32_t service_ind = + event.event - SubmenuIndexDynamic; // offset the three enums above + + text_box_reset(nfc->text_box); + furi_string_reset(nfc->text_box_store); + + const FelicaService* service = simple_array_cget(system->services, service_ind); + furi_string_cat_printf(nfc->text_box_store, "Service 0x%04X\n", service->code); + nfc_more_info_render_felica_blocks( + data, system, nfc->text_box_store, service->code); + + text_box_set_font(nfc->text_box, TextBoxFontHex); + text_box_set_text(nfc->text_box, furi_string_get_cstr(nfc->text_box_store)); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); + } + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneFelicaSystem, state | 1); + consumed = true; + } + + } else if(event.type == SceneManagerEventTypeBack) { + if(state & 1) { + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneFelicaSystem, state & ~1); + consumed = true; + } + } + + return consumed; +} + +void nfc_scene_felica_system_on_exit(void* context) { + NfcApp* nfc = context; + + // Clear views + widget_reset(nfc->widget); + text_box_reset(nfc->text_box); + furi_string_reset(nfc->text_box_store); + submenu_reset(nfc->submenu); +} diff --git a/lib/nfc/protocols/felica/felica.c b/lib/nfc/protocols/felica/felica.c index 4e0ff7472..2e8d5f92a 100644 --- a/lib/nfc/protocols/felica/felica.c +++ b/lib/nfc/protocols/felica/felica.c @@ -42,26 +42,16 @@ FelicaData* felica_alloc(void) { FelicaData* data = malloc(sizeof(FelicaData)); furi_check(data); - data->services = simple_array_alloc(&felica_service_array_cfg); - data->areas = simple_array_alloc(&felica_area_array_cfg); - data->public_blocks = simple_array_alloc(&felica_public_block_array_cfg); - furi_check(data->services); - furi_check(data->areas); - furi_check(data->public_blocks); + data->systems = simple_array_alloc(&felica_system_array_cfg); + furi_check(data->systems); return data; } void felica_free(FelicaData* data) { furi_check(data); - furi_check(data->services); - simple_array_free(data->services); - - furi_check(data->areas); - simple_array_free(data->areas); - - furi_check(data->public_blocks); - simple_array_free(data->public_blocks); + furi_check(data->systems); + simple_array_free(data->systems); free(data); } @@ -69,16 +59,8 @@ void felica_free(FelicaData* data) { void felica_reset(FelicaData* data) { furi_check(data); - if(data->services) { - simple_array_reset(data->services); - } - - if(data->areas) { - simple_array_reset(data->areas); - } - - if(data->public_blocks) { - simple_array_reset(data->public_blocks); + if(data->systems) { + simple_array_reset(data->systems); } data->blocks_read = 0; @@ -102,9 +84,7 @@ void felica_copy(FelicaData* data, const FelicaData* other) { data->data = other->data; data->workflow_type = other->workflow_type; - simple_array_copy(data->services, other->services); - simple_array_copy(data->areas, other->areas); - simple_array_copy(data->public_blocks, other->public_blocks); + simple_array_copy(data->systems, other->systems); } bool felica_verify(FelicaData* data, const FuriString* device_type) { @@ -175,99 +155,125 @@ bool felica_load(FelicaData* data, FlipperFormat* ff, uint32_t version) { } while(false); break; case FelicaStandard: - // Areas - do { - uint32_t area_count = 0; - if(!flipper_format_read_uint32(ff, "Area found", &area_count, 1)) break; - simple_array_init(data->areas, area_count); + uint32_t systems_total = 0; + if(!flipper_format_read_uint32(ff, "System found", &systems_total, 1)) break; + simple_array_init(data->systems, systems_total); + + for(uint8_t sys_idx = 0; sys_idx < systems_total; sys_idx++) { + FelicaSystem* system = simple_array_get(data->systems, sys_idx); + uint16_t system_code = 0; furi_string_reset(str_key_buffer); furi_string_reset(str_data_buffer); - for(uint16_t i = 0; i < area_count; i++) { - furi_string_printf(str_key_buffer, "Area %03X", i); - if(!flipper_format_read_string( - ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) { - break; - } - FelicaArea* area = simple_array_get(data->areas, i); - if(sscanf( - furi_string_get_cstr(str_data_buffer), - "| Code %04hX | Services #%03hX-#%03hX |", - &area->code, - &area->first_idx, - &area->last_idx) != 3) { - break; - } - } - } while(false); - - // Services - do { - uint32_t service_count = 0; - if(!flipper_format_read_uint32(ff, "Service found", &service_count, 1)) break; - simple_array_init(data->services, service_count); - - furi_string_reset(str_key_buffer); - furi_string_reset(str_data_buffer); - for(uint16_t i = 0; i < service_count; i++) { - furi_string_printf(str_key_buffer, "Service %03X", i); - if(!flipper_format_read_string( - ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) { - break; - } - FelicaService* service = simple_array_get(data->services, i); - - // all unread in the beginning. reserved for future block load - if(!sscanf( - furi_string_get_cstr(str_data_buffer), "| Code %04hX |", &service->code)) { - break; - } - service->attr = service->code & 0x3F; - } - } while(false); - - // Public blocks - do { - furi_string_reset(str_data_buffer); - furi_string_reset(str_key_buffer); - uint32_t public_block_count = 0; - if(!flipper_format_read_uint32(ff, "Public blocks read", &public_block_count, 1)) + furi_string_printf(str_key_buffer, "System %02X", sys_idx); + if(!flipper_format_read_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) break; - simple_array_init(data->public_blocks, public_block_count); - for(uint16_t i = 0; i < public_block_count; i++) { - furi_string_printf(str_key_buffer, "Block %04X", i); - if(!flipper_format_read_string( - ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) { - break; - } - FelicaPublicBlock* public_block = simple_array_get(data->public_blocks, i); - if(sscanf( - furi_string_get_cstr(str_data_buffer), - "| Service code %04hX | Block index %02hhX |", - &public_block->service_code, - &public_block->block_idx) != 2) { - break; - } - - size_t needle = furi_string_search_str(str_data_buffer, "Data: "); - if(needle == FURI_STRING_FAILURE) { - break; - } - needle += 6; // length of "Data: " = 6 - furi_string_mid(str_data_buffer, needle, 3 * FELICA_DATA_BLOCK_SIZE); - furi_string_replace_all(str_data_buffer, " ", ""); - if(!hex_chars_to_uint8( - furi_string_get_cstr(str_data_buffer), public_block->block.data)) { - break; - } - - furi_string_reset(str_data_buffer); - for(size_t j = 0; j < FELICA_DATA_BLOCK_SIZE; j++) { - furi_string_cat_printf(str_data_buffer, "%02X ", public_block->block.data[j]); - } + if(!sscanf(furi_string_get_cstr(str_data_buffer), "%04hX", &system_code)) { + break; } - } while(false); + + system->system_code = system_code; + system->system_code_idx = sys_idx; + + // Areas + do { + uint32_t area_count = 0; + if(!flipper_format_read_uint32(ff, "Area found", &area_count, 1)) break; + simple_array_init(system->areas, area_count); + + furi_string_reset(str_key_buffer); + furi_string_reset(str_data_buffer); + for(uint16_t i = 0; i < area_count; i++) { + furi_string_printf(str_key_buffer, "Area %03X", i); + if(!flipper_format_read_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) { + break; + } + FelicaArea* area = simple_array_get(system->areas, i); + if(sscanf( + furi_string_get_cstr(str_data_buffer), + "| Code %04hX | Services #%03hX-#%03hX |", + &area->code, + &area->first_idx, + &area->last_idx) != 3) { + break; + } + } + } while(false); + + // Services + do { + uint32_t service_count = 0; + if(!flipper_format_read_uint32(ff, "Service found", &service_count, 1)) break; + simple_array_init(system->services, service_count); + + furi_string_reset(str_key_buffer); + furi_string_reset(str_data_buffer); + for(uint16_t i = 0; i < service_count; i++) { + furi_string_printf(str_key_buffer, "Service %03X", i); + if(!flipper_format_read_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) { + break; + } + FelicaService* service = simple_array_get(system->services, i); + + // all unread in the beginning. reserved for future block load + if(!sscanf( + furi_string_get_cstr(str_data_buffer), + "| Code %04hX |", + &service->code)) { + break; + } + service->attr = service->code & 0x3F; + } + } while(false); + + // Public blocks + do { + furi_string_reset(str_data_buffer); + furi_string_reset(str_key_buffer); + uint32_t public_block_count = 0; + if(!flipper_format_read_uint32(ff, "Public blocks read", &public_block_count, 1)) + break; + simple_array_init(system->public_blocks, public_block_count); + for(uint16_t i = 0; i < public_block_count; i++) { + furi_string_printf(str_key_buffer, "Block %04X", i); + if(!flipper_format_read_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) { + break; + } + + FelicaPublicBlock* public_block = simple_array_get(system->public_blocks, i); + if(sscanf( + furi_string_get_cstr(str_data_buffer), + "| Service code %04hX | Block index %02hhX |", + &public_block->service_code, + &public_block->block_idx) != 2) { + break; + } + + size_t needle = furi_string_search_str(str_data_buffer, "Data: "); + if(needle == FURI_STRING_FAILURE) { + break; + } + needle += 6; // length of "Data: " = 6 + furi_string_mid(str_data_buffer, needle, 3 * FELICA_DATA_BLOCK_SIZE); + furi_string_replace_all(str_data_buffer, " ", ""); + if(!hex_chars_to_uint8( + furi_string_get_cstr(str_data_buffer), public_block->block.data)) { + break; + } + + furi_string_reset(str_data_buffer); + for(size_t j = 0; j < FELICA_DATA_BLOCK_SIZE; j++) { + furi_string_cat_printf( + str_data_buffer, "%02X ", public_block->block.data[j]); + } + } + } while(false); + } break; default: break; @@ -334,88 +340,107 @@ bool felica_save(const FelicaData* data, FlipperFormat* ff) { case FelicaStandard: if(!flipper_format_write_comment_cstr(ff, "Felica Standard specific data")) break; + uint32_t systems_count = simple_array_get_count(data->systems); + if(!flipper_format_write_uint32(ff, "System found", &systems_count, 1)) break; + for(uint32_t sys_idx = 0; sys_idx < systems_count; sys_idx++) { + FelicaSystem* system = simple_array_get(data->systems, sys_idx); - do { - uint32_t area_count = simple_array_get_count(data->areas); - uint32_t service_count = simple_array_get_count(data->services); - // Note: The theoretical max area/service count is 2^10 - // So uint16_t is already enough for practical usage - // The following key index print will use %03X because 12 bits are enough to cover 0-1023 - - // Area count - if(!flipper_format_write_uint32(ff, "Area found", &area_count, 1)) break; - - // Area data furi_string_reset(str_data_buffer); furi_string_reset(str_key_buffer); - for(uint16_t i = 0; i < area_count; i++) { - FelicaArea* area = simple_array_get(data->areas, i); - furi_string_printf(str_key_buffer, "Area %03X", i); - furi_string_printf( - str_data_buffer, - "| Code %04X | Services #%03X-#%03X |", - area->code, - area->first_idx, - area->last_idx); - if(!flipper_format_write_string( - ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) - break; - } - if(!flipper_format_write_empty_line(ff)) break; - - // Service count - if(!flipper_format_write_uint32(ff, "Service found", &service_count, 1)) break; - - // Service data - furi_string_reset(str_data_buffer); - furi_string_reset(str_key_buffer); - for(uint16_t i = 0; i < service_count; i++) { - FelicaService* service = simple_array_get(data->services, i); - furi_string_printf(str_key_buffer, "Service %03X", i); - furi_string_printf( - str_data_buffer, "| Code %04X | Attrib. %02X ", service->code, service->attr); - felica_service_get_attribute_string(service, str_data_buffer); - if(!flipper_format_write_string( - ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) - break; - } - if(!flipper_format_write_empty_line(ff)) break; - - // Directory tree - furi_string_reset(str_data_buffer); - furi_string_reset(str_key_buffer); - furi_string_printf( - str_data_buffer, "\n::: ... are public services\n||| ... are private services"); - felica_write_directory_tree(data, str_data_buffer); - furi_string_replace_all(str_data_buffer, ":", "+"); - // We use a clearer marker in saved text files - if(!flipper_format_write_string(ff, "Directory Tree", str_data_buffer)) break; - } while(false); - - // Public blocks - do { - uint32_t public_block_count = simple_array_get_count(data->public_blocks); - if(!flipper_format_write_uint32(ff, "Public blocks read", &public_block_count, 1)) + furi_string_printf(str_key_buffer, "\n\nSystem %02X", (uint8_t)sys_idx); + furi_string_printf(str_data_buffer, "%04X", system->system_code); + if(!flipper_format_write_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) break; - furi_string_reset(str_data_buffer); - furi_string_reset(str_key_buffer); - for(uint16_t i = 0; i < public_block_count; i++) { - FelicaPublicBlock* public_block = simple_array_get(data->public_blocks, i); - furi_string_printf(str_key_buffer, "Block %04X", i); + if(!flipper_format_write_empty_line(ff)) break; + + do { + uint32_t area_count = simple_array_get_count(system->areas); + uint32_t service_count = simple_array_get_count(system->services); + // Note: The theoretical max area/service count is 2^10 + // So uint16_t is already enough for practical usage + // The following key index print will use %03X because 12 bits are enough to cover 0-1023 + + // Area count + if(!flipper_format_write_uint32(ff, "Area found", &area_count, 1)) break; + + // Area data + furi_string_reset(str_data_buffer); + furi_string_reset(str_key_buffer); + for(uint16_t i = 0; i < area_count; i++) { + FelicaArea* area = simple_array_get(system->areas, i); + furi_string_printf(str_key_buffer, "Area %03X", i); + furi_string_printf( + str_data_buffer, + "| Code %04X | Services #%03X-#%03X |", + area->code, + area->first_idx, + area->last_idx); + if(!flipper_format_write_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) + break; + } + if(!flipper_format_write_empty_line(ff)) break; + + // Service count + if(!flipper_format_write_uint32(ff, "Service found", &service_count, 1)) break; + + // Service data + furi_string_reset(str_data_buffer); + furi_string_reset(str_key_buffer); + for(uint16_t i = 0; i < service_count; i++) { + FelicaService* service = simple_array_get(system->services, i); + furi_string_printf(str_key_buffer, "Service %03X", i); + furi_string_printf( + str_data_buffer, + "| Code %04X | Attrib. %02X ", + service->code, + service->attr); + felica_service_get_attribute_string(service, str_data_buffer); + if(!flipper_format_write_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) + break; + } + if(!flipper_format_write_empty_line(ff)) break; + + // Directory tree + furi_string_reset(str_data_buffer); + furi_string_reset(str_key_buffer); furi_string_printf( str_data_buffer, - "| Service code %04X | Block index %02X | Data: ", - public_block->service_code, - public_block->block_idx); - for(uint8_t j = 0; j < FELICA_DATA_BLOCK_SIZE; j++) { - furi_string_cat_printf(str_data_buffer, "%02X ", public_block->block.data[j]); - } - furi_string_cat_printf(str_data_buffer, "|"); - if(!flipper_format_write_string( - ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) + "\n::: ... are public services\n||| ... are private services"); + felica_write_directory_tree(system, str_data_buffer); + furi_string_replace_all(str_data_buffer, ":", "+"); + // We use a clearer marker in saved text files + if(!flipper_format_write_string(ff, "Directory Tree", str_data_buffer)) break; + } while(false); + + // Public blocks + do { + uint32_t public_block_count = simple_array_get_count(system->public_blocks); + if(!flipper_format_write_uint32(ff, "Public blocks read", &public_block_count, 1)) break; - } - } while(false); + furi_string_reset(str_data_buffer); + furi_string_reset(str_key_buffer); + for(uint16_t i = 0; i < public_block_count; i++) { + FelicaPublicBlock* public_block = simple_array_get(system->public_blocks, i); + furi_string_printf(str_key_buffer, "Block %04X", i); + furi_string_printf( + str_data_buffer, + "| Service code %04X | Block index %02X | Data: ", + public_block->service_code, + public_block->block_idx); + for(uint8_t j = 0; j < FELICA_DATA_BLOCK_SIZE; j++) { + furi_string_cat_printf( + str_data_buffer, "%02X ", public_block->block.data[j]); + } + furi_string_cat_printf(str_data_buffer, "|"); + if(!flipper_format_write_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) + break; + } + } while(false); + } break; default: break; @@ -436,9 +461,7 @@ bool felica_is_equal(const FelicaData* data, const FelicaData* other) { memcmp(data->pmm.data, other->pmm.data, sizeof(FelicaPMm)) == 0 && data->blocks_total == other->blocks_total && data->blocks_read == other->blocks_read && memcmp(&data->data, &other->data, sizeof(data->data)) == 0 && - simple_array_is_equal(data->services, other->services) && - simple_array_is_equal(data->areas, other->areas) && - simple_array_is_equal(data->public_blocks, other->public_blocks); + simple_array_is_equal(data->systems, other->systems); } const char* felica_get_device_name(const FelicaData* data, NfcDeviceNameType name_type) { @@ -640,8 +663,8 @@ void felica_calculate_mac_write( felica_calculate_mac(ctx, session_swapped, rc, first_block, data, FELICA_DATA_BLOCK_SIZE, mac); } -void felica_write_directory_tree(const FelicaData* data, FuriString* str) { - furi_check(data); +void felica_write_directory_tree(const FelicaSystem* system, FuriString* str) { + furi_check(system); furi_check(str); furi_string_cat_str(str, "\n"); @@ -650,12 +673,12 @@ void felica_write_directory_tree(const FelicaData* data, FuriString* str) { uint8_t depth = 0; size_t area_iter = 0; - const size_t area_count = simple_array_get_count(data->areas); - const size_t service_count = simple_array_get_count(data->services); + const size_t area_count = simple_array_get_count(system->areas); + const size_t service_count = simple_array_get_count(system->services); for(size_t svc_idx = 0; svc_idx < service_count; ++svc_idx) { while(area_iter < area_count) { - const FelicaArea* next_area = simple_array_get(data->areas, area_iter); + const FelicaArea* next_area = simple_array_get(system->areas, area_iter); if(next_area->first_idx != svc_idx) break; for(uint8_t i = 0; i < depth - 1; ++i) @@ -667,7 +690,7 @@ void felica_write_directory_tree(const FelicaData* data, FuriString* str) { area_iter++; } - const FelicaService* service = simple_array_get(data->services, svc_idx); + const FelicaService* service = simple_array_get(system->services, svc_idx); bool is_public = (service->attr & FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ) != 0; for(uint8_t i = 0; i < depth - 1; ++i) diff --git a/lib/nfc/protocols/felica/felica.h b/lib/nfc/protocols/felica/felica.h index 5a5a75215..bc83ff846 100644 --- a/lib/nfc/protocols/felica/felica.h +++ b/lib/nfc/protocols/felica/felica.h @@ -50,8 +50,10 @@ extern "C" { #define FELICA_TIME_SLOT_8 (0x07U) #define FELICA_TIME_SLOT_16 (0x0FU) -#define FELICA_CMD_LIST_SERVICE_CODE 0x0A -#define FELICA_CMD_LIST_SERVICE_CODE_RESP 0x0B +#define FELICA_CMD_LIST_SERVICE_CODE 0x0A +#define FELICA_CMD_LIST_SERVICE_CODE_RESP 0x0B +#define FELICA_CMD_REQUEST_SYSTEM_CODE 0x0C +#define FELICA_CMD_REQUEST_SYSTEM_CODE_RESP 0x0D #define FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ (0b000001) #define FELICA_SERVICE_ATTRIBUTE_READ_ONLY (0b000010) @@ -182,6 +184,14 @@ typedef struct { uint8_t block_idx; } FelicaPublicBlock; +typedef struct { + uint8_t system_code_idx; + uint16_t system_code; + SimpleArray* services; + SimpleArray* areas; + SimpleArray* public_blocks; +} FelicaSystem; + /** @brief Structure used to store Felica data and additional values about reading */ typedef struct { FelicaIDm idm; @@ -190,9 +200,8 @@ typedef struct { uint8_t blocks_read; FelicaFSUnion data; - SimpleArray* services; - SimpleArray* areas; - SimpleArray* public_blocks; + SimpleArray* systems; + FelicaWorkflowType workflow_type; } FelicaData; @@ -248,6 +257,12 @@ typedef struct { uint8_t data[]; } FelicaListServiceCommandResponse; +typedef struct { + FelicaCommandHeaderRaw header; + uint8_t system_count; + uint8_t system_code[]; +} FelicaListSystemCodeCommandResponse; + typedef FelicaCommandResponseHeader FelicaListenerWriteCommandResponse; typedef FelicaCommandResponseHeader FelicaPollerWriteCommandResponse; @@ -309,7 +324,7 @@ void felica_calculate_mac_write( const uint8_t* data, uint8_t* mac); -void felica_write_directory_tree(const FelicaData* data, FuriString* str); +void felica_write_directory_tree(const FelicaSystem* system, FuriString* str); void felica_get_workflow_type(FelicaData* data); diff --git a/lib/nfc/protocols/felica/felica_i.c b/lib/nfc/protocols/felica/felica_i.c index e265ea862..c5ecdf121 100644 --- a/lib/nfc/protocols/felica/felica_i.c +++ b/lib/nfc/protocols/felica/felica_i.c @@ -1,5 +1,36 @@ #include "felica_i.h" +void felica_system_init(FelicaSystem* system) { + system->system_code = 0; + system->system_code_idx = 0; + system->services = simple_array_alloc(&felica_service_array_cfg); + system->areas = simple_array_alloc(&felica_area_array_cfg); + system->public_blocks = simple_array_alloc(&felica_public_block_array_cfg); +} + +void felica_system_reset(FelicaSystem* system) { + furi_check(system); + system->system_code = 0; + system->system_code_idx = 0; + furi_check(system->services); + furi_check(system->areas); + furi_check(system->public_blocks); + simple_array_free(system->services); + simple_array_free(system->areas); + simple_array_free(system->public_blocks); + memset(system, 0, sizeof(FelicaSystem)); +} + +void felica_system_copy(FelicaSystem* system, const FelicaSystem* other) { + furi_check(system); + furi_check(other); + system->system_code = other->system_code; + system->system_code_idx = other->system_code_idx; + simple_array_copy(system->services, other->services); + simple_array_copy(system->areas, other->areas); + simple_array_copy(system->public_blocks, other->public_blocks); +} + const SimpleArrayConfig felica_service_array_cfg = { .init = NULL, .copy = NULL, @@ -20,3 +51,10 @@ const SimpleArrayConfig felica_public_block_array_cfg = { .reset = NULL, .type_size = sizeof(FelicaPublicBlock), }; + +const SimpleArrayConfig felica_system_array_cfg = { + .init = (SimpleArrayInit)felica_system_init, + .copy = (SimpleArrayCopy)felica_system_copy, + .reset = (SimpleArrayReset)felica_system_reset, + .type_size = sizeof(FelicaSystem), +}; diff --git a/lib/nfc/protocols/felica/felica_i.h b/lib/nfc/protocols/felica/felica_i.h index a708aa729..d55077c5e 100644 --- a/lib/nfc/protocols/felica/felica_i.h +++ b/lib/nfc/protocols/felica/felica_i.h @@ -8,3 +8,8 @@ extern const SimpleArrayConfig felica_service_array_cfg; extern const SimpleArrayConfig felica_area_array_cfg; extern const SimpleArrayConfig felica_public_block_array_cfg; +extern const SimpleArrayConfig felica_system_array_cfg; + +void felica_system_init(FelicaSystem* system); +void felica_system_reset(FelicaSystem* system); +void felica_system_copy(FelicaSystem* system, const FelicaSystem* other); diff --git a/lib/nfc/protocols/felica/felica_poller.c b/lib/nfc/protocols/felica/felica_poller.c index a6964c685..98e07df42 100644 --- a/lib/nfc/protocols/felica/felica_poller.c +++ b/lib/nfc/protocols/felica/felica_poller.c @@ -12,6 +12,7 @@ ARRAY_DEF(felica_service_array, FelicaService, M_POD_OPLIST); // -V658 ARRAY_DEF(felica_area_array, FelicaArea, M_POD_OPLIST); // -V658 ARRAY_DEF(felica_public_block_array, FelicaPublicBlock, M_POD_OPLIST); // -V658 +ARRAY_DEF(felica_system_array, FelicaSystem, M_POD_OPLIST); // -V658 typedef NfcCommand (*FelicaPollerReadHandler)(FelicaPoller* instance); @@ -43,6 +44,9 @@ static FelicaPoller* felica_poller_alloc(Nfc* nfc) { instance->general_event.event_data = &instance->felica_event; instance->general_event.instance = instance; + instance->systems_read = 0; + instance->systems_total = 0; + return instance; } @@ -94,7 +98,7 @@ NfcCommand felica_poller_state_handler_activate(FelicaPoller* instance) { switch(instance->data->workflow_type) { case FelicaStandard: - instance->state = FelicaPollerStateTraverseStandardSystem; + instance->state = FelicaPollerStateListSystem; break; case FelicaLite: instance->state = FelicaPollerStateReadLiteBlocks; @@ -117,6 +121,44 @@ NfcCommand felica_poller_state_handler_activate(FelicaPoller* instance) { return command; } +NfcCommand felica_poller_state_handler_list_system(FelicaPoller* instance) { + FURI_LOG_D(TAG, "List System"); + + NfcCommand command = NfcCommandContinue; + + FelicaListSystemCodeCommandResponse* response_system_code; + FelicaError error = felica_poller_list_system_code(instance, &response_system_code); + + instance->systems_total = response_system_code->system_count; + simple_array_init(instance->data->systems, instance->systems_total); + uint8_t* system_codes = response_system_code->system_code; + + for(uint8_t i = 0; i < instance->systems_total; i++) { + FelicaSystem* system = simple_array_get(instance->data->systems, i); + system->system_code = system_codes[i * 2] << 8 | system_codes[i * 2 + 1]; + system->system_code_idx = i; + } + + if(error == FelicaErrorNone) { + instance->state = FelicaPollerStateSelectSystemIndex; + } else if(error != FelicaErrorTimeout) { + instance->felica_event.type = FelicaPollerEventTypeError; + instance->felica_event_data.error = error; + instance->state = FelicaPollerStateReadFailed; + } + return command; +} + +NfcCommand felica_poller_state_handler_select_system_idx(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Select System Index %d", instance->systems_read); + uint8_t system_index_mask = instance->systems_read << 4; + instance->data->idm.data[0] &= 0x0F; + instance->data->idm.data[0] |= system_index_mask; + instance->state = FelicaPollerStateTraverseStandardSystem; + + return NfcCommandContinue; +} + NfcCommand felica_poller_state_handler_auth_internal(FelicaPoller* instance) { FURI_LOG_D(TAG, "Auth Internal"); @@ -274,6 +316,8 @@ NfcCommand felica_poller_state_handler_traverse_standard_system(FelicaPoller* in service->code = code_begin; service->attr = (uint8_t)(code_begin & 0x3F); + FURI_LOG_D(TAG, "Service %04X", service->code); + if(felica_area_array_size(area_buffer)) { FelicaArea* current_area = felica_area_array_back(area_buffer); current_area->last_idx = (uint16_t)(felica_service_array_size(service_buffer) - 1); @@ -284,31 +328,30 @@ NfcCommand felica_poller_state_handler_traverse_standard_system(FelicaPoller* in const size_t service_num = felica_service_array_size(service_buffer); const size_t area_num = felica_area_array_size(area_buffer); + FelicaSystem* system = simple_array_get(instance->data->systems, instance->systems_read); if(service_num) { - simple_array_init(instance->data->services, (uint32_t)service_num); + simple_array_init(system->services, (uint32_t)service_num); memcpy( - simple_array_get(instance->data->services, 0), + simple_array_get(system->services, 0), service_buffer->ptr, service_num * sizeof(FelicaService)); } else { - simple_array_reset(instance->data->services); + simple_array_reset(system->services); } if(area_num) { - simple_array_init(instance->data->areas, (uint32_t)area_num); + simple_array_init(system->areas, (uint32_t)area_num); memcpy( - simple_array_get(instance->data->areas, 0), - area_buffer->ptr, - area_num * sizeof(FelicaArea)); + simple_array_get(system->areas, 0), area_buffer->ptr, area_num * sizeof(FelicaArea)); } else { - simple_array_reset(instance->data->areas); + simple_array_reset(system->areas); } FURI_LOG_I( TAG, "Services found: %lu, Areas found: %lu", - simple_array_get_count(instance->data->services), - simple_array_get_count(instance->data->areas)); + simple_array_get_count(system->services), + simple_array_get_count(system->areas)); felica_service_array_clear(service_buffer); felica_area_array_clear(area_buffer); @@ -320,22 +363,22 @@ NfcCommand felica_poller_state_handler_traverse_standard_system(FelicaPoller* in NfcCommand felica_poller_state_handler_read_standard_blocks(FelicaPoller* instance) { FURI_LOG_D(TAG, "Read Standard Blocks"); - const uint32_t service_count = simple_array_get_count(instance->data->services); + FelicaSystem* system = simple_array_get(instance->data->systems, instance->systems_read); + const uint32_t service_count = simple_array_get_count(system->services); felica_public_block_array_t public_block_buffer; felica_public_block_array_init(public_block_buffer); - instance->state = FelicaPollerStateReadSuccess; bool have_read_anything = false; + FelicaError error = FelicaErrorNone; for(uint32_t i = 0; i < service_count; i++) { - const FelicaService* service = simple_array_get(instance->data->services, i); + const FelicaService* service = simple_array_get(system->services, i); if((service->attr & FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ) == 0) continue; uint8_t block_count = 1; uint8_t block_list[1] = {0}; - FelicaError error = FelicaErrorNone; FelicaPollerReadCommandResponse* response; do { error = felica_poller_read_blocks( @@ -369,11 +412,20 @@ NfcCommand felica_poller_state_handler_read_standard_blocks(FelicaPoller* instan } } + if(error == FelicaErrorNone) { + instance->systems_read++; + if(instance->systems_read == instance->systems_total) { + instance->state = FelicaPollerStateReadSuccess; + } else { + instance->state = FelicaPollerStateSelectSystemIndex; + } + } + if(have_read_anything) { const size_t n = felica_public_block_array_size(public_block_buffer); - simple_array_init(instance->data->public_blocks, (uint32_t)n); + simple_array_init(system->public_blocks, (uint32_t)n); memcpy( - simple_array_get(instance->data->public_blocks, 0), + simple_array_get(system->public_blocks, 0), public_block_buffer->ptr, n * sizeof(FelicaPublicBlock)); } @@ -446,6 +498,8 @@ NfcCommand felica_poller_state_handler_read_success(FelicaPoller* instance) { instance->felica_event.type = FelicaPollerEventTypeReady; } + instance->data->idm.data[0] &= 0x0F; + instance->felica_event_data.error = FelicaErrorNone; return instance->callback(instance->general_event, instance->context); } @@ -453,6 +507,7 @@ NfcCommand felica_poller_state_handler_read_success(FelicaPoller* instance) { NfcCommand felica_poller_state_handler_read_failed(FelicaPoller* instance) { FURI_LOG_D(TAG, "Read Fail"); instance->callback(instance->general_event, instance->context); + instance->data->idm.data[0] &= 0x0F; return NfcCommandStop; } @@ -460,6 +515,8 @@ NfcCommand felica_poller_state_handler_read_failed(FelicaPoller* instance) { static const FelicaPollerReadHandler felica_poller_handler[FelicaPollerStateNum] = { [FelicaPollerStateIdle] = felica_poller_state_handler_idle, [FelicaPollerStateActivated] = felica_poller_state_handler_activate, + [FelicaPollerStateListSystem] = felica_poller_state_handler_list_system, + [FelicaPollerStateSelectSystemIndex] = felica_poller_state_handler_select_system_idx, [FelicaPollerStateAuthenticateInternal] = felica_poller_state_handler_auth_internal, [FelicaPollerStateAuthenticateExternal] = felica_poller_state_handler_auth_external, [FelicaPollerStateTraverseStandardSystem] = diff --git a/lib/nfc/protocols/felica/felica_poller_i.c b/lib/nfc/protocols/felica/felica_poller_i.c index 4ab9a8e4c..bbbb824d3 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.c +++ b/lib/nfc/protocols/felica/felica_poller_i.c @@ -233,7 +233,9 @@ static void felica_poller_prepare_tx_buffer_raw( cmd.length = sizeof(FelicaCommandHeaderRaw) + data_length; bit_buffer_reset(instance->tx_buffer); bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)&cmd, sizeof(FelicaCommandHeaderRaw)); - bit_buffer_append_bytes(instance->tx_buffer, data, data_length); + if(data_length > 0) { + bit_buffer_append_bytes(instance->tx_buffer, data, data_length); + } } FelicaError felica_poller_list_service_by_cursor( @@ -264,3 +266,31 @@ FelicaError felica_poller_list_service_by_cursor( *response_ptr = (FelicaListServiceCommandResponse*)bit_buffer_get_data(instance->rx_buffer); return error; } + +FelicaError felica_poller_list_system_code( + FelicaPoller* instance, + FelicaListSystemCodeCommandResponse** const response_ptr) { + furi_assert(instance); + furi_assert(response_ptr); + + uint8_t data[] = {0}; + + felica_poller_prepare_tx_buffer_raw(instance, FELICA_CMD_REQUEST_SYSTEM_CODE, data, 0); + + bit_buffer_reset(instance->rx_buffer); + + FelicaError error = felica_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, FELICA_POLLER_POLLING_FWT); + if(error != FelicaErrorNone) { + FURI_LOG_E(TAG, "Request system code failed with error: %d", error); + return error; + } + + size_t rx_len = bit_buffer_get_size_bytes(instance->rx_buffer); + if(rx_len < sizeof(FelicaCommandHeaderRaw) + 3) return FelicaErrorProtocol; + // at least 1 system code + the count being 0x01 + + // error is known to be FelicaErrorNone here + *response_ptr = (FelicaListSystemCodeCommandResponse*)bit_buffer_get_data(instance->rx_buffer); + return error; +} diff --git a/lib/nfc/protocols/felica/felica_poller_i.h b/lib/nfc/protocols/felica/felica_poller_i.h index 98948db86..446f34c43 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.h +++ b/lib/nfc/protocols/felica/felica_poller_i.h @@ -17,6 +17,8 @@ extern "C" { typedef enum { FelicaPollerStateIdle, FelicaPollerStateActivated, + FelicaPollerStateListSystem, + FelicaPollerStateSelectSystemIndex, FelicaPollerStateAuthenticateInternal, FelicaPollerStateAuthenticateExternal, FelicaPollerStateTraverseStandardSystem, @@ -42,6 +44,8 @@ struct FelicaPoller { FelicaPollerEventData felica_event_data; NfcGenericCallback callback; uint8_t block_index; + uint8_t systems_read; + uint8_t systems_total; void* context; }; @@ -116,6 +120,10 @@ FelicaError felica_poller_list_service_by_cursor( uint16_t cursor, FelicaListServiceCommandResponse** response_ptr); +FelicaError felica_poller_list_system_code( + FelicaPoller* instance, + FelicaListSystemCodeCommandResponse** response_ptr); + #ifdef __cplusplus } #endif diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 801e0b2a3..429b3995e 100755 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -135,6 +135,7 @@ Header,+,lib/nfc/helpers/iso14443_crc.h,, Header,+,lib/nfc/helpers/nfc_data_generator.h,, Header,+,lib/nfc/helpers/nfc_util.h,, Header,+,lib/nfc/nfc.h,, +Header,+,lib/nfc/nfc_common.h,, Header,+,lib/nfc/nfc_device.h,, Header,+,lib/nfc/nfc_listener.h,, Header,+,lib/nfc/nfc_poller.h,, @@ -636,8 +637,8 @@ Function,-,arc4random_uniform,__uint32_t,__uint32_t Function,+,args_char_to_hex,_Bool,"char, char, uint8_t*" Function,+,args_get_first_word_length,size_t,FuriString* Function,+,args_length,size_t,FuriString* -Function,+,args_read_float_and_trim,_Bool,"FuriString*, float*" Function,+,args_read_duration,_Bool,"FuriString*, uint32_t*, const char*" +Function,+,args_read_float_and_trim,_Bool,"FuriString*, float*" Function,+,args_read_hex_bytes,_Bool,"FuriString*, uint8_t*, size_t" Function,+,args_read_int_and_trim,_Bool,"FuriString*, int*" Function,+,args_read_probably_quoted_string_and_trim,_Bool,"FuriString*, FuriString*" @@ -1119,7 +1120,7 @@ Function,+,felica_save,_Bool,"const FelicaData*, FlipperFormat*" Function,+,felica_service_get_attribute_string,void,"const FelicaService*, FuriString*" Function,+,felica_set_uid,_Bool,"FelicaData*, const uint8_t*, size_t" Function,+,felica_verify,_Bool,"FelicaData*, const FuriString*" -Function,+,felica_write_directory_tree,void,"const FelicaData*, FuriString*" +Function,+,felica_write_directory_tree,void,"const FelicaSystem*, FuriString*" Function,-,feof,int,FILE* Function,-,feof_unlocked,int,FILE* Function,-,ferror,int,FILE* From d8924c02adf1a704fdd9b0f681f052571bd59f81 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 12 Oct 2025 03:54:01 +0300 Subject: [PATCH 122/192] upd changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a74d9b9a..6706f0288 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,12 @@ * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * SubGHz: Fix crash in add manually menu +* OFW PR 4293: NFC FeliCa Improvement: Dump All Systems (by @zinongli) +* OFW PR 4285: ViewStack: Store View by value to save memory (by @CookiePLMonster) +* OFW PR 4290: Storage: Dont send mount event if SD mounted at boot (by @WillyJL) +* OFW PR 4283: NFC lib: Expose nfc_common.h (by @zinongli) +* OFW: Fix wrbl command tooltip +* OFW: VSCode: Reduce file watcher resource usage * OFW: cli: Buzzer command * OFW: Update demo_windows.txt * OFW: Fix PVS warnings From eb2bdf7ac219a42095719b451954e1f7214abfdf Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 12 Oct 2025 03:57:40 +0300 Subject: [PATCH 123/192] upd changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6706f0288..7c92771a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * SubGHz: Add **ZKTeco 430.5 MHz** add manually support * SubGHz: Add variant of 'Add Manually' menu with manual editing for each value (PR #909 #911 #914 | by @MrLego8-9) * SubGHz: Temporarily remove HoneywellSec protocol due to unstable decoding and incorrect encoding +* Apps: HID PTT: adding global zoom and google meet shortcuts for MacOS (PR #921 | by @hryamzik) * OFW: NFC FeliCa: Service Directory Traverse + Dump All Unencrypted-Readable Services' Blocks * OFW: **NFC CLI commands** * OFW: LFRFID: **Show ISO-3166 Country Names For Pet Chips** From 2efcc8f6ccf034e6d39e39a7c3d4021cc8d6b323 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 12 Oct 2025 03:58:30 +0300 Subject: [PATCH 124/192] fmt --- applications/system/hid_app/views/hid_ptt.c | 74 +++++++-------------- 1 file changed, 24 insertions(+), 50 deletions(-) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index ff57c6bb7..8a28cd292 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -93,28 +93,16 @@ static void hid_ptt_trigger_hand_linux_meet(HidPushToTalk* hid_ptt) { // meet global macos static void hid_ptt_trigger_mute_macos_meet_global(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( - hid_ptt->hid, - KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL | HID_KEYBOARD_7); - hid_hal_keyboard_release( - hid_ptt->hid, - KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL | HID_KEYBOARD_7); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL | HID_KEYBOARD_7); + hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL | HID_KEYBOARD_7); } static void hid_ptt_trigger_camera_macos_meet_global(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( - hid_ptt->hid, - KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL | HID_KEYBOARD_8); - hid_hal_keyboard_release( - hid_ptt->hid, - KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL | HID_KEYBOARD_8); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL | HID_KEYBOARD_8); + hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL | HID_KEYBOARD_8); } static void hid_ptt_trigger_hand_macos_meet_global(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( - hid_ptt->hid, - KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL | HID_KEYBOARD_9); - hid_hal_keyboard_release( - hid_ptt->hid, - KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL | HID_KEYBOARD_9); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL | HID_KEYBOARD_9); + hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL | HID_KEYBOARD_9); } static void hid_ptt_trigger_mute_macos_zoom(HidPushToTalk* hid_ptt) { hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_A); @@ -140,35 +128,23 @@ static void hid_ptt_trigger_hand_zoom(HidPushToTalk* hid_ptt) { // zoom global macos static void hid_ptt_trigger_mute_macos_zoom_global(HidPushToTalk* hid_ptt) { hid_hal_keyboard_press( - hid_ptt->hid, - KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | - HID_KEYBOARD_M); + hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); hid_hal_keyboard_release( - hid_ptt->hid, - KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | - HID_KEYBOARD_M); + hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); } static void hid_ptt_trigger_camera_macos_zoom_global(HidPushToTalk* hid_ptt) { hid_hal_keyboard_press( - hid_ptt->hid, - KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | - HID_KEYBOARD_U); + hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_U); hid_hal_keyboard_release( - hid_ptt->hid, - KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | - HID_KEYBOARD_U); + hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_U); } static void hid_ptt_trigger_hand_zoom_global(HidPushToTalk* hid_ptt) { hid_hal_keyboard_press( - hid_ptt->hid, - KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | - HID_KEYBOARD_Y); + hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_Y); hid_hal_keyboard_release( - hid_ptt->hid, - KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | - HID_KEYBOARD_Y); + hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_Y); } // this one is widely used across different apps @@ -628,13 +604,12 @@ static void hid_ptt_menu_callback( "reader. In this situation, the spacebar performs a different action.\n\n"; break; case HidPushToTalkAppIndexGoogleMeetGlobal: - app_specific_help = - "Google Meet (Global):\n" - "1. Install \"Google Meet - Global Shortcuts\" extension.\n" - "2. Open chrome://extensions/shortcuts.\n" - "3. Set 'Toggle microphone' to Cmd+Ctrl+7 and enable Global.\n" - "4. Set 'Toggle camera' to Cmd+Ctrl+8 and enable Global.\n" - "5. Set 'Raise hand' to Cmd+Ctrl+9 and enable Global.\n\n"; + app_specific_help = "Google Meet (Global):\n" + "1. Install \"Google Meet - Global Shortcuts\" extension.\n" + "2. Open chrome://extensions/shortcuts.\n" + "3. Set 'Toggle microphone' to Cmd+Ctrl+7 and enable Global.\n" + "4. Set 'Toggle camera' to Cmd+Ctrl+8 and enable Global.\n" + "5. Set 'Raise hand' to Cmd+Ctrl+9 and enable Global.\n\n"; break; case HidPushToTalkAppIndexDiscord: app_specific_help = @@ -655,13 +630,12 @@ static void hid_ptt_menu_callback( "Go to Settings > Privacy. Make sure Keyboard shortcut to unmute is toggled on.\n\n"; break; case HidPushToTalkAppIndexZoomGlobal: - app_specific_help = - "Zoom (Global):\n" - "1. Go to Settings > Keyboard Shortcuts.\n" - "2. Find the 'Mute/Unmute' shortcut and click 'Edit'.\n" - "3. Press the Mute button in the app to bind it.\n" - "4. Check global checkbox.\n" - "5. Repeat for video and hand shortcuts.\n\n"; + app_specific_help = "Zoom (Global):\n" + "1. Go to Settings > Keyboard Shortcuts.\n" + "2. Find the 'Mute/Unmute' shortcut and click 'Edit'.\n" + "3. Press the Mute button in the app to bind it.\n" + "4. Check global checkbox.\n" + "5. Repeat for video and hand shortcuts.\n\n"; } FuriString* msg = furi_string_alloc(); From b604c93eb01ff8456937030b94b4c52726a5b7e0 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 12 Oct 2025 21:51:25 +0300 Subject: [PATCH 125/192] fix merge artifact --- .../helpers/protocol_support/felica/felica.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica.c b/applications/main/nfc/helpers/protocol_support/felica/felica.c index 1b52b86bd..26dc86d41 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica.c +++ b/applications/main/nfc/helpers/protocol_support/felica/felica.c @@ -126,15 +126,6 @@ static void nfc_scene_read_success_on_enter_felica(NfcApp* instance) { furi_string_free(temp_str); } -static bool nfc_scene_saved_menu_on_event_felica(NfcApp* instance, SceneManagerEvent event) { - if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) { - scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); - return true; - } - - return false; -} - static void nfc_scene_emulate_on_enter_felica(NfcApp* instance) { const FelicaData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolFelica); instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolFelica, data); @@ -194,7 +185,7 @@ const NfcProtocolSupportBase nfc_protocol_support_felica = { .scene_saved_menu = { .on_enter = nfc_protocol_support_common_on_enter_empty, - .on_event = nfc_scene_saved_menu_on_event_felica, + .on_event = nfc_protocol_support_common_on_event_empty, }, .scene_save_name = { @@ -206,4 +197,11 @@ const NfcProtocolSupportBase nfc_protocol_support_felica = { .on_enter = nfc_scene_emulate_on_enter_felica, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_write = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, }; + +NFC_PROTOCOL_SUPPORT_PLUGIN(felica, NfcProtocolFelica); From 90f446880ab216164c1d9f256d11d29aecf404e7 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 12 Oct 2025 21:54:34 +0300 Subject: [PATCH 126/192] Add MIFARE Classic "Show Keys" UI by aaronjamt --- .../protocol_support/mf_classic/mf_classic.c | 21 +++++ .../main/nfc/scenes/nfc_scene_config.h | 1 + .../scenes/nfc_scene_mf_classic_show_keys.c | 85 +++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_show_keys.c diff --git a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c index fdf7fb35a..53218bb9f 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c +++ b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c @@ -15,6 +15,7 @@ enum { SubmenuIndexDictAttack, SubmenuIndexCrackNonces, SubmenuIndexUpdate, + SubmenuIndexShowKeys, }; static void nfc_scene_info_on_enter_mf_classic(NfcApp* instance) { @@ -139,6 +140,13 @@ static void nfc_scene_read_menu_on_enter_mf_classic(NfcApp* instance) { nfc_protocol_support_common_submenu_callback, instance); } + + submenu_add_item( + submenu, + "Show Keys", + SubmenuIndexShowKeys, + nfc_protocol_support_common_submenu_callback, + instance); } static void nfc_scene_read_success_on_enter_mf_classic(NfcApp* instance) { //-V524 @@ -186,6 +194,13 @@ static void nfc_scene_saved_menu_on_enter_mf_classic(NfcApp* instance) { SubmenuIndexUpdate, nfc_protocol_support_common_submenu_callback, instance); + + submenu_add_item( + submenu, + "Show Keys", + SubmenuIndexShowKeys, + nfc_protocol_support_common_submenu_callback, + instance); } static void nfc_scene_emulate_on_enter_mf_classic(NfcApp* instance) { @@ -218,6 +233,9 @@ static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, SceneManag instance->scene_manager, NfcSceneSaveConfirm, NfcSceneSaveConfirmStateCrackNonces); scene_manager_next_scene(instance->scene_manager, NfcSceneSaveConfirm); consumed = true; + } else if(event.event == SubmenuIndexShowKeys) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicShowKeys); + consumed = true; } } @@ -240,6 +258,9 @@ static bool nfc_scene_saved_menu_on_event_mf_classic(NfcApp* instance, SceneMana } else if(event.event == SubmenuIndexUpdate) { scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicUpdateInitial); consumed = true; + } else if(event.event == SubmenuIndexShowKeys) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicShowKeys); + consumed = true; } } diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index 6634827ed..2cace2925 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -46,6 +46,7 @@ ADD_SCENE(nfc, mf_classic_mfkey_complete, MfClassicMfkeyComplete) ADD_SCENE(nfc, mf_classic_update_initial, MfClassicUpdateInitial) ADD_SCENE(nfc, mf_classic_update_initial_success, MfClassicUpdateInitialSuccess) ADD_SCENE(nfc, mf_classic_update_initial_wrong_card, MfClassicUpdateInitialWrongCard) +ADD_SCENE(nfc, mf_classic_show_keys, MfClassicShowKeys) ADD_SCENE(nfc, mf_classic_keys, MfClassicKeys) ADD_SCENE(nfc, mf_classic_keys_list, MfClassicKeysList) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_show_keys.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_show_keys.c new file mode 100644 index 000000000..9493f2e7a --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_show_keys.c @@ -0,0 +1,85 @@ +#include "../nfc_app_i.h" + +#include +#include + +#define TAG "NfcMfClassicShowKeys" + +void nfc_scene_mf_classic_show_keys_callback(GuiButtonType button, InputType type, void* context) { + NfcApp* instance = context; + if(button == GuiButtonTypeLeft && type == InputTypeShort) { + scene_manager_previous_scene(instance->scene_manager); + } +} + +void nfc_scene_mf_classic_show_keys_on_enter(void* context) { + NfcApp* instance = context; + + const MfClassicData* mfc_data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); + + furi_string_reset(instance->text_box_store); + nfc_append_filename_string_when_present(instance, instance->text_box_store); + + furi_string_cat_printf(instance->text_box_store, "\e#Found MFC Keys:"); + + uint8_t num_sectors = mf_classic_get_total_sectors_num(mfc_data->type); + uint8_t found_keys_a = 0, found_keys_b = 0; + for(uint8_t i = 0; i < num_sectors; i++) { + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(mfc_data, i); + + bool key_a = FURI_BIT(mfc_data->key_a_mask, i); + bool key_b = FURI_BIT(mfc_data->key_b_mask, i); + + if(key_a || key_b) { + furi_string_cat_printf(instance->text_box_store, "\n -> Sector %d\n\e*AccBits:", i); + for(uint8_t j = 0; j < MF_CLASSIC_ACCESS_BYTES_SIZE; j++) { + furi_string_cat_printf( + instance->text_box_store, " %02X", sec_tr->access_bits.data[j]); + } + } + + if(key_a) { + found_keys_a++; + furi_string_cat_printf(instance->text_box_store, "\n\e*A:"); + for(uint8_t j = 0; j < MF_CLASSIC_KEY_SIZE; j++) + furi_string_cat_printf(instance->text_box_store, " %02X", sec_tr->key_a.data[j]); + } + if(key_b) { + found_keys_b++; + furi_string_cat_printf(instance->text_box_store, "\n\e*B:"); + for(uint8_t j = 0; j < MF_CLASSIC_KEY_SIZE; j++) + furi_string_cat_printf(instance->text_box_store, " %02X", sec_tr->key_b.data[j]); + } + } + + furi_string_cat_printf( + instance->text_box_store, + "\nTotal keys found:\n -> %d/%d A keys\n -> %d/%d B keys", + found_keys_a, + num_sectors, + found_keys_b, + num_sectors); + + widget_add_text_scroll_element( + instance->widget, 2, 2, 124, 60, furi_string_get_cstr(instance->text_box_store)); + widget_add_button_element( + instance->widget, + GuiButtonTypeLeft, + "Back", + nfc_scene_mf_classic_show_keys_callback, + instance); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_mf_classic_show_keys_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void nfc_scene_mf_classic_show_keys_on_exit(void* context) { + NfcApp* instance = context; + widget_reset(instance->widget); + furi_string_reset(instance->text_box_store); +} From c6f26c696c905766d89f8e4aca6a481b436a3b93 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 12 Oct 2025 21:55:06 +0300 Subject: [PATCH 127/192] upd changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c92771a6..9deca902a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * SubGHz: Add **ZKTeco 430.5 MHz** add manually support * SubGHz: Add variant of 'Add Manually' menu with manual editing for each value (PR #909 #911 #914 | by @MrLego8-9) * SubGHz: Temporarily remove HoneywellSec protocol due to unstable decoding and incorrect encoding +* NFC: Add MIFARE Classic "Show Keys" UI (by @aaronjamt) * Apps: HID PTT: adding global zoom and google meet shortcuts for MacOS (PR #921 | by @hryamzik) * OFW: NFC FeliCa: Service Directory Traverse + Dump All Unencrypted-Readable Services' Blocks * OFW: **NFC CLI commands** From 5a933f13ad3a1b51dd9d5d80c81dda1d47cbbb02 Mon Sep 17 00:00:00 2001 From: Mykhailo Shevchuk Date: Sun, 12 Oct 2025 22:53:25 +0300 Subject: [PATCH 128/192] Returning fix for reading PWD locked MFUL --- .../protocols/mf_ultralight/mf_ultralight_poller.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index c3dbe88d6..e29e1cb6b 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -565,16 +565,12 @@ static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* in instance->error = mf_ultralight_poller_read_page(instance, start_page, &data); } - // Regression review - const uint8_t read_cnt = instance->data->type == MfUltralightTypeMfulC ? 1 : 4; if(instance->error == MfUltralightErrorNone) { - for(size_t i = 0; i < read_cnt; i++) { - if(start_page + i < instance->pages_total) { - FURI_LOG_D(TAG, "Read page %d success", start_page + i); - instance->data->page[start_page + i] = data.page[i]; - instance->pages_read++; - instance->data->pages_read = instance->pages_read; - } + if(start_page < instance->pages_total) { + FURI_LOG_D(TAG, "Read page %d success", start_page); + instance->data->page[start_page] = data.page[0]; + instance->pages_read++; + instance->data->pages_read = instance->pages_read; } if(instance->pages_read == instance->pages_total) { From b3a5e2c28215d0d6df8de81e5721bde4f38788de Mon Sep 17 00:00:00 2001 From: Mykhailo Shevchuk Date: Sun, 12 Oct 2025 23:09:14 +0300 Subject: [PATCH 129/192] Added UL-C keys to the dictionary --- .../main/nfc/resources/nfc/assets/mf_ultralight_c_dict.nfc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/applications/main/nfc/resources/nfc/assets/mf_ultralight_c_dict.nfc b/applications/main/nfc/resources/nfc/assets/mf_ultralight_c_dict.nfc index fa5dbb1fb..b68e82867 100644 --- a/applications/main/nfc/resources/nfc/assets/mf_ultralight_c_dict.nfc +++ b/applications/main/nfc/resources/nfc/assets/mf_ultralight_c_dict.nfc @@ -8,6 +8,10 @@ 49454D4B41455242214E4143554F5900 # Modified Semnox Key (IEMKAERB!NACUOYF) 49454D4B41455242214E4143554F5946 +# iKey UL-C Magic Tag Key +921AAC1654B8D83A669A9012AE7C1222 +# Kyiv NKS Vizit Key +D2C8C2CEC8CCC420204E4042544E5846 # Mix of Proxmark and ChameleonMiniLiveDebugger 00000000000000000000000000000000 From d4658779192fa9cd2fc7b54cae396215bf337b7c Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 12 Oct 2025 23:36:50 +0300 Subject: [PATCH 130/192] upd changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9deca902a..05406c7c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ * SubGHz: Add **ZKTeco 430.5 MHz** add manually support * SubGHz: Add variant of 'Add Manually' menu with manual editing for each value (PR #909 #911 #914 | by @MrLego8-9) * SubGHz: Temporarily remove HoneywellSec protocol due to unstable decoding and incorrect encoding +* NFC: Returning fix for reading PWD locked MFUL (PR #922 | by @mishamyte) +* NFC: Added UL-C keys to the dictionary (PR #923 | by @mishamyte) * NFC: Add MIFARE Classic "Show Keys" UI (by @aaronjamt) * Apps: HID PTT: adding global zoom and google meet shortcuts for MacOS (PR #921 | by @hryamzik) * OFW: NFC FeliCa: Service Directory Traverse + Dump All Unencrypted-Readable Services' Blocks From b03316d89d652035fb3829d09b1ca8473e89cb56 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 13 Oct 2025 01:32:43 +0300 Subject: [PATCH 131/192] add support for top44rbn remotes --- CHANGELOG.md | 1 + lib/subghz/protocols/came_atomo.c | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05406c7c2..f05a88aab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## Main changes - Current API: 87.0 +* SubGHz: Add support for **Came Atomo (TOP44RBN)** remotes (thanks @mishamyte for recordings) * SubGHz: Add **Elplast 18bit** static code protocol (hello Hackcat ^_^) * SubGHz: Try to **decode BFT** (2 buttons remotes only) **on the fly** in regular Read mode (no more KL Unknown and all of that for free?!) (for 4 button remote follow docs [here](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md)) * SubGHz: **Tune Linear** (edited by @WillyJL in PR #919 #920) (add better EZCode support) and **Dickert MAHS** protocol decoders diff --git a/lib/subghz/protocols/came_atomo.c b/lib/subghz/protocols/came_atomo.c index 5b9e6defd..2162169b2 100644 --- a/lib/subghz/protocols/came_atomo.c +++ b/lib/subghz/protocols/came_atomo.c @@ -418,8 +418,11 @@ void subghz_protocol_decoder_came_atomo_feed(void* context, bool level, uint32_t ManchesterEvent event = ManchesterEventReset; switch(instance->decoder.parser_step) { case CameAtomoDecoderStepReset: - if((!level) && (DURATION_DIFF(duration, subghz_protocol_came_atomo_const.te_long * 60) < - subghz_protocol_came_atomo_const.te_delta * 40)) { + // There are two known options for the header: 72K us (TOP42R, TOP44R) or 12k us (found on TOP44RBN) + if((!level) && ((DURATION_DIFF(duration, subghz_protocol_came_atomo_const.te_long * 10) < + subghz_protocol_came_atomo_const.te_delta * 20) || + (DURATION_DIFF(duration, subghz_protocol_came_atomo_const.te_long * 60) < + subghz_protocol_came_atomo_const.te_delta * 40))) { //Found header CAME instance->decoder.parser_step = CameAtomoDecoderStepDecoderData; instance->decoder.decode_data = 0; From dbc5895b30f3afb2f692dd7a8681945832cf80a3 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 13 Oct 2025 22:26:31 +0300 Subject: [PATCH 132/192] upd anims --- .../external/L1_Halloween_128x64/frame_0.png | Bin 0 -> 1597 bytes .../external/L1_Halloween_128x64/frame_1.png | Bin 0 -> 1592 bytes .../external/L1_Halloween_128x64/frame_2.png | Bin 0 -> 1605 bytes .../external/L1_Halloween_128x64/frame_3.png | Bin 0 -> 1594 bytes .../external/L1_Halloween_128x64/meta.txt | 14 ++++++++++++++ assets/dolphin/external/manifest.txt | 7 +++++++ 6 files changed, 21 insertions(+) create mode 100644 assets/dolphin/external/L1_Halloween_128x64/frame_0.png create mode 100644 assets/dolphin/external/L1_Halloween_128x64/frame_1.png create mode 100644 assets/dolphin/external/L1_Halloween_128x64/frame_2.png create mode 100644 assets/dolphin/external/L1_Halloween_128x64/frame_3.png create mode 100644 assets/dolphin/external/L1_Halloween_128x64/meta.txt diff --git a/assets/dolphin/external/L1_Halloween_128x64/frame_0.png b/assets/dolphin/external/L1_Halloween_128x64/frame_0.png new file mode 100644 index 0000000000000000000000000000000000000000..f69a43f5e75a4efee946984b5f551d2af692bf72 GIT binary patch literal 1597 zcmV-D2EzG?P)Px)_en%SRCt{2T-kEuFbK4_=Kp`$`=k`X91TLp#O|upB(@GD5J%gS^SZ9<*bwJ= z-mYyb>R036Y5_RT^Zcd!ru6H&M5B1ohtFyO8n|-lX@8YJ4(F~R;Kdwl7Xn_)#cKc$ z$7=u&$7=u&$7=u&$7=u&$2L5`H^SmBz6Fmn0F6+JD6fsd`)Jp9#$$<~?6ZdRJl%Fk zn_Zw9fJeQ4%K{Nd&mOdPoR-$we~S@_NuYK*=+9pQU6W_{O|k7~#4`$OOzB>a3^xye zS~tZAgIMK(tT?WnYVH}RF?k-vn@bvlUgP(pp66E*=n15u*B~+$T#;}^H!ETk=y=lO z@AkK(XnLaRX#;FJoG9tYd{q)9L)*%+**4{v6_dG#zjL}gIfk>EGy%_SLGEE zS-ibK*N`zh+FtV*^d2gUX#iFnvV=s+q==9sAgwD?;(2+2H9u{e1>#LCIerX4vPx7z zQH63hwes{w-FK#sgzD-+^D3@EOI1MRR8-Wvk@|WyDu^H{rjIZPL?)34iV`CgK$EiS zOgw`MDp)#bt?vkKLCtHjRInBMtVz9?Wo>{p-U^S=6EeN5-trPrBe6VsnE_NOB66A| zN$M`ttVir1eKUrj?NjP$pfnJ|czNwcK_XUGK%;Td{HI;dtU+7*@E1ZPV#7kzsw3@I zE1yk;CAGPE0LiNUf)XUV&FgJG zQajV@Rqa+`OAio%NAn_dszz6%28Ft2%_CF+8JEkNG5}=38Du?6FS$b7X4i%8iDn4M zy0Z_}tmg{uJAu*3su0PJt#;@dXlO%YyOblK;Gv;+y|?m?8H+Z4nhC7V1tQNJBJ=Lf zB3h-MwjW3-Dvd2c4FPmgt0o?qp*D9Z{ZvBRVDD6pSUX)GQbA8_nH$d4g28SI9gvC-Cm|%qu<&ia^R@BNT4MsX_Vu zwwUG_hMMk?RSoVEB_A=8X-(91#4{vXZSGp7Ps_(*celTAal zERp!!JASXRsb3DY7LieY)}3l- zU{=B;8BjGwh6Q9?%~)%*(~&9#EnzJ$V9n3NfJg*YDXD@KnaQk4uyTzCD}mIMAob&YwQF8^>?xmqy8j|Z z5+oACxW!psz)QL;7pKXu@fq}5PaW+6^)%kzSnYd0MGp{Fga%3ZJ&1TFD~ZiOGo+|2 z6x0(`DmC&6JqtjyWUVb%J~yx#F5a38^#T#Okv2RWtT2L-he7Px)@<~KNRCt{2TiK4IKn#Rc{{NS=PrY&+A63Rp!-kSZnSnms_%0^<`~7}z7sS5r zU)L@w>aFo-wE!CXzJDlxDE<9@n?~{A7+$Ld7|_a<-u@_m9`>uIfCuN`aw*`!xp)lV z$MG1zkK-|bAID<=KaR%$ejJzK0q%&1ySNK}&Hy|@iBaBIL(lQ9Ul~731XbTP?E9XM zL&jVMt^s7!>s=0rz1R%cgUar3Gz&t;Sa@*pBe8cqBX61M`ZYl z0E{m=!UMWuih+kWmrD%DtLymtb;E3`i*c5R@iTdHOb2~yfS`+BKz44Y}y$EBii>9)Hv<;Lw4+R^- zSJo8P-qpemJx9gxHxLN3+f-OfIb7yc1Ze3HQf}`9QZJOn*%m}W;zaL4NSydlJ3D!uWh~8!ZD|&OL zNWBU*n-M$6+=?N1&#Ck_&{_y(4Oe>VY9sJyT(qzp*DGi6&OZGgLM&n<0&CUGxYf#+ z=}kw>!L>+skR`e*=Yt}E4mno{r5afZx{C6nwCRy0o6tls|!Q00&N zC|J@g8uyg~^x)NbfA#*ekfm0uR?u^!G1gkv7_$bjTD-d}VV3+Q{vD_btZPhh=Z(>0 zUrZ~t!U$&3&CK|S^~~7L0W|klXn2q5^TtR}JOE2U9>M5=I}MJJUy?^s9z-^)m1AU- zGOz`-)geT<26~%xc#I}2R;Qedd6VO2MT>|L zL}fubk1qqTdT?2|pqt+fY&$t{h7nj!kwu3wCzJ2%yMc(6b*6)v^fE)q9tr6yXmX~& zjqZPaH_-Y!y5|YC3bN&qUJwy79g3TEc7|K1;Dsu;qgS@p?Cc^Cc`V|lOOCT6LB|=E zy$hJpLPms8RuJhLpz2*`L_sFCdGp8kmp*9Ldq$pyD7Bkhvi8hEyhq}6M*?f_4#@S7 z%ItnQG&)2@`IQI4vSXQXGHV?L;xL_iAdY8^`RD>NFQ{5uwL6evjQ}RniN2{d9Ew3@}RHHtc1JJa0#8$qbiW&stYrle~yN zFWm-eg2hs{cb(;`SCUFrgkhtD_t3LB)p3!>OF87+X*>XG1hes`xQ+D!TKlm2!v zQhY`0RJx2aNEM{u%xVP-TG>&dj7CUS$vwM~VhoJj$QYdtRv1CczlCd0w;n__ZFHg2*s=&& z-RH6qpsOfE)?@cVJl9G^UaDXe*JVsnj%5+xsFIJ&uX>VKSTV}<9?UA79Rc=j+t!^H q0OH-&oU9PE=64pX3?c)b{rMLrmm-QE=NLNx0000Px)|4BqaRCt{2T-&mnFbp)E^Z&n`J~^W(y6nm}m+s^_}r^4&Vt&sL_t&aNG9lQJdZH zvqDhiT|+mlB~^QEcZVARGU@dW4;MkMzdtKtG~MPdhW21afM0zRm*sHJ*g7)YDHVK0 z03^IwZISi}u8ch0h@Rmh)IFSoh?5s(X=^fvk6DhOxZ; zN}!>>13htQ7}rjy_f8&6>(Ab}Xe0<7zzaiO+rGFN?N-BjF624UGLRVqf#)Qi1Hrn6 ztOBau%uG1iZ#GzCR!T)?4I`*1lV4>Zb1G(#D38^4T{tt!kY7IEfp!aa%z$Ls1sC;xJ%k;&5pFKt@{AhJ;*{Wsc*`spsyX9DpeY zy}z#jtHND%`lH%CJ2b8NPJzyC_1Wrs04;&`YiM5&J46O;=+-_BY4L#$W)IkB-O2$v zDvDM#*46OTRPE%w9ZRrLeqa*q*4oFFh{3Zthv^fIwSsh>g^F+#aRPKdtoI0}ZJTm{ z>V&Is_57niqZt~hD(O*pFP^tbBBGG{Mk;R#U)0)WSk<>O|r1= zRTfs-@y3FLph&U06Oi@yIz2L2)}|uV$i8|m>mJe{X-_GEtDT^dZYpvpI;JI8qj|Tt zx70nBu&vzlErp;eqEvM*T{!gJ(wpl*yx~tb&X!>$V=#XB0iQj*no; z$r)K~4g9x(E42V??6dRa%=uXDN7o^d2`{}3 z%QX#uYliZqjXq1FS5IBSjEh3qm+Q93V4_o}{_~XBl7S!E7@a z4xsz3b^B*E;2^;H%#gSiD8w&r*Q*(q|V=U_m~2b>2VE<1=O)u~CFa z%vAld{Ak_x)_;d|Fj_jx{%;Nt5j=D3)#EK))yrV)3gHctI8GqE-K7^pmFz94D zw^o3VIT`*KW$g@%aEZzs$YHYDd?|rmJt-$-7C7pmsKX&LacjQFwOWK)nHEM4rAY!= z5yFG_(Aj1%Tq0VZtc4@=u~5yRidkc#&g9J}ue()1R41dX+UvLSq@hOF>yrJnC^TCE zjs89p5&3nc;0$`RqLAwKlh=^aY$ce}RE-Q#l~Oqj5l6GG$%6-_fDuH-UyaBiSstS| zD|31a-lk`Ls_mkEm+MGVz|0&tYW&f4cwz}Mu4g>W8LT~{AdXqMS*C3od%$9 z0_Ew+$QR=AY?ZOHl*-`M^pP=jJIe@GKi${6mT4N9@#Sy$KZHaNmO*`<+E_Bys#cJ+mMxr zoQ*N87g3n`tg)_g0*V97LNp#b7h*Uo9WiWP14&gCC%?i8-W*`Ik!OJ+ld_M;L9A$n zp6XSwxYlV-&;~yUSqPx)^hrcPRCt{2TF?KB^t$xK73XS(7=^TPy4I4%GvA)aw%#XMxJ!)`}>Nw|R=9HHcM!b~@y`E>GY%cA7n%{H`)u+6S{%~aA`|aImPqSD)`^yZ$ozEP zED&#E8Y1NZs!j!hT?`?r%uK21&0B%2C%6$okyBBTL`m~CtTqk}y;QIi(?=MDH0Mng zi3yy|bXi6qp{MK`S*}KNC27s0-hWi}n4yBDjr5ELxsMn@Z`IH{Mg@?p-trPrBe6Vs z*Z`{Bz9(2_%!unKOS z>~=&vlvg=78+H|*Vx)c^D8tgObY?M zkT~$3u~x#8UsJqQ&-3vl!{Xu8Og51uFnSs*Yxz-0=PaPc98Koh_lO))pO06|W)!en zBDO=uUzMP04}7vAP<r3M&cH4wpOQ=MlYh5P zaaJa-%xFR_RR|U!B8U#lcTBGA19Jo@~zA zS!2!iq7iIK1Z82>$4EYdk|%sz-q&k~jGF;+fcQRye;qu^3slB(O}=O%7Lf$!saPGEGh3LrJDbiGI3Xo!#uHfk{Fyr^+#yntn#sSr?I9(tsR)>e`$3%aJ6 zv07qY`8_L%?A;IOw}B(M0B!7ij6gaRo_A<{Jo#?F4UAC3SvNRK`4LLC$EB~J z%p*y!b^mJs7AGxbnAydS-YBnBk~XbEp3ymhHD}MfxAy@wcVX4aJGk>6;-*Ue*?Up3 zhLV31xB5AOl(+EvUP8ok05lQgn6Vd;(5Cfo?LT~>UhS!!G&ClS;&NQR4lu7StN|5^TzS=dfr1g|fKcN>w z=;`s2FT~|VtBe_>3X*VI9zg?Fwx)E+JtOjTe_n?4J;{%C+C29c@j#x-)RI|xEmBr# zb6R~o!{&0#{DeF}W^=5K<%%A4vZe1@Tf_(=awBbcI9Oo>C9j3^(=Kg<6*gv|(%2AB zm6>N-4^WvDdgDdsLQbv}j-*^Bns8Pb6P06`2k2FDPyg&m(h{p^T$eUx6=r*Y^Ei%m ss|A3ZZmAEF2=e|~(mEQJ0gwLtA4(N8cxHshp8x;=07*qoM6N<$g2^=a*8l(j literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Halloween_128x64/meta.txt b/assets/dolphin/external/L1_Halloween_128x64/meta.txt new file mode 100644 index 000000000..9762d4caa --- /dev/null +++ b/assets/dolphin/external/L1_Halloween_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: 3 +Duration: 3600 +Active cooldown: 0 + +Bubble slots: 0 diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index 951a4cdf7..2048d09c4 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -245,3 +245,10 @@ Max butthurt: 13 Min level: 1 Max level: 3 Weight: 4 + +Name: L1_Halloween_128x64 +Min butthurt: 0 +Max butthurt: 14 +Min level: 1 +Max level: 3 +Weight: 4 From 9506ccde2e54ea5c2cd5a5e95d8d960f54b40110 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 13 Oct 2025 22:56:32 +0300 Subject: [PATCH 133/192] upd changelog --- CHANGELOG.md | 46 ++---------------------- lib/nfc/protocols/felica/felica_poller.c | 9 +++-- 2 files changed, 8 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f05a88aab..a6bf4b4a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,51 +1,9 @@ ## Main changes - Current API: 87.0 -* SubGHz: Add support for **Came Atomo (TOP44RBN)** remotes (thanks @mishamyte for recordings) -* SubGHz: Add **Elplast 18bit** static code protocol (hello Hackcat ^_^) -* SubGHz: Try to **decode BFT** (2 buttons remotes only) **on the fly** in regular Read mode (no more KL Unknown and all of that for free?!) (for 4 button remote follow docs [here](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md)) -* SubGHz: **Tune Linear** (edited by @WillyJL in PR #919 #920) (add better EZCode support) and **Dickert MAHS** protocol decoders -* SubGHz: RAW protocol fixes (by @WillyJL) -* SubGHz: Add **ZKTeco 430.5 MHz** add manually support -* SubGHz: Add variant of 'Add Manually' menu with manual editing for each value (PR #909 #911 #914 | by @MrLego8-9) -* SubGHz: Temporarily remove HoneywellSec protocol due to unstable decoding and incorrect encoding -* NFC: Returning fix for reading PWD locked MFUL (PR #922 | by @mishamyte) -* NFC: Added UL-C keys to the dictionary (PR #923 | by @mishamyte) -* NFC: Add MIFARE Classic "Show Keys" UI (by @aaronjamt) -* Apps: HID PTT: adding global zoom and google meet shortcuts for MacOS (PR #921 | by @hryamzik) -* OFW: NFC FeliCa: Service Directory Traverse + Dump All Unencrypted-Readable Services' Blocks -* OFW: **NFC CLI commands** -* OFW: LFRFID: **Show ISO-3166 Country Names For Pet Chips** -* OFW: **JS views finished** -* OFW: BLE: improved pairing security -* OFW: FeliCa Emulation: Handle certain Polling commands in firmware -* OFW PR 4287: Fix Ultralight EV1 regression (by @noproto) -* OFW PR 4271: NFC: **Ultralight C NFC App Key Management, Dictionary Attack** (by @noproto) -* OFW PR 4265: NFC: **Fix read crash** with unexpectedly large MFC AUTH(0) response (by @WillyJL) -* OFW PR 4251: CLI: **Fix long delay** with quick connect/disconnect (by @WillyJL) -* LFRFID: Add additional procotols supported by **EM4305** chipset (by @jamisonderek) +* 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 -* SubGHz: Fix crash in add manually menu -* OFW PR 4293: NFC FeliCa Improvement: Dump All Systems (by @zinongli) -* OFW PR 4285: ViewStack: Store View by value to save memory (by @CookiePLMonster) -* OFW PR 4290: Storage: Dont send mount event if SD mounted at boot (by @WillyJL) -* OFW PR 4283: NFC lib: Expose nfc_common.h (by @zinongli) -* OFW: Fix wrbl command tooltip -* OFW: VSCode: Reduce file watcher resource usage -* OFW: cli: Buzzer command -* OFW: Update demo_windows.txt -* OFW: Fix PVS warnings -* OFW: NFC: Amusement IC Card Parser (FeliCa Lite & Lite-S) -* OFW: hid_app mouse clicker: make mouse button selectable -* OFW: JS: Expose button event type in gui/widget button callback -* OFW: NFC: MFC 1k Banapass Parser -* OFW: GUI Bug Fix: Number Input Save Icon -* Add possibility to use custom buttons when using the SubGHz remote app (by @MrLego8-9) -* Input Settings: Add Vibro Trigger option (by @956MB & @WillyJL) -* BT Remote: Add Rename Option (by @aaronjamt & @WillyJL) -* Simplify Bad USB BLE profile (by @aaronjamt & @WillyJL) -* NFC: Fix incorrect Saflok year formula (by @Eltrick) -* JS: Expose button event type in gui/widget button callback (by @WillyJL) +* Enable halloween anim

#### Known NFC post-refactor regressions list: - Mifare Mini clones reading is broken (original mini working fine) (OFW) diff --git a/lib/nfc/protocols/felica/felica_poller.c b/lib/nfc/protocols/felica/felica_poller.c index 98e07df42..056ed4320 100644 --- a/lib/nfc/protocols/felica/felica_poller.c +++ b/lib/nfc/protocols/felica/felica_poller.c @@ -486,9 +486,12 @@ NfcCommand felica_poller_state_handler_read_lite_blocks(FelicaPoller* instance) NfcCommand felica_poller_state_handler_read_success(FelicaPoller* instance) { FURI_LOG_D(TAG, "Read Success"); - if(!instance->auth.context.auth_status.internal || - !instance->auth.context.auth_status.external) { - instance->data->blocks_read--; + if((!instance->auth.context.auth_status.internal || + !instance->auth.context.auth_status.external) && + instance->data->workflow_type == FelicaLite) { + if(instance->data->blocks_read != 0) { + instance->data->blocks_read--; + } instance->felica_event.type = FelicaPollerEventTypeIncomplete; } else { memcpy( From d673fd5573eaa2755f3004ec35a7445c2620cb6d Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 16 Oct 2025 10:15:26 +0300 Subject: [PATCH 134/192] return honeywellsec with some fixes --- CHANGELOG.md | 1 + ReadMe.md | 2 +- lib/subghz/protocols/honeywell.c | 408 ++++++++++++++++++++++++++ lib/subghz/protocols/honeywell.h | 109 +++++++ lib/subghz/protocols/protocol_items.c | 1 + lib/subghz/protocols/protocol_items.h | 1 + 6 files changed, 521 insertions(+), 1 deletion(-) create mode 100644 lib/subghz/protocols/honeywell.c create mode 100644 lib/subghz/protocols/honeywell.h diff --git a/CHANGELOG.md b/CHANGELOG.md index a6bf4b4a8..414be0a43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## Main changes - Current API: 87.0 +* SubGHz: Return Honeywell Sec with fixes and improvements (by htotoo & LiQuiDz & xMasterX) * 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 diff --git a/ReadMe.md b/ReadMe.md index 5e8903f9c..fee5aeb74 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -81,7 +81,7 @@ Before getting started: > - FAAC SLH, BFT Mitto / Somfy Telis / Nice Flor S / CAME Atomo, etc. manual creation with programming new remote into receiver (use button 0xF for BFT Mitto, 0x8 (Prog) on Somfy Telis, (right arrow button for other protocols)) > - Debug mode counter increase settings (+1 → +5, +10, default: +1) > - Debug PIN output settings for protocol development -> - Ignore options - Alarms: Hollarm, GangQi | Cars: Kia, Starline, ScherKhan | Sensors: Magellan, Honeywell WDB (doorbells), Legrand (doorbells), Feron (RGB lights) +> - Ignore options - Alarms: Hollarm, GangQi | Cars: Kia, Starline, ScherKhan | Sensors: Magellan, Honeywell Sec, Honeywell WDB (doorbells), Legrand (doorbells), Feron (RGB lights) >
>
diff --git a/lib/subghz/protocols/honeywell.c b/lib/subghz/protocols/honeywell.c new file mode 100644 index 000000000..66e1c5053 --- /dev/null +++ b/lib/subghz/protocols/honeywell.c @@ -0,0 +1,408 @@ +#include "honeywell.h" + +#include +#include +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +// Created by HTotoo 2023-10-30 +// Got a lot of help from LiQuiDz. +// Protocol decoding help from: https://github.com/merbanan/rtl_433/blob/master/src/devices/honeywell.c +// Fixes and style changes to use similar codebase as other protocols by @xMasterX 2025-10 + +/* +64 bit packets, repeated multiple times per open/close event. + +Protocol whitepaper: "DEFCON 22: Home Insecurity" by Logan Lamb. + +Data layout: + + PP PP C IIIII EE SS SS + +- P: 16bit Preamble and sync bit (always ff fe) +- C: 4bit Channel +- I: 20bit Device serial number / or counter value +- E: 8bit Event, where 0x80 = Open/Close, 0x04 = Heartbeat / or id +- S: 16bit CRC +*/ + +#define TAG "SubGhzProtocolHoneywell" + +static const SubGhzBlockConst subghz_protocol_honeywell_const = { + .te_long = 280, + .te_short = 143, + .te_delta = 51, + .min_count_bit_for_found = 62, +}; + +struct SubGhzProtocolDecoderHoneywell { + SubGhzProtocolDecoderBase base; + SubGhzBlockGeneric generic; + SubGhzBlockDecoder decoder; + ManchesterState manchester_saved_state; +}; + +struct SubGhzProtocolEncoderHoneywell { + SubGhzProtocolEncoderBase base; + SubGhzBlockGeneric generic; + SubGhzProtocolBlockEncoder encoder; +}; + +const SubGhzProtocolDecoder subghz_protocol_honeywell_decoder = { + .alloc = subghz_protocol_decoder_honeywell_alloc, + .free = subghz_protocol_decoder_honeywell_free, + .feed = subghz_protocol_decoder_honeywell_feed, + .reset = subghz_protocol_decoder_honeywell_reset, + .get_hash_data = subghz_protocol_decoder_honeywell_get_hash_data, + .serialize = subghz_protocol_decoder_honeywell_serialize, + .deserialize = subghz_protocol_decoder_honeywell_deserialize, + .get_string = subghz_protocol_decoder_honeywell_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_honeywell_encoder = { + .alloc = subghz_protocol_encoder_honeywell_alloc, + .free = subghz_protocol_encoder_honeywell_free, + .deserialize = subghz_protocol_encoder_honeywell_deserialize, + .stop = subghz_protocol_encoder_honeywell_stop, + .yield = subghz_protocol_encoder_honeywell_yield, +}; + +const SubGhzProtocol subghz_protocol_honeywell = { + .name = SUBGHZ_PROTOCOL_HONEYWELL_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send | + SubGhzProtocolFlag_Sensors, + .encoder = &subghz_protocol_honeywell_encoder, + .decoder = &subghz_protocol_honeywell_decoder, + +}; + +static void subghz_protocol_decoder_honeywell_addbit(void* context, bool data); + +void* subghz_protocol_decoder_honeywell_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderHoneywell* instance = malloc(sizeof(SubGhzProtocolDecoderHoneywell)); + instance->base.protocol = &subghz_protocol_honeywell; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void* subghz_protocol_encoder_honeywell_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderHoneywell* instance = malloc(sizeof(SubGhzProtocolEncoderHoneywell)); + + instance->base.protocol = &subghz_protocol_honeywell; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 4; + instance->encoder.size_upload = 64 * 2 + 10; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_honeywell_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderHoneywell* instance = context; + free(instance->encoder.upload); + free(instance); +} + +void subghz_protocol_decoder_honeywell_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHoneywell* instance = context; + free(instance); +} + +uint16_t subghz_protocol_honeywell_crc16( + uint8_t const message[], + unsigned nBytes, + uint16_t polynomial, + uint16_t init) { + uint16_t remainder = init; + unsigned byte, bit; + + for(byte = 0; byte < nBytes; ++byte) { + remainder ^= message[byte] << 8; + for(bit = 0; bit < 8; ++bit) { + if(remainder & 0x8000) { + remainder = (remainder << 1) ^ polynomial; + } else { + remainder = (remainder << 1); + } + } + } + return remainder; +} + +static LevelDuration + subghz_protocol_encoder_honeywell_add_duration_to_upload(ManchesterEncoderResult result) { + LevelDuration data = {.duration = 0, .level = 0}; + switch(result) { + case ManchesterEncoderResultShortLow: + data.duration = subghz_protocol_honeywell_const.te_short; + data.level = false; + break; + case ManchesterEncoderResultLongLow: + data.duration = subghz_protocol_honeywell_const.te_long; + data.level = false; + break; + case ManchesterEncoderResultLongHigh: + data.duration = subghz_protocol_honeywell_const.te_long; + data.level = true; + break; + case ManchesterEncoderResultShortHigh: + data.duration = subghz_protocol_honeywell_const.te_short; + data.level = true; + break; + + default: + furi_crash("SubGhz: ManchesterEncoderResult is incorrect."); + break; + } + return level_duration_make(data.level, data.duration); +} + +static void + subghz_protocol_encoder_honeywell_get_upload(SubGhzProtocolEncoderHoneywell* instance) { + furi_assert(instance); + size_t index = 0; + + ManchesterEncoderState enc_state; + manchester_encoder_reset(&enc_state); + ManchesterEncoderResult result; + + for(uint8_t i = 63; i > 0; i--) { + if(!manchester_encoder_advance( + &enc_state, bit_read(instance->generic.data, i - 1), &result)) { + instance->encoder.upload[index++] = + subghz_protocol_encoder_honeywell_add_duration_to_upload(result); + manchester_encoder_advance( + &enc_state, bit_read(instance->generic.data, i - 1), &result); + } + instance->encoder.upload[index++] = + subghz_protocol_encoder_honeywell_add_duration_to_upload(result); + } + instance->encoder.upload[index] = subghz_protocol_encoder_honeywell_add_duration_to_upload( + manchester_encoder_finish(&enc_state)); + if(level_duration_get_level(instance->encoder.upload[index])) { + index++; + } + //Send delay + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_honeywell_const.te_long * 300); + + instance->encoder.size_upload = index; +} + +SubGhzProtocolStatus + subghz_protocol_encoder_honeywell_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderHoneywell* instance = context; + SubGhzProtocolStatus res = SubGhzProtocolStatusError; + do { + if(SubGhzProtocolStatusOk != + subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_encoder_honeywell_get_upload(instance); + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + instance->encoder.is_running = true; + + res = SubGhzProtocolStatusOk; + } while(false); + + return res; +} + +void subghz_protocol_encoder_honeywell_stop(void* context) { + SubGhzProtocolEncoderHoneywell* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_honeywell_yield(void* context) { + SubGhzProtocolEncoderHoneywell* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = false; + return level_duration_reset(); + } + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + return ret; +} + +void subghz_protocol_decoder_honeywell_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHoneywell* instance = context; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; +} + +void subghz_protocol_decoder_honeywell_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderHoneywell* instance = context; + + ManchesterEvent event = ManchesterEventReset; + if(!level) { + if(DURATION_DIFF(duration, subghz_protocol_honeywell_const.te_short) < + subghz_protocol_honeywell_const.te_delta) { + event = ManchesterEventShortLow; + } else if( + DURATION_DIFF(duration, subghz_protocol_honeywell_const.te_long) < + subghz_protocol_honeywell_const.te_delta * 2) { + event = ManchesterEventLongLow; + } + } else { + if(DURATION_DIFF(duration, subghz_protocol_honeywell_const.te_short) < + subghz_protocol_honeywell_const.te_delta) { + event = ManchesterEventShortHigh; + } else if( + DURATION_DIFF(duration, subghz_protocol_honeywell_const.te_long) < + subghz_protocol_honeywell_const.te_delta * 2) { + event = ManchesterEventLongHigh; + } + } + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + instance->manchester_saved_state, event, &instance->manchester_saved_state, &data); + if(data_ok) { + subghz_protocol_decoder_honeywell_addbit(instance, data); + } + } else { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } +} + +static void subghz_protocol_decoder_honeywell_addbit(void* context, bool data) { + SubGhzProtocolDecoderHoneywell* instance = context; + instance->decoder.decode_data = (instance->decoder.decode_data << 1) | data; + instance->decoder.decode_count_bit++; + + if(instance->decoder.decode_count_bit < 62) return; + + uint16_t preamble = (instance->decoder.decode_data >> 48) & 0xFFFF; + //can be multiple, since flipper can't read it well.. (it can, but the sensors are not that good, there are multiple of variations seen) + if(preamble == 0b0011111111111110 || preamble == 0b0111111111111110 || + preamble == 0b1111111111111110) { + uint8_t datatocrc[4]; + datatocrc[0] = (instance->decoder.decode_data >> 40) & 0xFFFF; + datatocrc[1] = (instance->decoder.decode_data >> 32) & 0xFFFF; + datatocrc[2] = (instance->decoder.decode_data >> 24) & 0xFFFF; + datatocrc[3] = (instance->decoder.decode_data >> 16) & 0xFFFF; + uint8_t channel = (instance->decoder.decode_data >> 44) & 0xF; + uint16_t crc_calc = 0; + if(channel == 0x2 || channel == 0x4 || channel == 0xA) { + // 2GIG brand + crc_calc = subghz_protocol_honeywell_crc16(datatocrc, 4, 0x8050, 0); + } else if(channel == 0x8) { + crc_calc = subghz_protocol_honeywell_crc16(datatocrc, 4, 0x8005, 0); + } else { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + return; + } + uint16_t crc = instance->decoder.decode_data & 0xFFFF; + if(crc == crc_calc) { + //the data is good. process it. + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = + instance->decoder + .decode_count_bit; //maybe set it to 64, and hack the first 2 bits to 1! will see if replay needs it + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + return; + } + } else if(instance->decoder.decode_count_bit >= 64) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + return; + } +} + +uint8_t subghz_protocol_decoder_honeywell_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHoneywell* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus subghz_protocol_decoder_honeywell_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderHoneywell* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + subghz_protocol_decoder_honeywell_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderHoneywell* instance = context; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_honeywell_const.min_count_bit_for_found); +} + +void subghz_protocol_decoder_honeywell_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderHoneywell* instance = context; + + instance->generic.serial = (instance->generic.data >> 24) & 0xFFFFF; + uint8_t sensor_status = (instance->generic.data >> 16) & 0xFF; + uint16_t crc = instance->generic.data & 0xFFFF; + + uint8_t channel = (instance->generic.data >> 44) & 0xF; + uint8_t contact = (sensor_status & 0x80) >> 7; + uint8_t tamper = (sensor_status & 0x40) >> 6; + uint8_t reed = (sensor_status & 0x20) >> 5; + uint8_t alarm = (sensor_status & 0x10) >> 4; + uint8_t battery_low = (sensor_status & 0x08) >> 3; + uint8_t heartbeat = (sensor_status & 0x04) >> 2; + + furi_string_cat_printf( + output, + "%s\r\n%dbit " + "Sn:%07lu Ch:%u\r\n" + "BatLow:%d Hb: %d Cont: %s\r\n" + "Reed: %u Alrm: %u Tmpr: %u\r\n" + "CRC: %04X", + instance->generic.protocol_name, + instance->generic.data_count_bit, + instance->generic.serial, + channel, + battery_low, + heartbeat, + contact ? "open" : "closed", + reed, + alarm, + tamper, + crc); +} diff --git a/lib/subghz/protocols/honeywell.h b/lib/subghz/protocols/honeywell.h new file mode 100644 index 000000000..92ec38a44 --- /dev/null +++ b/lib/subghz/protocols/honeywell.h @@ -0,0 +1,109 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_HONEYWELL_NAME "Honeywell Sec" + +typedef struct SubGhzProtocolDecoderHoneywell SubGhzProtocolDecoderHoneywell; +typedef struct SubGhzProtocolEncoderHoneywell SubGhzProtocolEncoderHoneywell; + +extern const SubGhzProtocolDecoder subghz_protocol_honeywell_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_honeywell_encoder; +extern const SubGhzProtocol subghz_protocol_honeywell; + +/** + * Allocate SubGhzProtocolEncoderHoneywell. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderHoneywell* pointer to a SubGhzProtocolEncoderHoneywell instance + */ +void* subghz_protocol_encoder_honeywell_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderHoneywell. + * @param context Pointer to a SubGhzProtocolEncoderHoneywell instance + */ +void subghz_protocol_encoder_honeywell_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderHoneywell instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_encoder_honeywell_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderHoneywell instance + */ +void subghz_protocol_encoder_honeywell_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderHoneywell instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_honeywell_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderHoneywell. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderHoneywell* pointer to a SubGhzProtocolDecoderHoneywell instance + */ +void* subghz_protocol_decoder_honeywell_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderHoneywell. + * @param context Pointer to a SubGhzProtocolDecoderHoneywell instance + */ +void subghz_protocol_decoder_honeywell_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderHoneywell. + * @param context Pointer to a SubGhzProtocolDecoderHoneywell instance + */ +void subghz_protocol_decoder_honeywell_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderHoneywell instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_honeywell_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderHoneywell instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_honeywell_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderHoneywell. + * @param context Pointer to a SubGhzProtocolDecoderHoneywell instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus subghz_protocol_decoder_honeywell_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderHoneywell. + * @param context Pointer to a SubGhzProtocolDecoderHoneywell instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_decoder_honeywell_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderHoneywell instance + * @param output Resulting text + */ +void subghz_protocol_decoder_honeywell_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index 27297f293..e7b282b8a 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -44,6 +44,7 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = { &subghz_protocol_kinggates_stylo_4k, &subghz_protocol_bin_raw, &subghz_protocol_mastercode, + &subghz_protocol_honeywell, &subghz_protocol_legrand, &subghz_protocol_dickert_mahs, &subghz_protocol_gangqi, diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index ab7fd14f8..9820ee9e7 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -45,6 +45,7 @@ #include "kinggates_stylo_4k.h" #include "bin_raw.h" #include "mastercode.h" +#include "honeywell.h" #include "legrand.h" #include "dickert_mahs.h" #include "gangqi.h" From 94076e6c5c1890cdc6b565254e24fea24bc7edcc Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 17 Oct 2025 14:05:25 +0300 Subject: [PATCH 135/192] honeywell show whole key and loop states instead --- lib/subghz/protocols/honeywell.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/subghz/protocols/honeywell.c b/lib/subghz/protocols/honeywell.c index 66e1c5053..6e212356e 100644 --- a/lib/subghz/protocols/honeywell.c +++ b/lib/subghz/protocols/honeywell.c @@ -375,9 +375,11 @@ void subghz_protocol_decoder_honeywell_get_string(void* context, FuriString* out furi_assert(context); SubGhzProtocolDecoderHoneywell* instance = context; + uint32_t code_found_hi = instance->generic.data >> 32; + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + instance->generic.serial = (instance->generic.data >> 24) & 0xFFFFF; uint8_t sensor_status = (instance->generic.data >> 16) & 0xFF; - uint16_t crc = instance->generic.data & 0xFFFF; uint8_t channel = (instance->generic.data >> 44) & 0xF; uint8_t contact = (sensor_status & 0x80) >> 7; @@ -391,9 +393,9 @@ void subghz_protocol_decoder_honeywell_get_string(void* context, FuriString* out output, "%s\r\n%dbit " "Sn:%07lu Ch:%u\r\n" - "BatLow:%d Hb: %d Cont: %s\r\n" - "Reed: %u Alrm: %u Tmpr: %u\r\n" - "CRC: %04X", + "LowBat:%d HB: %d Cont: %s\r\n" + "Key:%08lX%08lX\r\n" + "State: L1:%u L2:%u L3:%u L4:%u", instance->generic.protocol_name, instance->generic.data_count_bit, instance->generic.serial, @@ -401,8 +403,10 @@ void subghz_protocol_decoder_honeywell_get_string(void* context, FuriString* out battery_low, heartbeat, contact ? "open" : "closed", + code_found_hi, + code_found_lo, + contact, reed, alarm, - tamper, - crc); + tamper); } From bd02e2f53cf7dc818b6f6d279cc816f0b4242e33 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 17 Oct 2025 19:19:49 +0300 Subject: [PATCH 136/192] honeywell fix uint8 array using uint16 values, fix header, fix bits --- lib/subghz/protocols/honeywell.c | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/lib/subghz/protocols/honeywell.c b/lib/subghz/protocols/honeywell.c index 6e212356e..dd556d094 100644 --- a/lib/subghz/protocols/honeywell.c +++ b/lib/subghz/protocols/honeywell.c @@ -35,7 +35,7 @@ static const SubGhzBlockConst subghz_protocol_honeywell_const = { .te_long = 280, .te_short = 143, .te_delta = 51, - .min_count_bit_for_found = 62, + .min_count_bit_for_found = 64, }; struct SubGhzProtocolDecoderHoneywell { @@ -299,17 +299,19 @@ static void subghz_protocol_decoder_honeywell_addbit(void* context, bool data) { instance->decoder.decode_data = (instance->decoder.decode_data << 1) | data; instance->decoder.decode_count_bit++; - if(instance->decoder.decode_count_bit < 62) return; + if(instance->decoder.decode_count_bit < 62) { + return; + } uint16_t preamble = (instance->decoder.decode_data >> 48) & 0xFFFF; //can be multiple, since flipper can't read it well.. (it can, but the sensors are not that good, there are multiple of variations seen) if(preamble == 0b0011111111111110 || preamble == 0b0111111111111110 || preamble == 0b1111111111111110) { uint8_t datatocrc[4]; - datatocrc[0] = (instance->decoder.decode_data >> 40) & 0xFFFF; - datatocrc[1] = (instance->decoder.decode_data >> 32) & 0xFFFF; - datatocrc[2] = (instance->decoder.decode_data >> 24) & 0xFFFF; - datatocrc[3] = (instance->decoder.decode_data >> 16) & 0xFFFF; + datatocrc[0] = (instance->decoder.decode_data >> 40) & 0xFF; + datatocrc[1] = (instance->decoder.decode_data >> 32) & 0xFF; + datatocrc[2] = (instance->decoder.decode_data >> 24) & 0xFF; + datatocrc[3] = (instance->decoder.decode_data >> 16) & 0xFF; uint8_t channel = (instance->decoder.decode_data >> 44) & 0xF; uint16_t crc_calc = 0; if(channel == 0x2 || channel == 0x4 || channel == 0xA) { @@ -324,11 +326,15 @@ static void subghz_protocol_decoder_honeywell_addbit(void* context, bool data) { } uint16_t crc = instance->decoder.decode_data & 0xFFFF; if(crc == crc_calc) { - //the data is good. process it. - instance->generic.data = instance->decoder.decode_data; - instance->generic.data_count_bit = - instance->decoder - .decode_count_bit; //maybe set it to 64, and hack the first 2 bits to 1! will see if replay needs it + // Removing possible artifacts from higher bits and setting header to FF FE + instance->generic.data = + ((((((0xFF << 16) | ((instance->decoder.decode_data >> 40) & 0xFFFF)) << 16) | + ((instance->decoder.decode_data >> 24) & 0xFFFF)) + << 16) | + ((instance->decoder.decode_data >> 8) & 0xFFFF)) + << 8 | + (instance->decoder.decode_data & 0xFF); + instance->generic.data_count_bit = 64; if(instance->base.callback) instance->base.callback(&instance->base, instance->base.context); instance->decoder.decode_data = 0; From a1c8dfb61b3b667d7c4ee5d2c3e6b1827f7b6c9f Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 21 Oct 2025 01:32:02 +0300 Subject: [PATCH 137/192] subghz overflow experimental mode --- .../scenes/subghz_scene_radio_settings.c | 4 ++- lib/subghz/protocols/alutech_at_4n.c | 25 +++++++++++---- lib/subghz/protocols/came_atomo.c | 25 +++++++++++---- lib/subghz/protocols/faac_slh.c | 6 ++-- lib/subghz/protocols/hay21.c | 27 +++++++++++----- lib/subghz/protocols/keeloq.c | 31 +++++++++++++------ lib/subghz/protocols/kinggates_stylo_4k.c | 25 +++++++++++---- lib/subghz/protocols/nice_flor_s.c | 26 ++++++++++++---- lib/subghz/protocols/phoenix_v2.c | 25 +++++++++++---- lib/subghz/protocols/somfy_keytis.c | 25 +++++++++++---- lib/subghz/protocols/somfy_telis.c | 25 +++++++++++---- lib/subghz/protocols/star_line.c | 25 +++++++++++---- 12 files changed, 199 insertions(+), 70 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_radio_settings.c b/applications/main/subghz/scenes/subghz_scene_radio_settings.c index 8e808618b..93afe5e31 100644 --- a/applications/main/subghz/scenes/subghz_scene_radio_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_radio_settings.c @@ -26,7 +26,7 @@ const char* const debug_pin_text[DEBUG_P_COUNT] = { "17(1W)", }; -#define DEBUG_COUNTER_COUNT 16 +#define DEBUG_COUNTER_COUNT 17 const char* const debug_counter_text[DEBUG_COUNTER_COUNT] = { "+1", "+2", @@ -36,6 +36,7 @@ const char* const debug_counter_text[DEBUG_COUNTER_COUNT] = { "+10", "+50", "OVFL", + "OFEX", "No", "-1", "-2", @@ -54,6 +55,7 @@ const int32_t debug_counter_val[DEBUG_COUNTER_COUNT] = { 10, 50, 65535, + 65534, 0, -1, -2, diff --git a/lib/subghz/protocols/alutech_at_4n.c b/lib/subghz/protocols/alutech_at_4n.c index 873f61ba9..55978e90f 100644 --- a/lib/subghz/protocols/alutech_at_4n.c +++ b/lib/subghz/protocols/alutech_at_4n.c @@ -273,14 +273,27 @@ static bool subghz_protocol_alutech_at_4n_gen_data( instance->generic.serial = (uint32_t)(data >> 24) & 0xFFFFFFFF; } - if(instance->generic.cnt < 0xFFFF) { - if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + // Check for OFEX (overflow experimental) mode + if(furi_hal_subghz_get_rolling_counter_mult() != 0xFFFE) { + if(instance->generic.cnt < 0xFFFF) { + if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + instance->generic.cnt = 0; + } else { + instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); + } + } else if( + (instance->generic.cnt >= 0xFFFF) && + (furi_hal_subghz_get_rolling_counter_mult() != 0)) { instance->generic.cnt = 0; - } else { - instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); } - } else if((instance->generic.cnt >= 0xFFFF) && (furi_hal_subghz_get_rolling_counter_mult() != 0)) { - instance->generic.cnt = 0; + } else { + 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(); + } else { + instance->generic.cnt++; + } } crc = subghz_protocol_alutech_at_4n_decrypt_data_crc((uint8_t)(instance->generic.cnt & 0xFF)); data = (uint64_t)crc << 56 | (uint64_t)instance->generic.serial << 24 | diff --git a/lib/subghz/protocols/came_atomo.c b/lib/subghz/protocols/came_atomo.c index 2162169b2..7a3181341 100644 --- a/lib/subghz/protocols/came_atomo.c +++ b/lib/subghz/protocols/came_atomo.c @@ -185,14 +185,27 @@ static void subghz_protocol_encoder_came_atomo_get_upload( uint8_t pack[8] = {}; - if(instance->generic.cnt < 0xFFFF) { - if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + // Check for OFEX (overflow experimental) mode + if(furi_hal_subghz_get_rolling_counter_mult() != 0xFFFE) { + if(instance->generic.cnt < 0xFFFF) { + if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + instance->generic.cnt = 0; + } else { + instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); + } + } else if( + (instance->generic.cnt >= 0xFFFF) && + (furi_hal_subghz_get_rolling_counter_mult() != 0)) { instance->generic.cnt = 0; - } else { - instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); } - } else if((instance->generic.cnt >= 0xFFFF) && (furi_hal_subghz_get_rolling_counter_mult() != 0)) { - instance->generic.cnt = 0; + } else { + 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(); + } else { + instance->generic.cnt++; + } } // Save original button for later use diff --git a/lib/subghz/protocols/faac_slh.c b/lib/subghz/protocols/faac_slh.c index 489fbdbc2..d44385ac7 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() >= 0xFFFF)) { + if(!(furi_hal_subghz_get_rolling_counter_mult() >= 0xFFFE)) { 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() >= 0xFFFF)) { + if(!(furi_hal_subghz_get_rolling_counter_mult() >= 0xFFFE)) { if(temp_counter_backup < 0xFFFFF) { if((temp_counter_backup + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFFF) { @@ -240,7 +240,7 @@ static bool subghz_protocol_faac_slh_gen_data(SubGhzProtocolEncoderFaacSLH* inst } if(allow_zero_seed || (instance->generic.seed != 0x0)) { - if(!(furi_hal_subghz_get_rolling_counter_mult() >= 0xFFFF)) { + if(!(furi_hal_subghz_get_rolling_counter_mult() >= 0xFFFE)) { if(instance->generic.cnt < 0xFFFFF) { if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFFF) { diff --git a/lib/subghz/protocols/hay21.c b/lib/subghz/protocols/hay21.c index 1e3576459..d622e4446 100644 --- a/lib/subghz/protocols/hay21.c +++ b/lib/subghz/protocols/hay21.c @@ -145,17 +145,28 @@ static void subghz_protocol_encoder_hay21_get_upload(SubGhzProtocolEncoderHay21* instance->generic.btn = subghz_protocol_hay21_get_btn_code(); // Counter increment - if(instance->generic.cnt < 0xF) { - if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xF) { + // Check for OFEX (overflow experimental) mode + if(furi_hal_subghz_get_rolling_counter_mult() != 0xFFFE) { + if(instance->generic.cnt < 0xF) { + if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xF) { + instance->generic.cnt = 0; + } else { + instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); + } + if(furi_hal_subghz_get_rolling_counter_mult() >= 0xF) { + instance->generic.cnt = 0xF; + } + } else if(instance->generic.cnt >= 0xF) { instance->generic.cnt = 0; + } + } else { + if((instance->generic.cnt + 0x1) > 0xF) { + instance->generic.cnt = 0; + } else if(instance->generic.cnt >= 0x1 && instance->generic.cnt != 0xE) { + instance->generic.cnt = 0xE; } else { - instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); + instance->generic.cnt++; } - if(furi_hal_subghz_get_rolling_counter_mult() >= 0xF) { - instance->generic.cnt = 0xF; - } - } else if(instance->generic.cnt >= 0xF) { - instance->generic.cnt = 0; } // Reconstruction of the data diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index ace6e0d6e..22070f1ec 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -182,18 +182,29 @@ static bool subghz_protocol_keeloq_gen_data( if(counter_up && prog_mode == PROG_MODE_OFF) { // Counter increment conditions - // If counter is 0xFFFF we will reset it to 0 - if(instance->generic.cnt < 0xFFFF) { - // Increase counter with value set in global settings (mult) - if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + // Check for OFEX (overflow experimental) mode + if(furi_hal_subghz_get_rolling_counter_mult() != 0xFFFE) { + // If counter is 0xFFFF we will reset it to 0 + if(instance->generic.cnt < 0xFFFF) { + // Increase counter with value set in global settings (mult) + if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + instance->generic.cnt = 0; + } else { + instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); + } + } else if( + (instance->generic.cnt >= 0xFFFF) && + (furi_hal_subghz_get_rolling_counter_mult() != 0)) { instance->generic.cnt = 0; - } else { - instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); } - } else if( - (instance->generic.cnt >= 0xFFFF) && - (furi_hal_subghz_get_rolling_counter_mult() != 0)) { - instance->generic.cnt = 0; + } else { + 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(); + } else { + instance->generic.cnt++; + } } } if(prog_mode == PROG_MODE_OFF) { diff --git a/lib/subghz/protocols/kinggates_stylo_4k.c b/lib/subghz/protocols/kinggates_stylo_4k.c index 119a198bc..77624f5dc 100644 --- a/lib/subghz/protocols/kinggates_stylo_4k.c +++ b/lib/subghz/protocols/kinggates_stylo_4k.c @@ -155,14 +155,27 @@ static bool subghz_protocol_kinggates_stylo_4k_gen_data( } instance->generic.cnt = decrypt & 0xFFFF; - if(instance->generic.cnt < 0xFFFF) { - if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + // Check for OFEX (overflow experimental) mode + if(furi_hal_subghz_get_rolling_counter_mult() != 0xFFFE) { + if(instance->generic.cnt < 0xFFFF) { + if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + instance->generic.cnt = 0; + } else { + instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); + } + } else if( + (instance->generic.cnt >= 0xFFFF) && + (furi_hal_subghz_get_rolling_counter_mult() != 0)) { instance->generic.cnt = 0; - } else { - instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); } - } else if((instance->generic.cnt >= 0xFFFF) && (furi_hal_subghz_get_rolling_counter_mult() != 0)) { - instance->generic.cnt = 0; + } else { + 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(); + } else { + instance->generic.cnt++; + } } instance->generic.btn = (fix >> 17) & 0x0F; diff --git a/lib/subghz/protocols/nice_flor_s.c b/lib/subghz/protocols/nice_flor_s.c index 50adb43dc..201284724 100644 --- a/lib/subghz/protocols/nice_flor_s.c +++ b/lib/subghz/protocols/nice_flor_s.c @@ -151,15 +151,29 @@ static void subghz_protocol_encoder_nice_flor_s_get_upload( instance->encoder.size_upload = size_upload; } - if(instance->generic.cnt < 0xFFFF) { - if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + // Check for OFEX (overflow experimental) mode + if(furi_hal_subghz_get_rolling_counter_mult() != 0xFFFE) { + if(instance->generic.cnt < 0xFFFF) { + if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + instance->generic.cnt = 0; + } else { + instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); + } + } else if( + (instance->generic.cnt >= 0xFFFF) && + (furi_hal_subghz_get_rolling_counter_mult() != 0)) { instance->generic.cnt = 0; - } else { - instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); } - } else if((instance->generic.cnt >= 0xFFFF) && (furi_hal_subghz_get_rolling_counter_mult() != 0)) { - instance->generic.cnt = 0; + } else { + 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(); + } else { + instance->generic.cnt++; + } } + uint64_t decrypt = ((uint64_t)instance->generic.serial << 16) | instance->generic.cnt; uint64_t enc_part = subghz_protocol_nice_flor_s_encrypt(decrypt, file_name); diff --git a/lib/subghz/protocols/phoenix_v2.c b/lib/subghz/protocols/phoenix_v2.c index a6a8a7108..5764871fb 100644 --- a/lib/subghz/protocols/phoenix_v2.c +++ b/lib/subghz/protocols/phoenix_v2.c @@ -252,14 +252,27 @@ static bool btn = subghz_protocol_phoenix_v2_get_btn_code(); // Reconstruction of the data - if(instance->generic.cnt < 0xFFFF) { - if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + // Check for OFEX (overflow experimental) mode + if(furi_hal_subghz_get_rolling_counter_mult() != 0xFFFE) { + if(instance->generic.cnt < 0xFFFF) { + if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + instance->generic.cnt = 0; + } else { + instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); + } + } else if( + (instance->generic.cnt >= 0xFFFF) && + (furi_hal_subghz_get_rolling_counter_mult() != 0)) { instance->generic.cnt = 0; - } else { - instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); } - } else if((instance->generic.cnt >= 0xFFFF) && (furi_hal_subghz_get_rolling_counter_mult() != 0)) { - instance->generic.cnt = 0; + } else { + 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(); + } else { + instance->generic.cnt++; + } } uint64_t local_data_rev = subghz_protocol_blocks_reverse_key( diff --git a/lib/subghz/protocols/somfy_keytis.c b/lib/subghz/protocols/somfy_keytis.c index 0606b9bf5..07f950095 100644 --- a/lib/subghz/protocols/somfy_keytis.c +++ b/lib/subghz/protocols/somfy_keytis.c @@ -130,14 +130,27 @@ static bool instance->generic.cnt = (data >> 24) & 0xFFFF; instance->generic.serial = data & 0xFFFFFF; - if(instance->generic.cnt < 0xFFFF) { - if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + // Check for OFEX (overflow experimental) mode + if(furi_hal_subghz_get_rolling_counter_mult() != 0xFFFE) { + if(instance->generic.cnt < 0xFFFF) { + if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + instance->generic.cnt = 0; + } else { + instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); + } + } else if( + (instance->generic.cnt >= 0xFFFF) && + (furi_hal_subghz_get_rolling_counter_mult() != 0)) { instance->generic.cnt = 0; - } else { - instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); } - } else if((instance->generic.cnt >= 0xFFFF) && (furi_hal_subghz_get_rolling_counter_mult() != 0)) { - instance->generic.cnt = 0; + } else { + 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(); + } else { + instance->generic.cnt++; + } } uint8_t frame[10]; diff --git a/lib/subghz/protocols/somfy_telis.c b/lib/subghz/protocols/somfy_telis.c index fd41180d5..4218ad8c5 100644 --- a/lib/subghz/protocols/somfy_telis.c +++ b/lib/subghz/protocols/somfy_telis.c @@ -124,14 +124,27 @@ static bool subghz_protocol_somfy_telis_gen_data( btn = subghz_protocol_somfy_telis_get_btn_code(); - if(instance->generic.cnt < 0xFFFF) { - if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + // Check for OFEX (overflow experimental) mode + if(furi_hal_subghz_get_rolling_counter_mult() != 0xFFFE) { + if(instance->generic.cnt < 0xFFFF) { + if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + instance->generic.cnt = 0; + } else { + instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); + } + } else if( + (instance->generic.cnt >= 0xFFFF) && + (furi_hal_subghz_get_rolling_counter_mult() != 0)) { instance->generic.cnt = 0; - } else { - instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); } - } else if((instance->generic.cnt >= 0xFFFF) && (furi_hal_subghz_get_rolling_counter_mult() != 0)) { - instance->generic.cnt = 0; + } else { + 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(); + } else { + instance->generic.cnt++; + } } uint8_t frame[7]; diff --git a/lib/subghz/protocols/star_line.c b/lib/subghz/protocols/star_line.c index 991957abb..d6706704c 100644 --- a/lib/subghz/protocols/star_line.c +++ b/lib/subghz/protocols/star_line.c @@ -129,14 +129,27 @@ void subghz_protocol_encoder_star_line_free(void* context) { */ static bool subghz_protocol_star_line_gen_data(SubGhzProtocolEncoderStarLine* instance, uint8_t btn) { - if(instance->generic.cnt < 0xFFFF) { - if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + // Check for OFEX (overflow experimental) mode + if(furi_hal_subghz_get_rolling_counter_mult() != 0xFFFE) { + if(instance->generic.cnt < 0xFFFF) { + if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + instance->generic.cnt = 0; + } else { + instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); + } + } else if( + (instance->generic.cnt >= 0xFFFF) && + (furi_hal_subghz_get_rolling_counter_mult() != 0)) { instance->generic.cnt = 0; - } else { - instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); } - } else if((instance->generic.cnt >= 0xFFFF) && (furi_hal_subghz_get_rolling_counter_mult() != 0)) { - instance->generic.cnt = 0; + } else { + 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(); + } else { + instance->generic.cnt++; + } } uint32_t fix = btn << 24 | instance->generic.serial; uint32_t decrypt = btn << 24 | (instance->generic.serial & 0xFF) << 16 | instance->generic.cnt; From 01c168e351efb3077aef914a37512b6e122918a9 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 21 Oct 2025 01:44:30 +0300 Subject: [PATCH 138/192] subghz il100 smart add manually support --- applications/main/subghz/helpers/subghz_custom_event.h | 1 + applications/main/subghz/helpers/subghz_gen_info.c | 10 ++++++++++ .../main/subghz/scenes/subghz_scene_set_type.c | 1 + 3 files changed, 12 insertions(+) diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index d212de4ef..e118948a0 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -91,6 +91,7 @@ typedef enum { SetTypeSommer_FM238_868, SetTypeStilmatic, SetTypeIronLogic, + SetTypeIronLogicSmart, SetTypeDeaMio433, SetTypeDTMNeo433, SetTypeGibidi433, diff --git a/applications/main/subghz/helpers/subghz_gen_info.c b/applications/main/subghz/helpers/subghz_gen_info.c index 459f600ca..fe5cd0e8a 100644 --- a/applications/main/subghz/helpers/subghz_gen_info.c +++ b/applications/main/subghz/helpers/subghz_gen_info.c @@ -398,6 +398,16 @@ void subghz_scene_set_type_fill_generation_infos(GenInfo* infos_dest, SetType ty .keeloq.cnt = 0x05, .keeloq.manuf = "IronLogic"}; break; + case SetTypeIronLogicSmart: + gen_info = (GenInfo){ + .type = GenKeeloq, + .mod = "AM650", + .freq = 433920000, + .keeloq.serial = key & 0x00FFFFF0, + .keeloq.btn = 0x04, + .keeloq.cnt = 0x05, + .keeloq.manuf = "IL-100(Smart)"}; + break; case SetTypeStilmatic: gen_info = (GenInfo){ .type = GenKeeloq, diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index d3bb2482e..8ca5b95ab 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -39,6 +39,7 @@ static const char* submenu_names[SetTypeMAX] = { [SetTypeSommer_FM238_868] = "KL: Sommer fm2 868Mhz", [SetTypeStilmatic] = "KL: Stilmatic 433MHz", [SetTypeIronLogic] = "KL: IronLogic 433MHz", + [SetTypeIronLogicSmart] = "KL: IronLogic SM 433MHz", [SetTypeDeaMio433] = "KL: DEA Mio 433MHz", [SetTypeDTMNeo433] = "KL: DTM Neo 433MHz", [SetTypeGibidi433] = "KL: Gibidi 433MHz", From cf5761860f320b9756b663a6d96aa4ea655b4a33 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 21 Oct 2025 01:48:02 +0300 Subject: [PATCH 139/192] upd changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 414be0a43..70e041f44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## Main changes - Current API: 87.0 +* 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) * 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) From 3e968069620215b3420467a8ade5249b7ee1dd8f Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 21 Oct 2025 04:12:22 +0300 Subject: [PATCH 140/192] honeywell read old files with 62-63bits change them on the fly to new format during reading, files are not replaced, they will contain old format, you can fix them manually by replacing header to FF FE and bits to 64 --- lib/subghz/protocols/honeywell.c | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/subghz/protocols/honeywell.c b/lib/subghz/protocols/honeywell.c index dd556d094..106ddeead 100644 --- a/lib/subghz/protocols/honeywell.c +++ b/lib/subghz/protocols/honeywell.c @@ -371,10 +371,26 @@ SubGhzProtocolStatus subghz_protocol_decoder_honeywell_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderHoneywell* instance = context; - return subghz_block_generic_deserialize_check_count_bit( - &instance->generic, - flipper_format, - subghz_protocol_honeywell_const.min_count_bit_for_found); + + SubGhzProtocolStatus res = SubGhzProtocolStatusError; + res = subghz_block_generic_deserialize(&instance->generic, flipper_format); + if(res != SubGhzProtocolStatusOk) { + return res; + } + + if(instance->generic.data_count_bit != 64) { + // Removing possible artifacts from higher bits and setting header to FF FE + instance->generic.data = + ((((((0xFF << 16) | ((instance->generic.data >> 40) & 0xFFFF)) << 16) | + ((instance->generic.data >> 24) & 0xFFFF)) + << 16) | + ((instance->generic.data >> 8) & 0xFFFF)) + << 8 | + (instance->generic.data & 0xFF); + instance->generic.data_count_bit = 64; + } + + return res; } void subghz_protocol_decoder_honeywell_get_string(void* context, FuriString* output) { From a553bc2f57afcfe4de7ceca66aea30001bcaed1a Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 21 Oct 2025 04:22:09 +0300 Subject: [PATCH 141/192] add extra check --- lib/subghz/protocols/honeywell.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/subghz/protocols/honeywell.c b/lib/subghz/protocols/honeywell.c index 106ddeead..f1ed6e4b8 100644 --- a/lib/subghz/protocols/honeywell.c +++ b/lib/subghz/protocols/honeywell.c @@ -379,6 +379,9 @@ SubGhzProtocolStatus } if(instance->generic.data_count_bit != 64) { + if(instance->generic.data_count_bit < 62) { + return SubGhzProtocolStatusErrorValueBitCount; + } // Removing possible artifacts from higher bits and setting header to FF FE instance->generic.data = ((((((0xFF << 16) | ((instance->generic.data >> 40) & 0xFFFF)) << 16) | From 72bdccf7461b6604cd4cdc2dabb0553498b6b734 Mon Sep 17 00:00:00 2001 From: ry4000 <154689120+ry4000@users.noreply.github.com> Date: Sun, 26 Oct 2025 07:27:11 +1100 Subject: [PATCH 142/192] NFC: Synchronise `mf_classic_dict.nfc` with PM3 `mfc_default_keys.dic` Synchronised with PM3 / PR 3007. --- .../resources/nfc/assets/mf_classic_dict.nfc | 493 +++++++++++++++++- 1 file changed, 476 insertions(+), 17 deletions(-) diff --git a/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc b/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc index 0a3de18af..c8585ea0e 100644 --- a/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc +++ b/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc @@ -1,8 +1,9 @@ # Key dictionary from https://github.com/RfidResearchGroup/proxmark3/ # # Mifare Default Keys -# -- iceman fork version -- -# -- contribute to this list, sharing is caring -- +# -- Iceman Fork Version -- +# -- Sharing is Caring -- +# -- Contribute to This Dictionary -- # # Default key FFFFFFFFFFFF @@ -48,6 +49,10 @@ AABBCCDDEEFF 4D3A99C351DD 1A982C7E459A # +# Gym +FAFAFAFAFAFA +FBFBFBFBFBFB +# # key A Wien D3F7D3F7D3F7 # @@ -336,8 +341,8 @@ EAB8066C7479 E5519E1CC92B # # Jett's 24 Hour Fitness S0 KA/B -# 049979614077 -# 829338771705 +049979614077 +829338771705 # # Hotel KeyCard D3B595E9DD63 @@ -1902,15 +1907,14 @@ FF16014FEFC7 # Food GEM 6686FADE5566 # -# Samsung Data Systems (SDS) — Electronic Locks -# Gen 1 S10 KA/KB is FFFFFFFFFFFF, incompatible with Gen 2 locks -# -# SDS Gen 2 S10 KB +# Samsung Data Systems (SDS) +# 10-11 A/B (Gen 2) +9B7C25052FC3 C22E04247D9A +6C4F77534170 +704153564F6C # # Data from Discord, French pool -# SDS Gen 2 S10 KA -9B7C25052FC3 494446555455 # # Data from Discord, seems to be related to ASSA @@ -2172,6 +2176,7 @@ D144BD193063 # Brazil transport Sec 8 / A 50d4c54fcdf5 # +# TEKKEN 6 Namco Data Card # Bandai Namco Passport [fka Banapassport] / Sega Aime Card # Dumped on the Flipper Devices Discord Server 6090D00632F5 @@ -2208,6 +2213,40 @@ C8382A233993 7B304F2A12A6 FC9418BF788B # +# Super Street Fighter 4 Capcom NESYS Card +4B6F74696174 +6F746961744B +4176696E7520 +76696E752041 +576C61737265 +6C6173726557 +416962616320 +696261632041 +42622074656E +622074656E42 +416174363030 +617436303041 +5475206F7469 +75206F746954 +41726576696E +726576696E41 +4B63206C6173 +63206C61734B +41656E696261 +656E69626141 +5A3030622074 +30306220745A +557469617436 +746961743655 +48696E75206F +696E75206F48 +496173726576 +617372657649 +52626163206C +626163206C52 +4F2074656E69 +2074656E694F +# # Guest Cashless Prepaid Arcade Payment Cards 168168168168 198407157610 @@ -2505,10 +2544,8 @@ FAB943906E9C # R.A.T.T transport card key A/B AA034F342A55 456776908C48 - +# # BusFacil - Brazilian public transport card for some cities -7b296f353c6b -3fa7217ec575 fae9b14365a9 c567dd4a6004 c567dd4a6005 @@ -2599,10 +2636,6 @@ b1ea40b2caa6 3abf8431003b # Sector 15 - see above # SKGT personalised subscription card -# Sector 0, 2, 16, key A -a0a1a2a3a4a5 -# Sector 8-14, 17-39, key A -ffffffffffff # Sector 1, key A # blue f1df0ca8948b @@ -2756,3 +2789,429 @@ D37C8F1793F7 543071543071 5F01015F0101 200510241234 +# +# Momentum-Firmware 20241201 +AC935925A876 +ADC169F922CB +AD00EFD353E4 +AEF617B3D004 +AEF617B3D040 +AE381EA0811B +AE683AC2A232 +AF2BFB44A4A5 +A2CA48CA4C05 +A2D5D7469472 +A23412F92811 +A64536FAC799 +A7127F539A16 +A8700E07A58F +A9B9C1D0E3F1 +BC305FE2DA65 +BE3C1BF60B37 +B0A3212D47A5 +B0D58BF147B7 +B02094F92A71 +B1EEAA640EF6 +B19A0664ECA6 +B4FAE0FAD22E +B456E1951216 +B48D7E4E508F +B54D99618ADC +B6728D9B95BA +CA22AF33A19B +CBC83E1548B4 +CB5ED0E57B08 +CC5F59A0CE0A +CF0EC6ACF2F9 +C1F6C7B55F5E +C290A397F84A +C34FAA1931CA +C49C9BF59547 +C497C3BE8273 +C7D1CB6774B0 +C789E4568B99 +C992F85B2DDD +DCD003CF0EA3 +DD30A13519C3 +DD6E74174648 +DE5865F29C44 +DF7C4EC20B50 +D017A84BB582 +D156F66D38EC +D3AEB15D410B +D324152F5BB0 +D3849E31EE4F +D410EFF9113E +D51BCA1DFFFF +D6818C29ED9B +D7142E0F6D0D +D881B675D881 +D9762D114AE5 +EA19E58DD046 +EA4987F8D096 +EB9D9C1B03F6 +EDA4BF3E7B04 +EDC9CC9109A2 +EEF144866688 +E10F0E7A8DD5 +E108EA397A9A +E24359F37FE4 +E3007FA4F781 +E5051FAB4371 +E861FDE1B59F +E87E3554727E +E9B376925A00 +E954024EE754 +FA4D2B3BAFEA +FA8CA10C7D59 +FE98F38F3EE2 +F18D91EE3033 +F3E3F9F977B9 +F4756F7EEAE1 +F654D6C7004F +F697E87E759D +F7A545095C49 +F833E24C3F1C +0CD464CDC100 +0D61BA88789C +0E726E11CFCC +0FC4B1D2EBBA +01E2C14F1B18 +02DB253DC0C7 +03A7AAAA28AD +0380293A9E6D +0402B44FB679 +050BF33DC217 +08A55BC96DC1 +08D6A7765640 +08ED3F92AA53 +09F4EC8D7A66 +1A8CFF2F1BC7 +1C000EB0752F +1D14130D1A0B +1E13EFF32CE2 +101209170A13 +11DDA4862A1C +113355779933 +1153AABAFF6C +12BA20088ED3 +120A7837BB5D +1202165D4EAB +122F595302AA +124F004321D3 +132F641C948B +14CD299DC0C7 +141DF3B1C017 +1415FFFED68D +15B35D0BF715 +156EED7C5F9D +1581C317B073 +164EE10EFFFF +16785FD65BA7 +17C06D19E92F +17E0FA2308FD +1719EB5DAC66 +19BA6776233F +190E6242CE7B +193DFE0FA18E +2B29232D3624 +2CAD8A83DF28 +2D2F182C4024 +2EAD4DD0F7B0 +2FA9B556A4F6 +201106141030 +202011F918A2 +204C0D3DCD9A +20525276F443 +215E9DED9DDF +25FE2B4E4FA9 +251BDBF1C71D +251780F9FBE6 +25467EB0212F +280FD37AD407 +280713CBA260 +286A8893AC6F +3D6F823FFFFF +3E0557273982 +3F41891454EE +30C1DC9DD040 +310308EC52EF +321803E38664 +32774E46C64F +330075000850 +3333F411AAAA +334E91BE3377 +381F84DB8134 +38540EEE8B1C +4A1094F378D8 +4A6E1CAD6D3D +4C44200BC9C5 +4D4946415245 +40454EE64229 +41CD3CD99DD5 +4149206E9BAE +42F82DB5C4AF +4204784B0DC9 +43204334546F +439FB891279F +44ACB624CA14 +44B61F116125 +4427385D72AB +444E4650475A +45450AC8DCA8 +45524DACC5E9 +45574D373B9D +475A444E4650 +4752533E1965 +48B390984150 +48C8852D15F9 +4844426F6E69 +485242F22BE0 +49EE8D52AAB6 +49FEE42DDC18 +49414556EF4D +5AD3FC074A4C +5A15888F3419 +5A2050DA7E3F +5A4920FD6F87 +5CD02DAD8ADE +5C475D2C70C6 +5D819B4BFAF3 +5E696FA0EAD1 +5FA28B8E8BA4 +5F8892561BED +505209266A1F +507A6181E4BF +538BF58687EB +544954CBB2C4 +54546255CDE9 +549BB4FD70C4 +552049EFF3F4 +55213B4F7328 +555D8BBC2D3E +56A4B81B3FC3 +5669C363A4A5 +57E39104CC87 +570FB865D650 +5703815494EF +57059FFD3EE6 +58DBC850A4D5 +585462E190F2 +5990EC1571D7 +6AC79644E0CD +6A530C91F85B +6A86C1895A21 +6C4953590463 +6C79548B3FC3 +6D2BF79566A8 +60D53F070572 +6036F9D72D68 +61152534ACEF +62616E616E61 +66A4932816D3 +6611DFFAAE32 +68C867397AD5 +6862FD600F78 +7ADD3D735725 +7AEB989A5525 +7BF0BE85080F +7B6C00CBAC92 +7CB033257498 +7C20975C6EC9 +7C3AF198425F +7EDAE7923287 +7F796F60FFFF +701AA491A4A5 +72A0C485D3F7 +7213B13D02E0 +722538817225 +7246FCE86427 +735DD20237A9 +746A70C4EF6F +78DF1176C8FD +79E8B59A51E0 +8AC04C1A4E15 +8A0DFD9B7AEA +8A35039F6CD6 +8C524B535E1D +8DF64AB19A16 +8D2B780A148D +8D96A0BA7234 +8EF0AA6432FA +80003D23C6F5 +8000806B5072 +81C0BBCE32E9 +81D6CC146E50 +8380ACDC017E +84A3FD4BA0C6 +840C16869171 +8430A669558C +9AE05868233F +9B2C3E00B561 +9EE3896C4530 +9F14D35BAC08 +9001D0E23F8C +907E5C641D94 +9089B668FFFF +91B1B62402D5 +93FB38FE585A +96AECCC0F7EB +96227EDADBCF +# +# BW Kantine +56cf3acd90ca +542089792be2 +5420aeada758 +# +# CSC Laundry +212223242555 +717273747555 +# +# Hotel cards, BETECH brand, Vietnam +AAC34D9A4E65 +# +# Dutch Statistics Agency (CBS) +DC7B15AA0938 +# +# keys from https://pastebin.com/tpKwph0h +AAAAAABBBBCC +74ABCB1405DE +A25CDE2F781A +6054AC9541C8 +828DDEEE4D98 +ED2B22929167 +C552C1B92395 +F4A4AA2F63A4 +25ECB7B2BAB1 +8B02EF84CDF1 +23EAFB5DA46D +AB921CF0752C +# +# Hotel cards, from one facility using TESA locks... +90C270F690C2 +# ...along with the key used for one of the other services. +0000013B0ED0 +# +# Saragossa transport cards (Bus & Lazo) +04000C0F0903 +0B02070A0409 +4E303D402F20 +216F5B212A7A +5148755C3427 +5C7A355C295A +5246612E7C4B +354B39454861 +455D732C385F +243372407C2E +44202E476E5B +3C4520753758 +206F7C4C4F36 +265A5F32DE73 +567D734C403C +2426217B3B3B +# +# German Aral Gas Station Car-Wash cards +080507020706 +0100815D8D00 +2459514AED5B +5D493F6B0352 +1CEC0F0ACC0E +922B5D1BF2BC +2D7E76C7B8EC +5E59896806FF +097EEA4FE51B +688FC86BAB79 +C01D1DBEEE79 +2529BF8544C2 +C6052FBAA150 +A1D7B3A95605 +00D0BF748E77 +C082C0F35CE6 +3C86C78541A7 +5632DCC517E1 +9310191C338F +2761858C02D7 +8C64B49C7638 +B1BA3E778930 +2037627D9260 +28C4D7170FCD +# +# Card keys from Andalusian public transport system (Consorcio de Transportes) +1848A8D1E4C5 +16EE1FE134E4 +5246B8F4ACFC +515A8209843C +0EF7636AA829 +E59D0F78C413 +5AF68604DD6B +B0BCB22DCBA3 +51B3EF60BF56 +99100225D83B +63C88F562B97 +B30B6A5AD434 +D33E4A4A0041 +9C0A4CC89D61 +5204D83D8CD3 +A662F9DC0D3D +# +# Card keys from EMT Malaga (Spain) bus system +41534E354936 +454D41343253 +4541444C4130 +46305234324E +505444505232 +5239425A3546 +454449434631 +414F4544384C +344E4F4E4937 +45444E413254 +3255534D3033 +4F554D523935 +3141544D3735 +494E47463539 +32414F4E3341 +41534C473637 +534E41395430 +41364C38364F +525241414D39 +41304532334F +4D4545494F35 +4E324C453045 +394143494E32 +5352554E3245 +324553553036 +444D414E3539 +324745413232 +4E4E41455236 +394C52493639 +4D4941413236 +414D504F3243 +434C414E3639 +# Key for Waferlock shadow programming card and shadow user card +333030313536 +# +# Poland Warsaw public transport card keys +2481118e5355 +b6f0fc87f57f +e4fdac292bed +5888180adbe6 +d572c9491137 +64ea317b7abd +a39a286285db +898989890823 +898989891789 +898989893089 +b6e56bad206a +8fe6fa230c69 +4d1095f1af34 +1ad2f99bb9e9 +891089898989 +896389898989 +890163898989 +898927638989 +898989063889 +898989428989 +898989048989 +# University of Portsmouth external library card keys +505254554E49 +505457524954 From 5db6a0381126aec2d1e44b623ccb6c9419f500ba Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Thu, 30 Oct 2025 16:48:23 +0700 Subject: [PATCH 143/192] Remove display_back_light bug from "DisplayBacklightEnforceOn" --- .../services/notification/notification_app.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c index c0ab417b3..de6f50d2e 100644 --- a/applications/services/notification/notification_app.c +++ b/applications/services/notification/notification_app.c @@ -488,9 +488,10 @@ static void notification_process_notification_message( break; case NotificationMessageTypeLedDisplayBacklightEnforceOn: furi_check(app->display_led_lock < UINT8_MAX); - app->display_led_lock++; + // --- NIGHT SHIFT --- - if(app->display_led_lock == 1) { + if(app->display_led_lock <1 ) { + app->display_led_lock = 1; notification_apply_internal_led_layer( &app->display, notification_message->data.led.value * display_brightness_setting * @@ -499,13 +500,11 @@ static void notification_process_notification_message( break; case NotificationMessageTypeLedDisplayBacklightEnforceAuto: if(app->display_led_lock > 0) { - app->display_led_lock--; - if(app->display_led_lock == 0) { - notification_apply_internal_led_layer( - &app->display, - notification_message->data.led.value * display_brightness_setting * - app->current_night_shift * 1.0f); - } + app->display_led_lock = 0; + notification_apply_internal_led_layer( + &app->display, + notification_message->data.led.value * display_brightness_setting * + app->current_night_shift * 1.0f); // --- NIGHT SHIFT END --- } else { FURI_LOG_E(TAG, "Incorrect BacklightEnforce use"); From e392bff8081bb940d09222d2f274ccdbb15aab4b Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 31 Oct 2025 12:12:07 +0300 Subject: [PATCH 144/192] fmt and upd changelog --- CHANGELOG.md | 1 + applications/services/notification/notification_app.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70e041f44..422b5e698 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ * 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) * 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 diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c index de6f50d2e..3cc39ed01 100644 --- a/applications/services/notification/notification_app.c +++ b/applications/services/notification/notification_app.c @@ -490,7 +490,7 @@ static void notification_process_notification_message( furi_check(app->display_led_lock < UINT8_MAX); // --- NIGHT SHIFT --- - if(app->display_led_lock <1 ) { + if(app->display_led_lock < 1) { app->display_led_lock = 1; notification_apply_internal_led_layer( &app->display, From ed2c40de4b0287c51e59d61d399092a535d8df09 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 31 Oct 2025 12:28:08 +0300 Subject: [PATCH 145/192] to bool --- applications/services/notification/notification_app.c | 10 ++++------ applications/services/notification/notification_app.h | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c index 3cc39ed01..68573c201 100644 --- a/applications/services/notification/notification_app.c +++ b/applications/services/notification/notification_app.c @@ -487,11 +487,9 @@ static void notification_process_notification_message( } break; case NotificationMessageTypeLedDisplayBacklightEnforceOn: - furi_check(app->display_led_lock < UINT8_MAX); - // --- NIGHT SHIFT --- - if(app->display_led_lock < 1) { - app->display_led_lock = 1; + if(!app->display_led_lock) { + app->display_led_lock = true; notification_apply_internal_led_layer( &app->display, notification_message->data.led.value * display_brightness_setting * @@ -499,8 +497,8 @@ static void notification_process_notification_message( } break; case NotificationMessageTypeLedDisplayBacklightEnforceAuto: - if(app->display_led_lock > 0) { - app->display_led_lock = 0; + if(app->display_led_lock) { + app->display_led_lock = false; notification_apply_internal_led_layer( &app->display, notification_message->data.led.value * display_brightness_setting * diff --git a/applications/services/notification/notification_app.h b/applications/services/notification/notification_app.h index 239bf69c0..754c8d4c3 100644 --- a/applications/services/notification/notification_app.h +++ b/applications/services/notification/notification_app.h @@ -77,7 +77,7 @@ struct NotificationApp { NotificationLedLayer display; NotificationLedLayer led[NOTIFICATION_LED_COUNT]; - uint8_t display_led_lock; + bool display_led_lock; NotificationSettings settings; From 632638583eb2b63fa659f8cfe2abcfd965639d03 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sun, 2 Nov 2025 02:47:21 +0100 Subject: [PATCH 146/192] Update apps - FlipDownloader: New keyboard, better saving system (by jblanked) - FlipSocial: Fixed auto-updating, better saving system, bugfixes (by jblanked) - Flipp Pomodoro: Added hints, added configurat`ion page (by thevan4), new notification modes (by evilsquid888) - Flipper Blackhat: Updated for latest firmware (by o7-machinehum) - NFC Playlist: Time controls setting (by acegoal07) - RFID/iButton Fuzzer: Fix prev navigation for custom UIDs (by ahnilica) - Seader: Support config card, code optimizations, use same commands as Proxmark3 (by bettse) - WiFi Marauder: Support for ESP32Marauder 1.8.6 (by justcallmekoko) --- CHANGELOG.md | 14 ++++++++------ applications/external | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93ff9bea2..b1380676e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -116,24 +116,26 @@ - Asteroids: Bugfixes, title screen, Drone Buddy power-up (by @SimplyMinimal) - Combo Cracker: Allow press and hold to change values, add tutorial (by @TAxelAnderson), support alphabetic combination locks (by @henrygab) - ESP Flasher: Bump Marauder 1.8.4 (by @justcallmekoko), add C5 support (by @Play2BReal), more reliable bootloader mode on SWCLK (by @WillyJL) - - FlipDownloader: Added a new option to download GitHub repositories with dedicated keyboard, add auto updating (by @jblanked) - - FlipSocial: C++ rewrite, comments on feed posts, simpler logic and registration (by @jblanked) + - FlipDownloader: Added a new option to download GitHub repositories with dedicated keyboard, add auto updating, new keyboard, better saving system (by @jblanked) + - FlipSocial: C++ rewrite, comments on feed posts, simpler logic and registration, fixed auto-updating, better saving system, bugfixes (by @jblanked) - FlipWiFi: Minor bugfixes (by @jblanked) - - Flipper Blackhat: Add Deauth Broadcast command (by @o7-machinehum) + - Flipp Pomodoro: Added hints, added configuration page (by @thevan4), new notification modes (by @evilsquid888) + - Flipper Blackhat: Add Deauth Broadcast command, updated for latest firmware (by @o7-machinehum) - KeyCopier: Added Weiser WR3 key format (by @lightos), added Suzuki SUZ18 key format (by @RIcePatrol) - Mass Storage: Add ability to spoof USB identity values (by @xtruan) - Metroflip: Fix unsupported card crash, RENFE Suma 10 support, GEG Connect AID added, Top Up log parsing and animations, 16 new rail lines, support for parsing area codes, saving function for Suica/Japan Rail IC, bugfixes (by @luu176) - NFC Maker: Support making empty/blank NDEF payloads (by @WillyJL) - - NFC Playlist: Refactor playlist worker, new settings layout and management, loop setting, controls to move between items (by @acegoal07) + - NFC Playlist: Refactor playlist worker, new settings layout and management, loop setting, controls to move between items, time controls setting (by @acegoal07) - NMEA GPS: Moved to GPIO/GPS subfolder (by @WillyJL) - Passy: Misc memory management bugfixes, misc UI improvements (by @qistoph) - - Seader: Fix ATS handling (by @NVX), reset SAM on error (by @bettse) + - RFID/iButton Fuzzer: Fix prev navigation for custom UIDs (by @ahnilica) + - Seader: Fix ATS handling (by @NVX), reset SAM on error, support config card, code optimizations, use same commands as Proxmark3 (by @bettse) - Sentry Safe: New interface, settings & help page (by @H4ckd4ddy) - Seos Compatible: Add keys v2 support with per-device encryption (by @bettse) - Sub-GHz Playlist: Fix crash on disallowed frequencies (by @WillyJL) - Weather Station: Added support for solight TE44 (by @fersingb) - Weebo: Prevent 0x88 in UID[3], add more figures to the database (by @bettse) - - WiFi Marauder: Support for ESP32Marauder 1.8.4 (by @justcallmekoko) + - WiFi Marauder: Support for ESP32Marauder 1.8.6 (by @justcallmekoko) - Sub-GHz: - UL: Add 868.46 MHz to default subghz freqs list (by @xMasterX) - UL: Reduce less popular freqs in default hopper preset, make it faster (by @xMasterX) diff --git a/applications/external b/applications/external index 89d493daa..d923e694e 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit 89d493daa1ea0cb86906fa0042c41dd4edc22a6a +Subproject commit d923e694e35b41d70de41f5a1556daecfc7013a3 From 6c6f7ac8a392f21a3a4dbea31d7e10b18b87808d Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sun, 2 Nov 2025 03:20:17 +0100 Subject: [PATCH 147/192] FBT: Fix plugin API warning --- scripts/fbt_tools/fbt_extapps.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index 6ff592a85..9c44b121a 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -310,6 +310,7 @@ def _validate_app_imports(target, source, env): "js_value_parse", "js_event_loop_get_loop", "js_gui_make_view_factory", + "js_gui_font_declaration", ), # metroflip_api_table ( From 944c5ffb98c455e55a2d430f090d49fb3e2d7c6f Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 2 Nov 2025 22:13:16 +0300 Subject: [PATCH 148/192] anmotors at4 C button --- lib/subghz/protocols/keeloq.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 22070f1ec..17872edf6 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -462,6 +462,8 @@ static bool klq_last_custom_btn = 0x9; } else if((strcmp(instance->manufacture_name, "EcoStar") == 0)) { klq_last_custom_btn = 0x6; + } else if((strcmp(instance->manufacture_name, "AN-Motors") == 0)) { + klq_last_custom_btn = 0xC; } btn = subghz_protocol_keeloq_get_btn_code(klq_last_custom_btn); From 79fc832356af9301e0ab36784eb4035677c9c3f4 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 4 Nov 2025 15:36:34 +0300 Subject: [PATCH 149/192] subghz implement countermodes --- documentation/SubGHzCounterMode.md | 193 +++++++++++++++++++++++++++ lib/subghz/protocols/alutech_at_4n.c | 70 ++++++++-- lib/subghz/protocols/came_atomo.c | 91 ++++++++++--- lib/subghz/protocols/keeloq.c | 106 +++++++++++++-- lib/subghz/protocols/nice_flor_s.c | 69 ++++++++-- 5 files changed, 473 insertions(+), 56 deletions(-) create mode 100644 documentation/SubGHzCounterMode.md diff --git a/documentation/SubGHzCounterMode.md b/documentation/SubGHzCounterMode.md new file mode 100644 index 000000000..4dc486301 --- /dev/null +++ b/documentation/SubGHzCounterMode.md @@ -0,0 +1,193 @@ +# SubGHz Counter Experimental Mode + +## Overview + +Experimental Counter Mode is an advanced feature that allows you to customize how rolling codes increment when transmitting SubGHz signals. Different protocols support different counter modes, which can be useful for specific cases, main purpose is to make clone of the original remote that will not conflict with original remote, so both Flipper and original remote can be used at same time in rolling code systems without desync or other issues. + +**Be aware, do not experiment with equipment you don't have access to, if you are not sure what mode works for you and can't reprogram your original remote to the receiver - do not use these modes!!!** + +In case you have access to the receiver and can do some tests, try some of these modes on your system and let us know how it works for you, in github issues or our communities, thanks! + +## How to Use Experimental Counter Mode + +To enable a specific counter mode, you need to manually edit your `.sub` file and add a `CounterMode` line. + +### Steps: + +1. Locate your `.sub` file (in `/ext/subghz/` on your Flipper Zero SD card) +2. Open the file in a text editor +3. Add the following line at the end of the file: + ``` + CounterMode: X + ``` + Where `X` is the mode number (0, 1, 2, etc.) + +### Example .sub File: + +``` +Filetype: Flipper SubGhz Key File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: Nice FloR-S +Bit: 52 +Key: AA AA AA AA AA AA AA +CounterMode: 1 +``` + +## Supported Protocols and Counter Modes + +### 1. Nice Flor S + +**Mode 0 (Default):** +- Standard - acts like regular remote +- Uses rolling counter multiplier from global settings (default +1) +- Counter increments based on the multiplier value (default +1) +- Resets to 0 when overflow occurs (> 0xFFFF) + +**Mode 1 (floxi2r):** +- Counter sequence: `0x0001 / 0xFFFE` +- For receiver model floxi2r + +**Mode 2 (ox2):** +- Counter sequence: `0x0000 / 0x0001` +- For receiver model ox2 + +--- + +### 2. Came Atomo + +**Mode 0 (Default):** +- Standard - acts like regular remote +- Uses rolling counter multiplier from global settings (default +1) +- Counter increments based on the multiplier value (default +1) +- Resets to 0 when overflow occurs (> 0xFFFF) + +**Mode 1:** +- Counter sequence: `0x0000 / 0x0001 / 0xFFFE / 0xFFFF` +- Works with external CAME RE432 receiver, may work with other type of receivers + +**Mode 2:** +- Counter sequence: `0x807B / 0x807C / 0x007B / 0x007C` +- Works with external CAME RE432 receiver, may work with other type of receivers + +--- + +### 3. Alutech AT-4N + +**Mode 0 (Default):** +- Standard - acts like regular remote +- Uses rolling counter multiplier from global settings (default +1) +- Counter increments based on the multiplier value (default +1) +- Resets to 0 when overflow occurs (> 0xFFFF) + +**Mode 1:** +- Counter sequence: `0x0000 / 0x0001 / 0xFFFE / 0xFFFF` +- For receiver model MCSW-3.3M + +**Mode 2:** +- Counter sequence: `0x0000 / 0x0001 / 0x0002 / 0x0003 / 0x0004 / 0x0005` +- For other receiver boards + +--- + +### 4. KeeLoq + +**Mode 0 (Default):** +- Standard - acts like regular remote +- Uses rolling counter multiplier from global settings (default +1) +- Counter increments based on the multiplier value (default +1) +- Resets to 0 when overflow occurs (> 0xFFFF) + +**Mode 1:** +- Counter sequence: `0x0000 / 0x0001 / 0xFFFE / 0xFFFF` +- Might work with some systems (let us know!) + +**Mode 2:** +- Incremental mode: `+0x3333` each transmission +- Adds 0x3333 (13107 in decimal) to counter each time +- Might work with Doorhan, seen in some "universal remotes" + +**Mode 3:** +- Counter sequence: `0x8006 / 0x8007 / 0x0006 / 0x0007` +- Might work with some systems like Hormann EcoStar + +**Mode 4:** +- Counter sequence: `0x807B / 0x807C / 0x007B / 0x007C` +- Might work with some systems like Nice Smilo + +**Mode 5:** +- Counter sequence: `0x0000 / 0xFFFF` +- Alternates between 0 and maximum value (65535) +- Might work with some systems (let us know!) + +--- + +## Notes and Warnings + +### Important Considerations: + +1. **Default Behavior:** + - If you don't specify a `CounterMode`, Regular remote simulation (cnt +1) is used by default + - Regular remote simulation is the standard mode and works with most cases + +2. **Protocol Compatibility:** + - Not all protocols support all counter modes + - Using an unsupported mode number may result in unexpected behavior + - Always test your configuration before relying on it + +### Troubleshooting: + +- If your file doesn't work after adding `CounterMode`: + 1. Double-check the syntax: `CounterMode: X` (with space after colon) + 2. Verify the mode number is valid for your protocol + + +--- + +## Example Configurations + +### Example 1: Nice Flor S with Mode 1 +``` +Filetype: Flipper SubGhz Key File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: Nice FloR-S +Bit: 52 +Key: 01 23 45 67 89 AB CD +CounterMode: 1 +``` + +### Example 2: KeeLoq with Mode 2 (+0x3333 (Doorhan)) +``` +Filetype: Flipper SubGhz Key File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: KeeLoq +Bit: 64 +Key: DE AD BE EF CA FE BA BE +Manufacture: Doorhan +CounterMode: 2 +``` + +--- + +## FAQ + +**Q: Will this damage my remote or receiver?** +A: Yes it may do that - depending on the receiver, it may desync your remote, be aware and don't experiment with equipment you don't have access to + +**Q: Which mode should I use?** +A: Do not use those mods if you are not sure + +**Q: What happens if I use an invalid mode number?** +A: Last mode from the list will be used + +**Q: Do I need to add CounterMode to every .sub file?** +A: No, only add it if you need non-default behavior. Mode 0 is automatic if not specified. + +--- + +*Made for Unleashed FW, please mention source when copying* diff --git a/lib/subghz/protocols/alutech_at_4n.c b/lib/subghz/protocols/alutech_at_4n.c index 55978e90f..a966382e0 100644 --- a/lib/subghz/protocols/alutech_at_4n.c +++ b/lib/subghz/protocols/alutech_at_4n.c @@ -47,6 +47,8 @@ typedef enum { Alutech_at_4nDecoderStepCheckDuration, } Alutech_at_4nDecoderStep; +static uint8_t alutech_at4n_counter_mode = 0; + const SubGhzProtocolDecoder subghz_protocol_alutech_at_4n_decoder = { .alloc = subghz_protocol_decoder_alutech_at_4n_alloc, .free = subghz_protocol_decoder_alutech_at_4n_free, @@ -273,24 +275,44 @@ static bool subghz_protocol_alutech_at_4n_gen_data( instance->generic.serial = (uint32_t)(data >> 24) & 0xFFFFFFFF; } - // Check for OFEX (overflow experimental) mode - if(furi_hal_subghz_get_rolling_counter_mult() != 0xFFFE) { - if(instance->generic.cnt < 0xFFFF) { - if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + if(alutech_at4n_counter_mode == 0) { + // Check for OFEX (overflow experimental) mode + if(furi_hal_subghz_get_rolling_counter_mult() != 0xFFFE) { + if(instance->generic.cnt < 0xFFFF) { + if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + instance->generic.cnt = 0; + } else { + instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); + } + } else if( + (instance->generic.cnt >= 0xFFFF) && + (furi_hal_subghz_get_rolling_counter_mult() != 0)) { instance->generic.cnt = 0; - } else { - instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); } - } else if( - (instance->generic.cnt >= 0xFFFF) && - (furi_hal_subghz_get_rolling_counter_mult() != 0)) { - instance->generic.cnt = 0; + } else { + 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(); + } else { + instance->generic.cnt++; + } } - } else { + } else if(alutech_at4n_counter_mode == 1) { + // Mode 1 + // 0000 / 0001 / FFFE / FFFF 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++; + } + } else { + // Mode 2 + // 0x0000 / 0x0001 / 0x0002 / 0x0003 / 0x0004 / 0x0005 + if(instance->generic.cnt >= 0x0005) { + instance->generic.cnt = 0; } else { instance->generic.cnt++; } @@ -447,6 +469,18 @@ SubGhzProtocolStatus subghz_protocol_encoder_alutech_at_4n_deserialize( flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + uint32_t tmp_counter_mode; + if(flipper_format_read_uint32(flipper_format, "CounterMode", &tmp_counter_mode, 1)) { + alutech_at4n_counter_mode = (uint8_t)tmp_counter_mode; + } else { + alutech_at4n_counter_mode = 0; + } + subghz_protocol_alutech_at_4n_remote_controller( &instance->generic, instance->crc, instance->alutech_at_4n_rainbow_table_file_name); @@ -720,6 +754,18 @@ SubGhzProtocolStatus subghz_protocol_decoder_alutech_at_4n_deserialize( ret = SubGhzProtocolStatusErrorParserOthers; break; } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + uint32_t tmp_counter_mode; + if(flipper_format_read_uint32(flipper_format, "CounterMode", &tmp_counter_mode, 1)) { + alutech_at4n_counter_mode = (uint8_t)tmp_counter_mode; + } else { + alutech_at4n_counter_mode = 0; + } + } while(false); return ret; } diff --git a/lib/subghz/protocols/came_atomo.c b/lib/subghz/protocols/came_atomo.c index 7a3181341..bdd30e59f 100644 --- a/lib/subghz/protocols/came_atomo.c +++ b/lib/subghz/protocols/came_atomo.c @@ -39,6 +39,8 @@ typedef enum { CameAtomoDecoderStepDecoderData, } CameAtomoDecoderStep; +static uint8_t came_atomo_counter_mode = 0; + const SubGhzProtocolDecoder subghz_protocol_came_atomo_decoder = { .alloc = subghz_protocol_decoder_came_atomo_alloc, .free = subghz_protocol_decoder_came_atomo_free, @@ -185,24 +187,47 @@ static void subghz_protocol_encoder_came_atomo_get_upload( uint8_t pack[8] = {}; - // Check for OFEX (overflow experimental) mode - if(furi_hal_subghz_get_rolling_counter_mult() != 0xFFFE) { - if(instance->generic.cnt < 0xFFFF) { - if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + if(came_atomo_counter_mode == 0) { + // Check for OFEX (overflow experimental) mode + if(furi_hal_subghz_get_rolling_counter_mult() != 0xFFFE) { + if(instance->generic.cnt < 0xFFFF) { + if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + instance->generic.cnt = 0; + } else { + instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); + } + } else if( + (instance->generic.cnt >= 0xFFFF) && + (furi_hal_subghz_get_rolling_counter_mult() != 0)) { instance->generic.cnt = 0; - } else { - instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); } - } else if( - (instance->generic.cnt >= 0xFFFF) && - (furi_hal_subghz_get_rolling_counter_mult() != 0)) { - instance->generic.cnt = 0; + } else { + 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(); + } else { + instance->generic.cnt++; + } } - } else { + } else if(came_atomo_counter_mode == 1) { + // Mode 1 + // 0000 / 0001 / FFFE / FFFF 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++; + } + } else { + // Mode 2 + // 0x807B / 0x807C / 0x007B / 0x007C + if(instance->generic.cnt != 0x807B && instance->generic.cnt != 0x807C && + instance->generic.cnt != 0x007B) { + instance->generic.cnt = 0x807B; + } else if(instance->generic.cnt == 0x807C) { + instance->generic.cnt = 0x007B; } else { instance->generic.cnt++; } @@ -352,6 +377,18 @@ SubGhzProtocolStatus flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + uint32_t tmp_counter_mode; + if(flipper_format_read_uint32(flipper_format, "CounterMode", &tmp_counter_mode, 1)) { + came_atomo_counter_mode = (uint8_t)tmp_counter_mode; + } else { + came_atomo_counter_mode = 0; + } + subghz_protocol_came_atomo_remote_controller(&instance->generic); subghz_protocol_encoder_came_atomo_get_upload(instance, instance->generic.btn); @@ -752,10 +789,32 @@ SubGhzProtocolStatus subghz_protocol_decoder_came_atomo_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderCameAtomo* instance = context; - return subghz_block_generic_deserialize_check_count_bit( - &instance->generic, - flipper_format, - subghz_protocol_came_atomo_const.min_count_bit_for_found); + + SubGhzProtocolStatus status = SubGhzProtocolStatusOk; + status = subghz_block_generic_deserialize(&instance->generic, flipper_format); + if(status != SubGhzProtocolStatusOk) { + FURI_LOG_E(TAG, "Deserialize error"); + return status; + } + if(instance->generic.data_count_bit != + subghz_protocol_came_atomo_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + return SubGhzProtocolStatusErrorValueBitCount; + } + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + return SubGhzProtocolStatusError; + } + + uint32_t tmp_counter_mode; + if(flipper_format_read_uint32(flipper_format, "CounterMode", &tmp_counter_mode, 1)) { + came_atomo_counter_mode = (uint8_t)tmp_counter_mode; + } else { + came_atomo_counter_mode = 0; + } + + return status; } void subghz_protocol_decoder_came_atomo_get_string(void* context, FuriString* output) { diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 17872edf6..3456560a3 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -54,6 +54,8 @@ typedef enum { KeeloqDecoderStepCheckDuration, } KeeloqDecoderStep; +static uint8_t keeloq_counter_mode = 0; + const SubGhzProtocolDecoder subghz_protocol_keeloq_decoder = { .alloc = subghz_protocol_decoder_keeloq_alloc, .free = subghz_protocol_decoder_keeloq_free, @@ -182,29 +184,81 @@ static bool subghz_protocol_keeloq_gen_data( if(counter_up && prog_mode == PROG_MODE_OFF) { // Counter increment conditions - // Check for OFEX (overflow experimental) mode - if(furi_hal_subghz_get_rolling_counter_mult() != 0xFFFE) { - // If counter is 0xFFFF we will reset it to 0 - if(instance->generic.cnt < 0xFFFF) { - // Increase counter with value set in global settings (mult) - if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + if(keeloq_counter_mode == 0) { + // Check for OFEX (overflow experimental) mode + if(furi_hal_subghz_get_rolling_counter_mult() != 0xFFFE) { + // If counter is 0xFFFF we will reset it to 0 + if(instance->generic.cnt < 0xFFFF) { + // Increase counter with value set in global settings (mult) + if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > + 0xFFFF) { + instance->generic.cnt = 0; + } else { + instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); + } + } else if( + (instance->generic.cnt >= 0xFFFF) && + (furi_hal_subghz_get_rolling_counter_mult() != 0)) { instance->generic.cnt = 0; - } else { - instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); } - } else if( - (instance->generic.cnt >= 0xFFFF) && - (furi_hal_subghz_get_rolling_counter_mult() != 0)) { - instance->generic.cnt = 0; + } else { + 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(); + } else { + instance->generic.cnt++; + } } - } else { + } else if(keeloq_counter_mode == 1) { + // Mode 1 + // 0000 / 0001 / FFFE / FFFF 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++; } + } else if(keeloq_counter_mode == 2) { + // Mode 2 + // + 0x3333 each time + if((instance->generic.cnt + 0x3333) > 0xFFFF) { + instance->generic.cnt = 0; + } else { + instance->generic.cnt += 0x3333; + } + } else if(keeloq_counter_mode == 3) { + // Mode 3 + // 0x8006 / 0x8007 / 0x0006 / 0x0007 + if(instance->generic.cnt != 0x8006 && instance->generic.cnt != 0x8007 && + instance->generic.cnt != 0x0006) { + instance->generic.cnt = 0x8006; + } else if(instance->generic.cnt == 0x8007) { + instance->generic.cnt = 0x0006; + } else { + instance->generic.cnt++; + } + + } else if(keeloq_counter_mode == 4) { + // Mode 4 + // 0x807B / 0x807C / 0x007B / 0x007C + if(instance->generic.cnt != 0x807B && instance->generic.cnt != 0x807C && + instance->generic.cnt != 0x007B) { + instance->generic.cnt = 0x807B; + } else if(instance->generic.cnt == 0x807C) { + instance->generic.cnt = 0x007B; + } else { + instance->generic.cnt++; + } + } else { + // Mode 5 + // 0000 / FFFF + if((instance->generic.cnt + 0x1) > 0xFFFF) { + instance->generic.cnt = 0; + } else { + instance->generic.cnt = 0xFFFF; + } } } if(prog_mode == PROG_MODE_OFF) { @@ -573,6 +627,18 @@ SubGhzProtocolStatus break; } + uint32_t tmp_counter_mode; + if(flipper_format_read_uint32(flipper_format, "CounterMode", &tmp_counter_mode, 1)) { + keeloq_counter_mode = (uint8_t)tmp_counter_mode; + } else { + keeloq_counter_mode = 0; + } + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + subghz_protocol_keeloq_check_remote_controller( &instance->generic, instance->keystore, &instance->manufacture_name); @@ -1212,6 +1278,18 @@ SubGhzProtocolStatus break; } + uint32_t tmp_counter_mode; + if(flipper_format_read_uint32(flipper_format, "CounterMode", &tmp_counter_mode, 1)) { + keeloq_counter_mode = (uint8_t)tmp_counter_mode; + } else { + keeloq_counter_mode = 0; + } + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + res = SubGhzProtocolStatusOk; } while(false); diff --git a/lib/subghz/protocols/nice_flor_s.c b/lib/subghz/protocols/nice_flor_s.c index 201284724..f3e1e33bb 100644 --- a/lib/subghz/protocols/nice_flor_s.c +++ b/lib/subghz/protocols/nice_flor_s.c @@ -53,6 +53,8 @@ typedef enum { NiceFlorSDecoderStepCheckDuration, } NiceFlorSDecoderStep; +static uint8_t nice_flors_counter_mode = 0; + const SubGhzProtocolDecoder subghz_protocol_nice_flor_s_decoder = { .alloc = subghz_protocol_decoder_nice_flor_s_alloc, .free = subghz_protocol_decoder_nice_flor_s_free, @@ -150,25 +152,42 @@ static void subghz_protocol_encoder_nice_flor_s_get_upload( } else { instance->encoder.size_upload = size_upload; } - - // Check for OFEX (overflow experimental) mode - if(furi_hal_subghz_get_rolling_counter_mult() != 0xFFFE) { - if(instance->generic.cnt < 0xFFFF) { - if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + if(nice_flors_counter_mode == 0) { + // Check for OFEX (overflow experimental) mode + if(furi_hal_subghz_get_rolling_counter_mult() != 0xFFFE) { + if(instance->generic.cnt < 0xFFFF) { + if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + instance->generic.cnt = 0; + } else { + instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); + } + } else if( + (instance->generic.cnt >= 0xFFFF) && + (furi_hal_subghz_get_rolling_counter_mult() != 0)) { instance->generic.cnt = 0; - } else { - instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); } - } else if( - (instance->generic.cnt >= 0xFFFF) && - (furi_hal_subghz_get_rolling_counter_mult() != 0)) { - instance->generic.cnt = 0; + } else { + 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(); + } else { + instance->generic.cnt++; + } + } + } else if(nice_flors_counter_mode == 1) { + // Mode 1 (floxi2r) + // 0001 / FFFE + if(instance->generic.cnt == 0xFFFE) { + instance->generic.cnt = 0x0001; + } else { + instance->generic.cnt = 0xFFFE; } } else { - if((instance->generic.cnt + 0x1) > 0xFFFF) { + // Mode 2 (ox2) + // 0x0000 / 0x0001 + if(instance->generic.cnt >= 0x0001) { instance->generic.cnt = 0; - } else if(instance->generic.cnt >= 0x1 && instance->generic.cnt != 0xFFFE) { - instance->generic.cnt = furi_hal_subghz_get_rolling_counter_mult(); } else { instance->generic.cnt++; } @@ -266,6 +285,17 @@ SubGhzProtocolStatus flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); // flipper_format_read_uint32( // flipper_format, "Data", (uint32_t*)&instance->generic.data_2, 1); + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + uint32_t tmp_counter_mode; + if(flipper_format_read_uint32(flipper_format, "CounterMode", &tmp_counter_mode, 1)) { + nice_flors_counter_mode = (uint8_t)tmp_counter_mode; + } else { + nice_flors_counter_mode = 0; + } subghz_protocol_nice_flor_s_remote_controller( &instance->generic, instance->nice_flor_s_rainbow_table_file_name); @@ -766,6 +796,17 @@ SubGhzProtocolStatus } instance->data = (uint64_t)temp; } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + uint32_t tmp_counter_mode; + if(flipper_format_read_uint32(flipper_format, "CounterMode", &tmp_counter_mode, 1)) { + nice_flors_counter_mode = (uint8_t)tmp_counter_mode; + } else { + nice_flors_counter_mode = 0; + } } while(false); return ret; } From c87205fe9a25c712f65c312bed9278104e2b7d9f Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 4 Nov 2025 16:12:19 +0300 Subject: [PATCH 150/192] upd changelog and readme --- CHANGELOG.md | 4 +++- ReadMe.md | 3 ++- .../external/L1_Halloween_128x64/frame_0.png | Bin 1597 -> 0 bytes .../external/L1_Halloween_128x64/frame_1.png | Bin 1592 -> 0 bytes .../external/L1_Halloween_128x64/frame_2.png | Bin 1605 -> 0 bytes .../external/L1_Halloween_128x64/frame_3.png | Bin 1594 -> 0 bytes .../external/L1_Halloween_128x64/meta.txt | 14 -------------- assets/dolphin/external/manifest.txt | 7 ------- 8 files changed, 5 insertions(+), 23 deletions(-) delete mode 100644 assets/dolphin/external/L1_Halloween_128x64/frame_0.png delete mode 100644 assets/dolphin/external/L1_Halloween_128x64/frame_1.png delete mode 100644 assets/dolphin/external/L1_Halloween_128x64/frame_2.png delete mode 100644 assets/dolphin/external/L1_Halloween_128x64/frame_3.png delete mode 100644 assets/dolphin/external/L1_Halloween_128x64/meta.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 422b5e698..1180cb352 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## Main changes - Current API: 87.0 +* 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) @@ -7,7 +9,7 @@ * 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 -* Enable halloween anim +* Disable halloween anim

#### Known NFC post-refactor regressions list: - Mifare Mini clones reading is broken (original mini working fine) (OFW) diff --git a/ReadMe.md b/ReadMe.md index fee5aeb74..5a133ffb9 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -273,7 +273,8 @@ Enhance your Flipper Zero with apps and plugins created by the community: ### ![SubGhz Icon Badge] Sub-GHz -- [How to use Flipper as new remote (Nice FlorS, BFT Mitto, Somfy Telis, Aprimatic, AN-Motors, etc..)](/documentation/SubGHzRemoteProg.md) +- [How to use Flipper as rolling code remote (Doorhan, Nice FlorS, BFT Mitto, Somfy Telis, Aprimatic, AN-Motors, etc..)](/documentation/SubGHzRemoteProg.md) +- [Experimental rolling code counter modes (avoid desync)](/documentation/SubGHzCounterMode.md) - External Radio: [How to connect CC1101 module](https://github.com/quen0n/flipperzero-ext-cc1101) - Transmission is blocked? [How to extend Sub-GHz frequency range](/documentation/DangerousSettings.md) - [How to add extra Sub-GHz frequencies](/documentation/SubGHzSettings.md) diff --git a/assets/dolphin/external/L1_Halloween_128x64/frame_0.png b/assets/dolphin/external/L1_Halloween_128x64/frame_0.png deleted file mode 100644 index f69a43f5e75a4efee946984b5f551d2af692bf72..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1597 zcmV-D2EzG?P)Px)_en%SRCt{2T-kEuFbK4_=Kp`$`=k`X91TLp#O|upB(@GD5J%gS^SZ9<*bwJ= z-mYyb>R036Y5_RT^Zcd!ru6H&M5B1ohtFyO8n|-lX@8YJ4(F~R;Kdwl7Xn_)#cKc$ z$7=u&$7=u&$7=u&$7=u&$2L5`H^SmBz6Fmn0F6+JD6fsd`)Jp9#$$<~?6ZdRJl%Fk zn_Zw9fJeQ4%K{Nd&mOdPoR-$we~S@_NuYK*=+9pQU6W_{O|k7~#4`$OOzB>a3^xye zS~tZAgIMK(tT?WnYVH}RF?k-vn@bvlUgP(pp66E*=n15u*B~+$T#;}^H!ETk=y=lO z@AkK(XnLaRX#;FJoG9tYd{q)9L)*%+**4{v6_dG#zjL}gIfk>EGy%_SLGEE zS-ibK*N`zh+FtV*^d2gUX#iFnvV=s+q==9sAgwD?;(2+2H9u{e1>#LCIerX4vPx7z zQH63hwes{w-FK#sgzD-+^D3@EOI1MRR8-Wvk@|WyDu^H{rjIZPL?)34iV`CgK$EiS zOgw`MDp)#bt?vkKLCtHjRInBMtVz9?Wo>{p-U^S=6EeN5-trPrBe6VsnE_NOB66A| zN$M`ttVir1eKUrj?NjP$pfnJ|czNwcK_XUGK%;Td{HI;dtU+7*@E1ZPV#7kzsw3@I zE1yk;CAGPE0LiNUf)XUV&FgJG zQajV@Rqa+`OAio%NAn_dszz6%28Ft2%_CF+8JEkNG5}=38Du?6FS$b7X4i%8iDn4M zy0Z_}tmg{uJAu*3su0PJt#;@dXlO%YyOblK;Gv;+y|?m?8H+Z4nhC7V1tQNJBJ=Lf zB3h-MwjW3-Dvd2c4FPmgt0o?qp*D9Z{ZvBRVDD6pSUX)GQbA8_nH$d4g28SI9gvC-Cm|%qu<&ia^R@BNT4MsX_Vu zwwUG_hMMk?RSoVEB_A=8X-(91#4{vXZSGp7Ps_(*celTAal zERp!!JASXRsb3DY7LieY)}3l- zU{=B;8BjGwh6Q9?%~)%*(~&9#EnzJ$V9n3NfJg*YDXD@KnaQk4uyTzCD}mIMAob&YwQF8^>?xmqy8j|Z z5+oACxW!psz)QL;7pKXu@fq}5PaW+6^)%kzSnYd0MGp{Fga%3ZJ&1TFD~ZiOGo+|2 z6x0(`DmC&6JqtjyWUVb%J~yx#F5a38^#T#Okv2RWtT2L-he7Px)@<~KNRCt{2TiK4IKn#Rc{{NS=PrY&+A63Rp!-kSZnSnms_%0^<`~7}z7sS5r zU)L@w>aFo-wE!CXzJDlxDE<9@n?~{A7+$Ld7|_a<-u@_m9`>uIfCuN`aw*`!xp)lV z$MG1zkK-|bAID<=KaR%$ejJzK0q%&1ySNK}&Hy|@iBaBIL(lQ9Ul~731XbTP?E9XM zL&jVMt^s7!>s=0rz1R%cgUar3Gz&t;Sa@*pBe8cqBX61M`ZYl z0E{m=!UMWuih+kWmrD%DtLymtb;E3`i*c5R@iTdHOb2~yfS`+BKz44Y}y$EBii>9)Hv<;Lw4+R^- zSJo8P-qpemJx9gxHxLN3+f-OfIb7yc1Ze3HQf}`9QZJOn*%m}W;zaL4NSydlJ3D!uWh~8!ZD|&OL zNWBU*n-M$6+=?N1&#Ck_&{_y(4Oe>VY9sJyT(qzp*DGi6&OZGgLM&n<0&CUGxYf#+ z=}kw>!L>+skR`e*=Yt}E4mno{r5afZx{C6nwCRy0o6tls|!Q00&N zC|J@g8uyg~^x)NbfA#*ekfm0uR?u^!G1gkv7_$bjTD-d}VV3+Q{vD_btZPhh=Z(>0 zUrZ~t!U$&3&CK|S^~~7L0W|klXn2q5^TtR}JOE2U9>M5=I}MJJUy?^s9z-^)m1AU- zGOz`-)geT<26~%xc#I}2R;Qedd6VO2MT>|L zL}fubk1qqTdT?2|pqt+fY&$t{h7nj!kwu3wCzJ2%yMc(6b*6)v^fE)q9tr6yXmX~& zjqZPaH_-Y!y5|YC3bN&qUJwy79g3TEc7|K1;Dsu;qgS@p?Cc^Cc`V|lOOCT6LB|=E zy$hJpLPms8RuJhLpz2*`L_sFCdGp8kmp*9Ldq$pyD7Bkhvi8hEyhq}6M*?f_4#@S7 z%ItnQG&)2@`IQI4vSXQXGHV?L;xL_iAdY8^`RD>NFQ{5uwL6evjQ}RniN2{d9Ew3@}RHHtc1JJa0#8$qbiW&stYrle~yN zFWm-eg2hs{cb(;`SCUFrgkhtD_t3LB)p3!>OF87+X*>XG1hes`xQ+D!TKlm2!v zQhY`0RJx2aNEM{u%xVP-TG>&dj7CUS$vwM~VhoJj$QYdtRv1CczlCd0w;n__ZFHg2*s=&& z-RH6qpsOfE)?@cVJl9G^UaDXe*JVsnj%5+xsFIJ&uX>VKSTV}<9?UA79Rc=j+t!^H q0OH-&oU9PE=64pX3?c)b{rMLrmm-QE=NLNx0000Px)|4BqaRCt{2T-&mnFbp)E^Z&n`J~^W(y6nm}m+s^_}r^4&Vt&sL_t&aNG9lQJdZH zvqDhiT|+mlB~^QEcZVARGU@dW4;MkMzdtKtG~MPdhW21afM0zRm*sHJ*g7)YDHVK0 z03^IwZISi}u8ch0h@Rmh)IFSoh?5s(X=^fvk6DhOxZ; zN}!>>13htQ7}rjy_f8&6>(Ab}Xe0<7zzaiO+rGFN?N-BjF624UGLRVqf#)Qi1Hrn6 ztOBau%uG1iZ#GzCR!T)?4I`*1lV4>Zb1G(#D38^4T{tt!kY7IEfp!aa%z$Ls1sC;xJ%k;&5pFKt@{AhJ;*{Wsc*`spsyX9DpeY zy}z#jtHND%`lH%CJ2b8NPJzyC_1Wrs04;&`YiM5&J46O;=+-_BY4L#$W)IkB-O2$v zDvDM#*46OTRPE%w9ZRrLeqa*q*4oFFh{3Zthv^fIwSsh>g^F+#aRPKdtoI0}ZJTm{ z>V&Is_57niqZt~hD(O*pFP^tbBBGG{Mk;R#U)0)WSk<>O|r1= zRTfs-@y3FLph&U06Oi@yIz2L2)}|uV$i8|m>mJe{X-_GEtDT^dZYpvpI;JI8qj|Tt zx70nBu&vzlErp;eqEvM*T{!gJ(wpl*yx~tb&X!>$V=#XB0iQj*no; z$r)K~4g9x(E42V??6dRa%=uXDN7o^d2`{}3 z%QX#uYliZqjXq1FS5IBSjEh3qm+Q93V4_o}{_~XBl7S!E7@a z4xsz3b^B*E;2^;H%#gSiD8w&r*Q*(q|V=U_m~2b>2VE<1=O)u~CFa z%vAld{Ak_x)_;d|Fj_jx{%;Nt5j=D3)#EK))yrV)3gHctI8GqE-K7^pmFz94D zw^o3VIT`*KW$g@%aEZzs$YHYDd?|rmJt-$-7C7pmsKX&LacjQFwOWK)nHEM4rAY!= z5yFG_(Aj1%Tq0VZtc4@=u~5yRidkc#&g9J}ue()1R41dX+UvLSq@hOF>yrJnC^TCE zjs89p5&3nc;0$`RqLAwKlh=^aY$ce}RE-Q#l~Oqj5l6GG$%6-_fDuH-UyaBiSstS| zD|31a-lk`Ls_mkEm+MGVz|0&tYW&f4cwz}Mu4g>W8LT~{AdXqMS*C3od%$9 z0_Ew+$QR=AY?ZOHl*-`M^pP=jJIe@GKi${6mT4N9@#Sy$KZHaNmO*`<+E_Bys#cJ+mMxr zoQ*N87g3n`tg)_g0*V97LNp#b7h*Uo9WiWP14&gCC%?i8-W*`Ik!OJ+ld_M;L9A$n zp6XSwxYlV-&;~yUSqPx)^hrcPRCt{2TF?KB^t$xK73XS(7=^TPy4I4%GvA)aw%#XMxJ!)`}>Nw|R=9HHcM!b~@y`E>GY%cA7n%{H`)u+6S{%~aA`|aImPqSD)`^yZ$ozEP zED&#E8Y1NZs!j!hT?`?r%uK21&0B%2C%6$okyBBTL`m~CtTqk}y;QIi(?=MDH0Mng zi3yy|bXi6qp{MK`S*}KNC27s0-hWi}n4yBDjr5ELxsMn@Z`IH{Mg@?p-trPrBe6Vs z*Z`{Bz9(2_%!unKOS z>~=&vlvg=78+H|*Vx)c^D8tgObY?M zkT~$3u~x#8UsJqQ&-3vl!{Xu8Og51uFnSs*Yxz-0=PaPc98Koh_lO))pO06|W)!en zBDO=uUzMP04}7vAP<r3M&cH4wpOQ=MlYh5P zaaJa-%xFR_RR|U!B8U#lcTBGA19Jo@~zA zS!2!iq7iIK1Z82>$4EYdk|%sz-q&k~jGF;+fcQRye;qu^3slB(O}=O%7Lf$!saPGEGh3LrJDbiGI3Xo!#uHfk{Fyr^+#yntn#sSr?I9(tsR)>e`$3%aJ6 zv07qY`8_L%?A;IOw}B(M0B!7ij6gaRo_A<{Jo#?F4UAC3SvNRK`4LLC$EB~J z%p*y!b^mJs7AGxbnAydS-YBnBk~XbEp3ymhHD}MfxAy@wcVX4aJGk>6;-*Ue*?Up3 zhLV31xB5AOl(+EvUP8ok05lQgn6Vd;(5Cfo?LT~>UhS!!G&ClS;&NQR4lu7StN|5^TzS=dfr1g|fKcN>w z=;`s2FT~|VtBe_>3X*VI9zg?Fwx)E+JtOjTe_n?4J;{%C+C29c@j#x-)RI|xEmBr# zb6R~o!{&0#{DeF}W^=5K<%%A4vZe1@Tf_(=awBbcI9Oo>C9j3^(=Kg<6*gv|(%2AB zm6>N-4^WvDdgDdsLQbv}j-*^Bns8Pb6P06`2k2FDPyg&m(h{p^T$eUx6=r*Y^Ei%m ss|A3ZZmAEF2=e|~(mEQJ0gwLtA4(N8cxHshp8x;=07*qoM6N<$g2^=a*8l(j diff --git a/assets/dolphin/external/L1_Halloween_128x64/meta.txt b/assets/dolphin/external/L1_Halloween_128x64/meta.txt deleted file mode 100644 index 9762d4caa..000000000 --- a/assets/dolphin/external/L1_Halloween_128x64/meta.txt +++ /dev/null @@ -1,14 +0,0 @@ -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: 3 -Duration: 3600 -Active cooldown: 0 - -Bubble slots: 0 diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index 2048d09c4..951a4cdf7 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -245,10 +245,3 @@ Max butthurt: 13 Min level: 1 Max level: 3 Weight: 4 - -Name: L1_Halloween_128x64 -Min butthurt: 0 -Max butthurt: 14 -Min level: 1 -Max level: 3 -Weight: 4 From f177c0491d2131abb16acc8bf237f845c4f62b65 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 4 Nov 2025 19:28:30 +0300 Subject: [PATCH 151/192] subghz counter freeze mode --- documentation/SubGHzCounterMode.md | 6 ++++++ lib/subghz/protocols/came_atomo.c | 4 +++- lib/subghz/protocols/keeloq.c | 4 +++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/documentation/SubGHzCounterMode.md b/documentation/SubGHzCounterMode.md index 4dc486301..54fd0e24e 100644 --- a/documentation/SubGHzCounterMode.md +++ b/documentation/SubGHzCounterMode.md @@ -71,6 +71,9 @@ CounterMode: 1 - Counter sequence: `0x807B / 0x807C / 0x007B / 0x007C` - Works with external CAME RE432 receiver, may work with other type of receivers +**Mode 3:** +- Counter freeze - do not increment + --- ### 3. Alutech AT-4N @@ -121,6 +124,9 @@ CounterMode: 1 - Alternates between 0 and maximum value (65535) - Might work with some systems (let us know!) +**Mode 6:** +- Counter freeze - do not increment + --- ## Notes and Warnings diff --git a/lib/subghz/protocols/came_atomo.c b/lib/subghz/protocols/came_atomo.c index bdd30e59f..56ed6ce38 100644 --- a/lib/subghz/protocols/came_atomo.c +++ b/lib/subghz/protocols/came_atomo.c @@ -220,7 +220,7 @@ static void subghz_protocol_encoder_came_atomo_get_upload( } else { instance->generic.cnt++; } - } else { + } else if(came_atomo_counter_mode == 2) { // Mode 2 // 0x807B / 0x807C / 0x007B / 0x007C if(instance->generic.cnt != 0x807B && instance->generic.cnt != 0x807C && @@ -231,6 +231,8 @@ static void subghz_protocol_encoder_came_atomo_get_upload( } else { instance->generic.cnt++; } + } else { + // Mode 3 - Freeze counter } // Save original button for later use diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 3456560a3..c90cfcf0e 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -251,7 +251,7 @@ static bool subghz_protocol_keeloq_gen_data( } else { instance->generic.cnt++; } - } else { + } else if(keeloq_counter_mode == 5) { // Mode 5 // 0000 / FFFF if((instance->generic.cnt + 0x1) > 0xFFFF) { @@ -259,6 +259,8 @@ static bool subghz_protocol_keeloq_gen_data( } else { instance->generic.cnt = 0xFFFF; } + } else { + // Mode 6 - Freeze counter } } if(prog_mode == PROG_MODE_OFF) { From 4f5ab0b15b23808b7ce2db92fbbdeaf4f7e4605c Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Wed, 5 Nov 2025 23:15:08 +0700 Subject: [PATCH 152/192] Start working with SubGhz_signal_settings menu --- .../main/subghz/helpers/subghz_custom_event.h | 1 + .../main/subghz/scenes/subghz_scene_config.h | 1 + .../subghz/scenes/subghz_scene_saved_menu.c | 15 +++++++- .../scenes/subghz_scene_signal_settings.c | 34 +++++++++++++++++++ applications_user/README.md | 1 - 5 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 applications/main/subghz/scenes/subghz_scene_signal_settings.c delete mode 100644 applications_user/README.md diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index e118948a0..41e704ea5 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -16,6 +16,7 @@ typedef enum { SubGhzCustomEventSceneReceiverInfoTxStop, SubGhzCustomEventSceneReceiverInfoSave, SubGhzCustomEventSceneSaveName, + SubGhzCustomEventSceneSignalSettings, SubGhzCustomEventSceneSaveSuccess, SubGhzCustomEventSceneShowErrorBack, SubGhzCustomEventSceneShowErrorOk, diff --git a/applications/main/subghz/scenes/subghz_scene_config.h b/applications/main/subghz/scenes/subghz_scene_config.h index a23a12a2b..7be062eba 100644 --- a/applications/main/subghz/scenes/subghz_scene_config.h +++ b/applications/main/subghz/scenes/subghz_scene_config.h @@ -25,3 +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) diff --git a/applications/main/subghz/scenes/subghz_scene_saved_menu.c b/applications/main/subghz/scenes/subghz_scene_saved_menu.c index 61e362d5b..011e719a7 100644 --- a/applications/main/subghz/scenes/subghz_scene_saved_menu.c +++ b/applications/main/subghz/scenes/subghz_scene_saved_menu.c @@ -4,6 +4,7 @@ enum SubmenuIndex { SubmenuIndexEmulate, SubmenuIndexEdit, SubmenuIndexDelete, + SubmenuIndexSignalSettings }; void subghz_scene_saved_menu_submenu_callback(void* context, uint32_t index) { @@ -34,6 +35,13 @@ void subghz_scene_saved_menu_on_enter(void* context) { subghz_scene_saved_menu_submenu_callback, subghz); + 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)); @@ -60,7 +68,12 @@ bool subghz_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexEdit); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); return true; - } + } else if(event.event == SubmenuIndexSignalSettings) { + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexSignalSettings); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSignalSettings); + return true; + } } return false; } diff --git a/applications/main/subghz/scenes/subghz_scene_signal_settings.c b/applications/main/subghz/scenes/subghz_scene_signal_settings.c new file mode 100644 index 000000000..1bbe6f2c8 --- /dev/null +++ b/applications/main/subghz/scenes/subghz_scene_signal_settings.c @@ -0,0 +1,34 @@ +#include "../subghz_i.h" +#include "subghz/types.h" +#include "../helpers/subghz_custom_event.h" +#include +#include +#include +#include + +void subghz_scene_signal_settings_text_input_callback(void* context) { + furi_assert(context); + SubGhz* subghz = context; + view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneSignalSettings); +} + +void subghz_scene_signal_settings_on_enter(void* context) { + SubGhz* subghz = context; + + 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; + } + + +void subghz_scene_signal_settings_on_exit(void* context) { + SubGhz* subghz = context; + +} diff --git a/applications_user/README.md b/applications_user/README.md deleted file mode 100644 index 8bb7823c1..000000000 --- a/applications_user/README.md +++ /dev/null @@ -1 +0,0 @@ -Put your custom applications in this folder. \ No newline at end of file From 7f7d1e1d32c75f6798dad8d5db1f3a2396dbb997 Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Thu, 6 Nov 2025 17:42:55 +0700 Subject: [PATCH 153/192] Signal settings gui --- .../scenes/subghz_scene_signal_settings.c | 60 ++++++++++++++++--- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_signal_settings.c b/applications/main/subghz/scenes/subghz_scene_signal_settings.c index 1bbe6f2c8..3c3e83f6f 100644 --- a/applications/main/subghz/scenes/subghz_scene_signal_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_signal_settings.c @@ -1,20 +1,63 @@ #include "../subghz_i.h" #include "subghz/types.h" #include "../helpers/subghz_custom_event.h" -#include -#include #include -#include +#include + +#define COUNTER_MODE_COUNT 7 +const char* const counter_mode_text[COUNTER_MODE_COUNT] = { + "System", + "Mode 1", + "Mode 2", + "Mode 3", + "Mode 4", + "Mode 5", + "Mode 6", +}; + +int8_t counter_mode = 0; + +const int32_t counter_mode_value[COUNTER_MODE_COUNT] = { + 0, + 1, + 2, + 3, + 4, + 5, + 6, +}; + +void subghz_scene_signal_settings_counter_mode_changed (VariableItem* item){ + SubGhz* subghz = variable_item_get_context(item); + UNUSED (subghz); + 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_text_input_callback(void* context) { - furi_assert(context); - SubGhz* subghz = context; - view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneSignalSettings); } void subghz_scene_signal_settings_on_enter(void* context) { SubGhz* subghz = context; + VariableItemList* variable_item_list = subghz->variable_item_list; + int32_t value_index; + VariableItem* item; + int32_t available_count = COUNTER_MODE_COUNT; + + item = variable_item_list_add( + variable_item_list, + "Counter Mode", + available_count, + subghz_scene_signal_settings_counter_mode_changed, + subghz); + value_index = value_index_int32( + counter_mode, + counter_mode_value, + available_count); + + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, counter_mode_text[value_index]); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList); } @@ -30,5 +73,6 @@ bool subghz_scene_signal_settings_on_event(void* context, SceneManagerEvent even void subghz_scene_signal_settings_on_exit(void* context) { SubGhz* subghz = context; - + variable_item_list_set_selected_item(subghz->variable_item_list, 0); + variable_item_list_reset(subghz->variable_item_list); } From 772c944163b3d82b2aebba1b3e497080b6c74578 Mon Sep 17 00:00:00 2001 From: Alexey Zakharov <110112149+alexeyzakh@users.noreply.github.com> Date: Thu, 6 Nov 2025 13:21:44 +0000 Subject: [PATCH 154/192] Devboard docs update (#4301) Wi-Fi devboard pinout added, updated devboard hardware image, fixed the schematics link, and corrected typos. Co-authored-by: hedger --- .../Firmware update on Developer Board.md | 42 ++++++++++++------- documentation/doxygen/dev_board.dox | 8 +++- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/documentation/devboard/Firmware update on Developer Board.md b/documentation/devboard/Firmware update on Developer Board.md index 9cc9a8ea0..2ffe01dab 100644 --- a/documentation/devboard/Firmware update on Developer Board.md +++ b/documentation/devboard/Firmware update on Developer Board.md @@ -13,21 +13,31 @@ It's important to regularly update your Developer Board to ensure that you have **On Linux & macOS:** -Run the following command in the Terminal: - -``` -python3 -m pip install --upgrade ufbt -``` +1. Open a terminal. +2. Install `pipx` by following the instructions on the [official website](https://pipx.pypa.io/stable/installation/). +3. Restart the terminal. +4. Install `ufbt`: + ``` + pipx install ufbt + ``` **On Windows:** -1. Download the latest version of Python on -2. Run the following command in the PowerShell - +1. Download the latest version of Python on [the official website](https://www.python.org/downloads/windows/) and install it. +2. Open PowerShell. +3. Install `pipx`: ``` - py -m pip install --upgrade ufbt + py -m pip install --user pipx + ``` +4. Add `pipx` to PATH: + ``` + py -m pipx ensurepath + ``` +5. Restart PowerShell. +6. Install `ufbt`: + ``` + pipx install ufbt ``` - *** ## Step 2. Connect the Devboard to PC @@ -49,12 +59,12 @@ To update the firmware, you need to switch your Developer Board to Bootloader mo 3.1. Press and hold the **BOOT** button. - 3.2. Press the **RESET** button while holding the **BOOT** button. + 3.2. Press and release the **RESET** button while holding the **BOOT** button. 3.3. Release the **BOOT** button. - \image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_reboot_to_bootloader.png width=700 + \image html https://cdn.flipper.net/Flipper_Zero_devboard_bootloader.jpg width=700 -4. Repeat **Step 1** and view the name of your Developer Board that appeared in the list. +4. Repeat the first command above (listing serial devices) and view the name of your Developer Board that appeared in the list. *** @@ -66,7 +76,7 @@ To update the firmware, you need to switch your Developer Board to Bootloader mo python3 -m ufbt devboard_flash ``` -**On Windows:** Run the following command in the PowerShell: +**On Windows:** Run the following command in PowerShell: ``` py -m ufbt devboard_flash @@ -74,7 +84,7 @@ py -m ufbt devboard_flash You should see the following message: `WiFi board flashed successfully`. -### If flashing failed +### If flashing fails Occasionally, you might get an error message during the flashing process, such as: @@ -90,7 +100,7 @@ FileNotFoundError: [Errno 2] No such file or directory: '/dev/cu.usbmodem01' To fix it, try doing the following: -- Disconnect the Developer Board from your computer, then reconnect it. After that, switch your Developer Board to Bootloader mode once again, as described in +- Disconnect the Developer Board from your computer, then reconnect it. After that, switch your Developer Board to Bootloader mode once again, as described in Step 2. - Use a different USB port on your computer. diff --git a/documentation/doxygen/dev_board.dox b/documentation/doxygen/dev_board.dox index 8d5a2bf35..d90c6e7b0 100644 --- a/documentation/doxygen/dev_board.dox +++ b/documentation/doxygen/dev_board.dox @@ -26,11 +26,15 @@ Check out these guides to get started with the Devboard: The Developer Board is equipped with an [ESP32-S2-WROVER](https://www.espressif.com/en/products/socs/esp32-s2) module, which includes built-in Wi-Fi capabilities. It also offers GPIO pins for easy connectivity to various targets. Additionally, the Developer Board features a USB Type-C connector for data transfer and power supply. For user interaction, the Developer Board has tactile switches. -\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_developer_board_hardware_CDN.jpg width=700 +\image html https://cdn.flipper.net/Flipper_Zero_devboard_main_parts.jpg width=700 + +## Pinout + +\image html https://cdn.flipper.net/Flipper_Zero_wifi_devboard_pinout_updated.png width=700 ## Additional resources -To learn more about the Wi-Fi Developer Board hardware, visit [Schematics in Flipper Docs](https://docs.flipperzero.one/development/hardware/wifi-debugger-module/schematics). +To learn more about the Wi-Fi Developer Board hardware, visit [Schematics in Flipper Docs](https://docs.flipper.net/zero/development/hardware/devboard-schematics). For additional information about Flipper Zero GPIO pins, visit [GPIO & modules in Flipper Docs](https://docs.flipperzero.one/gpio-and-modules). From 67b906e6ba8a09aee1cf6a0230e5879aa1dd0a7d Mon Sep 17 00:00:00 2001 From: an5t <8015386+an5t@users.noreply.github.com> Date: Thu, 6 Nov 2025 18:31:17 +0300 Subject: [PATCH 155/192] github: fail submit_sdk on catalog api error (#4294) Co-authored-by: hedger --- .github/actions/submit_sdk/action.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/submit_sdk/action.yml b/.github/actions/submit_sdk/action.yml index b515b5285..2c556d296 100644 --- a/.github/actions/submit_sdk/action.yml +++ b/.github/actions/submit_sdk/action.yml @@ -49,7 +49,7 @@ runs: - name: Submit SDK shell: bash run: | - curl -sX 'GET' \ + curl --fail -sX 'GET' \ '${{ inputs.catalog-url }}/api/v0/0/sdk?length=500' \ -H 'Accept: application/json' > sdk_versions.json if jq -r -e ".[] | select((.api == \"${{ inputs.firmware-api }}\") and .target == \"${{ inputs.firmware-target }}\")" sdk_versions.json > found_sdk.json ; then @@ -61,7 +61,7 @@ runs: if ! echo "${{ inputs.firmware-version }}" | grep -q -- "-rc" ; then SDK_ID=$(jq -r ._id found_sdk.json) echo "Marking SDK $SDK_ID as released" - curl -X 'POST' \ + curl --fail-with-body -X 'POST' \ "${{ inputs.catalog-url }}/api/v0/0/sdk/${SDK_ID}/release" \ -H 'Accept: application/json' \ -H 'Authorization: Bearer ${{ inputs.catalog-api-token }}' \ @@ -69,7 +69,7 @@ runs: fi else echo "API version ${{ inputs.firmware-api }} doesn't exist in catalog, adding" - curl -X 'POST' \ + curl --fail-with-body -X 'POST' \ '${{ inputs.catalog-url }}/api/v0/0/sdk' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer ${{ inputs.catalog-api-token }}' \ From 6a5ae6cc0dee6ade5803905ec75d831be5ee5102 Mon Sep 17 00:00:00 2001 From: MMX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Nov 2025 19:23:59 +0300 Subject: [PATCH 156/192] Infrared Universal remote DBs unit test & move infrared_signal / infrared_brute_force into lib (#4284) * make infrared db unit tests * fix the tests, no assets are in asset folder oh no * fix formate * ship ir app along with unit_test pkg * libify ir signal and bruteforce parts * small cleanup * api: removed infrared methods (you can link with the lib if needed), unit_tests, infrared: adjusted to link with ir lib; api: added `__aeabi_f2d` --------- Co-authored-by: hedger Co-authored-by: hedger --- applications/debug/unit_tests/application.fam | 1 + .../unit_tests/tests/infrared/infrared_test.c | 76 +++++++++++++++++++ applications/main/infrared/application.fam | 8 +- applications/main/infrared/infrared_app.h | 4 +- applications/main/infrared/infrared_app_i.h | 2 +- applications/main/infrared/infrared_cli.c | 4 +- applications/main/infrared/infrared_remote.h | 2 +- fbt_options.py | 1 + lib/infrared/SConscript | 4 + .../infrared/signal}/infrared_brute_force.c | 0 .../infrared/signal}/infrared_brute_force.h | 8 ++ .../infrared/signal}/infrared_error_code.h | 8 ++ .../infrared/signal}/infrared_signal.c | 2 +- .../infrared/signal}/infrared_signal.h | 10 ++- targets/f18/api_symbols.csv | 3 +- targets/f7/api_symbols.csv | 34 ++++++++- .../f7/platform_specific/intrinsic_export.h | 1 + 17 files changed, 150 insertions(+), 18 deletions(-) rename {applications/main/infrared => lib/infrared/signal}/infrared_brute_force.c (100%) rename {applications/main/infrared => lib/infrared/signal}/infrared_brute_force.h (98%) rename {applications/main/infrared => lib/infrared/signal}/infrared_error_code.h (96%) rename {applications/main/infrared => lib/infrared/signal}/infrared_signal.c (99%) rename {applications/main/infrared => lib/infrared/signal}/infrared_signal.h (98%) diff --git a/applications/debug/unit_tests/application.fam b/applications/debug/unit_tests/application.fam index 252eb57c5..3190db3f0 100644 --- a/applications/debug/unit_tests/application.fam +++ b/applications/debug/unit_tests/application.fam @@ -131,6 +131,7 @@ App( apptype=FlipperAppType.PLUGIN, entry_point="get_api", requires=["unit_tests"], + fap_libs=["infrared"], ) App( diff --git a/applications/debug/unit_tests/tests/infrared/infrared_test.c b/applications/debug/unit_tests/tests/infrared/infrared_test.c index e64b630b2..9ca78c2f4 100644 --- a/applications/debug/unit_tests/tests/infrared/infrared_test.c +++ b/applications/debug/unit_tests/tests/infrared/infrared_test.c @@ -2,6 +2,7 @@ #include #include #include +#include #include "../test.h" // IWYU pragma: keep #define IR_TEST_FILES_DIR EXT_PATH("unit_tests/infrared/") @@ -13,6 +14,7 @@ typedef struct { InfraredEncoderHandler* encoder_handler; FuriString* file_path; FlipperFormat* ff; + InfraredBruteForce* brutedb; } InfraredTest; static InfraredTest* test; @@ -24,12 +26,14 @@ static void infrared_test_alloc(void) { test->encoder_handler = infrared_alloc_encoder(); test->ff = flipper_format_buffered_file_alloc(storage); test->file_path = furi_string_alloc(); + test->brutedb = infrared_brute_force_alloc(); } static void infrared_test_free(void) { furi_check(test); infrared_free_decoder(test->decoder_handler); infrared_free_encoder(test->encoder_handler); + infrared_brute_force_free(test->brutedb); flipper_format_free(test->ff); furi_string_free(test->file_path); furi_record_close(RECORD_STORAGE); @@ -523,6 +527,74 @@ MU_TEST(infrared_test_encoder_decoder_all) { infrared_test_run_encoder_decoder(InfraredProtocolPioneer, 1); } +MU_TEST(infrared_test_ac_database) { + infrared_brute_force_set_db_filename(test->brutedb, EXT_PATH("infrared/assets/ac.ir")); + uint32_t i = 0; + infrared_brute_force_add_record(test->brutedb, i++, "Off"); + infrared_brute_force_add_record(test->brutedb, i++, "Dh"); + infrared_brute_force_add_record(test->brutedb, i++, "Cool_hi"); + infrared_brute_force_add_record(test->brutedb, i++, "Heat_hi"); + infrared_brute_force_add_record(test->brutedb, i++, "Cool_lo"); + infrared_brute_force_add_record(test->brutedb, i++, "Heat_lo"); + + mu_assert( + infrared_brute_force_calculate_messages(test->brutedb) == InfraredErrorCodeNone, + "universal ac database is invalid"); + + infrared_brute_force_reset(test->brutedb); +} + +MU_TEST(infrared_test_audio_database) { + infrared_brute_force_set_db_filename(test->brutedb, EXT_PATH("infrared/assets/audio.ir")); + uint32_t i = 0; + infrared_brute_force_add_record(test->brutedb, i++, "Power"); + infrared_brute_force_add_record(test->brutedb, i++, "Mute"); + infrared_brute_force_add_record(test->brutedb, i++, "Play"); + infrared_brute_force_add_record(test->brutedb, i++, "Pause"); + infrared_brute_force_add_record(test->brutedb, i++, "Prev"); + infrared_brute_force_add_record(test->brutedb, i++, "Next"); + infrared_brute_force_add_record(test->brutedb, i++, "Vol_dn"); + infrared_brute_force_add_record(test->brutedb, i++, "Vol_up"); + + mu_assert( + infrared_brute_force_calculate_messages(test->brutedb) == InfraredErrorCodeNone, + "universal audio database is invalid"); + + infrared_brute_force_reset(test->brutedb); +} + +MU_TEST(infrared_test_projector_database) { + infrared_brute_force_set_db_filename(test->brutedb, EXT_PATH("infrared/assets/projector.ir")); + uint32_t i = 0; + infrared_brute_force_add_record(test->brutedb, i++, "Power"); + infrared_brute_force_add_record(test->brutedb, i++, "Mute"); + infrared_brute_force_add_record(test->brutedb, i++, "Vol_up"); + infrared_brute_force_add_record(test->brutedb, i++, "Vol_dn"); + + mu_assert( + infrared_brute_force_calculate_messages(test->brutedb) == InfraredErrorCodeNone, + "universal projector database is invalid"); + + infrared_brute_force_reset(test->brutedb); +} + +MU_TEST(infrared_test_tv_database) { + infrared_brute_force_set_db_filename(test->brutedb, EXT_PATH("infrared/assets/tv.ir")); + uint32_t i = 0; + infrared_brute_force_add_record(test->brutedb, i++, "Power"); + infrared_brute_force_add_record(test->brutedb, i++, "Mute"); + infrared_brute_force_add_record(test->brutedb, i++, "Vol_up"); + infrared_brute_force_add_record(test->brutedb, i++, "Ch_next"); + infrared_brute_force_add_record(test->brutedb, i++, "Vol_dn"); + infrared_brute_force_add_record(test->brutedb, i++, "Ch_prev"); + + mu_assert( + infrared_brute_force_calculate_messages(test->brutedb) == InfraredErrorCodeNone, + "universal tv database is invalid"); + + infrared_brute_force_reset(test->brutedb); +} + MU_TEST_SUITE(infrared_test) { MU_SUITE_CONFIGURE(&infrared_test_alloc, &infrared_test_free); @@ -543,6 +615,10 @@ MU_TEST_SUITE(infrared_test) { MU_RUN_TEST(infrared_test_decoder_pioneer); MU_RUN_TEST(infrared_test_decoder_mixed); MU_RUN_TEST(infrared_test_encoder_decoder_all); + MU_RUN_TEST(infrared_test_ac_database); + MU_RUN_TEST(infrared_test_audio_database); + MU_RUN_TEST(infrared_test_projector_database); + MU_RUN_TEST(infrared_test_tv_database); } int run_minunit_test_infrared(void) { diff --git a/applications/main/infrared/application.fam b/applications/main/infrared/application.fam index 79b3fdbfa..3ed928ad8 100644 --- a/applications/main/infrared/application.fam +++ b/applications/main/infrared/application.fam @@ -9,7 +9,7 @@ App( order=40, sources=["*.c", "!infrared_cli.c"], resources="resources", - fap_libs=["assets"], + fap_libs=["assets", "infrared"], fap_icon="icon.png", fap_category="Infrared", ) @@ -20,9 +20,5 @@ App( apptype=FlipperAppType.PLUGIN, entry_point="cli_ir_ep", requires=["cli"], - sources=[ - "infrared_cli.c", - "infrared_brute_force.c", - "infrared_signal.c", - ], + sources=["infrared_cli.c"], ) diff --git a/applications/main/infrared/infrared_app.h b/applications/main/infrared/infrared_app.h index a6f87402a..ef41f8f79 100644 --- a/applications/main/infrared/infrared_app.h +++ b/applications/main/infrared/infrared_app.h @@ -3,9 +3,7 @@ * @brief Infrared application - start here. * * @see infrared_app_i.h for the main application data structure and functions. - * @see infrared_signal.h for the infrared signal library - loading, storing and transmitting signals. - * @see infrared_remote.hl for the infrared remote library - loading, storing and manipulating remotes. - * @see infrared_brute_force.h for the infrared brute force - loading and transmitting multiple signals. + * @see infrared_remote.h for the infrared remote library - loading, storing and manipulating remotes */ #pragma once diff --git a/applications/main/infrared/infrared_app_i.h b/applications/main/infrared/infrared_app_i.h index 75d8502f2..caed2d601 100644 --- a/applications/main/infrared/infrared_app_i.h +++ b/applications/main/infrared/infrared_app_i.h @@ -31,7 +31,7 @@ #include "infrared_app.h" #include "infrared_remote.h" -#include "infrared_brute_force.h" +#include #include "infrared_custom_event.h" #include "scenes/infrared_scene.h" diff --git a/applications/main/infrared/infrared_cli.c b/applications/main/infrared/infrared_cli.c index eb13bcd79..b953daad6 100644 --- a/applications/main/infrared/infrared_cli.c +++ b/applications/main/infrared/infrared_cli.c @@ -8,8 +8,8 @@ #include #include -#include "infrared_signal.h" -#include "infrared_brute_force.h" +#include +#include #define INFRARED_CLI_BUF_SIZE (10U) #define INFRARED_CLI_FILE_NAME_SIZE (256U) diff --git a/applications/main/infrared/infrared_remote.h b/applications/main/infrared/infrared_remote.h index 14416cd65..65f00100f 100644 --- a/applications/main/infrared/infrared_remote.h +++ b/applications/main/infrared/infrared_remote.h @@ -11,7 +11,7 @@ */ #pragma once -#include "infrared_signal.h" +#include /** * @brief InfraredRemote opaque type declaration. diff --git a/fbt_options.py b/fbt_options.py index 191c4ef1a..a801c1c48 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -76,6 +76,7 @@ FIRMWARE_APPS = { "radio_device_cc1101_ext", "unit_tests", "js_app", + "infrared", "archive", ], } diff --git a/lib/infrared/SConscript b/lib/infrared/SConscript index a32248a64..aa4b52776 100644 --- a/lib/infrared/SConscript +++ b/lib/infrared/SConscript @@ -4,11 +4,15 @@ env.Append( CPPPATH=[ "#/lib/infrared/encoder_decoder", "#/lib/infrared/worker", + "#/lib/infrared/signal", ], SDK_HEADERS=[ File("encoder_decoder/infrared.h"), File("worker/infrared_worker.h"), File("worker/infrared_transmit.h"), + File("signal/infrared_error_code.h"), + File("signal/infrared_signal.h"), + File("signal/infrared_brute_force.h"), ], LINT_SOURCES=[ Dir("."), diff --git a/applications/main/infrared/infrared_brute_force.c b/lib/infrared/signal/infrared_brute_force.c similarity index 100% rename from applications/main/infrared/infrared_brute_force.c rename to lib/infrared/signal/infrared_brute_force.c diff --git a/applications/main/infrared/infrared_brute_force.h b/lib/infrared/signal/infrared_brute_force.h similarity index 98% rename from applications/main/infrared/infrared_brute_force.h rename to lib/infrared/signal/infrared_brute_force.h index 2c75d37f2..f6e31af98 100644 --- a/applications/main/infrared/infrared_brute_force.h +++ b/lib/infrared/signal/infrared_brute_force.h @@ -12,6 +12,10 @@ #include #include "infrared_error_code.h" +#ifdef __cplusplus +extern "C" { +#endif + /** * @brief InfraredBruteForce opaque type declaration. */ @@ -107,3 +111,7 @@ void infrared_brute_force_add_record( * @param[in,out] brute_force pointer to the instance to be reset. */ void infrared_brute_force_reset(InfraredBruteForce* brute_force); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/infrared/infrared_error_code.h b/lib/infrared/signal/infrared_error_code.h similarity index 96% rename from applications/main/infrared/infrared_error_code.h rename to lib/infrared/signal/infrared_error_code.h index 841721b17..7aa619c4a 100644 --- a/applications/main/infrared/infrared_error_code.h +++ b/lib/infrared/signal/infrared_error_code.h @@ -1,5 +1,9 @@ #pragma once +#ifdef __cplusplus +extern "C" { +#endif + typedef enum { InfraredErrorCodeNone = 0, InfraredErrorCodeFileOperationFailed = 0x800000, @@ -43,3 +47,7 @@ typedef enum { #define INFRARED_ERROR_PRESENT(error) (INFRARED_ERROR_GET_CODE(error) != InfraredErrorCodeNone) #define INFRARED_ERROR_CHECK(error, test_code) (INFRARED_ERROR_GET_CODE(error) == (test_code)) + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/infrared/infrared_signal.c b/lib/infrared/signal/infrared_signal.c similarity index 99% rename from applications/main/infrared/infrared_signal.c rename to lib/infrared/signal/infrared_signal.c index 75643f1d3..436de4e29 100644 --- a/applications/main/infrared/infrared_signal.c +++ b/lib/infrared/signal/infrared_signal.c @@ -85,7 +85,7 @@ static bool infrared_signal_is_raw_valid(const InfraredRawSignal* raw) { raw->frequency); return false; - } else if((raw->duty_cycle <= 0) || (raw->duty_cycle > 1)) { + } else if((raw->duty_cycle <= 0) || (raw->duty_cycle > 1.f)) { FURI_LOG_E(TAG, "Duty cycle is out of range (0 - 1): %f", (double)raw->duty_cycle); return false; diff --git a/applications/main/infrared/infrared_signal.h b/lib/infrared/signal/infrared_signal.h similarity index 98% rename from applications/main/infrared/infrared_signal.h rename to lib/infrared/signal/infrared_signal.h index 3fa7768b3..96afd8c2e 100644 --- a/applications/main/infrared/infrared_signal.h +++ b/lib/infrared/signal/infrared_signal.h @@ -10,7 +10,11 @@ #include "infrared_error_code.h" #include -#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /** * @brief InfraredSignal opaque type declaration. @@ -218,3 +222,7 @@ InfraredErrorCode * @param[in] signal pointer to the instance holding the signal to be transmitted. */ void infrared_signal_transmit(const InfraredSignal* signal); + +#ifdef __cplusplus +} +#endif diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 27b65e202..f66fcb9e4 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,87.0,, +Version,+,87.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -339,6 +339,7 @@ Function,-,Osal_MemSet,void*,"void*, int, unsigned int" Function,-,SystemCoreClockUpdate,void, Function,-,SystemInit,void, Function,-,_Exit,void,int +Function,+,__aeabi_f2d,double,float Function,+,__aeabi_uldivmod,void*,"uint64_t, uint64_t" Function,-,__assert,void,"const char*, int, const char*" Function,+,__assert_func,void,"const char*, int, const char*, const char*" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 15a3d360b..9963b3515 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,87.0,, +Version,+,87.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -63,6 +63,9 @@ Header,+,lib/ibutton/ibutton_protocols.h,, Header,+,lib/ibutton/ibutton_worker.h,, Header,+,lib/ieee754_parse_wrap/wrappers.h,, Header,+,lib/infrared/encoder_decoder/infrared.h,, +Header,+,lib/infrared/signal/infrared_brute_force.h,, +Header,+,lib/infrared/signal/infrared_error_code.h,, +Header,+,lib/infrared/signal/infrared_signal.h,, Header,+,lib/infrared/worker/infrared_transmit.h,, Header,+,lib/infrared/worker/infrared_worker.h,, Header,+,lib/lfrfid/lfrfid_dict_file.h,, @@ -418,6 +421,7 @@ Function,-,Osal_MemSet,void*,"void*, int, unsigned int" Function,-,SystemCoreClockUpdate,void, Function,-,SystemInit,void, Function,-,_Exit,void,int +Function,+,__aeabi_f2d,double,float Function,+,__aeabi_uldivmod,void*,"uint64_t, uint64_t" Function,-,__assert,void,"const char*, int, const char*" Function,+,__assert_func,void,"const char*, int, const char*, const char*" @@ -625,8 +629,8 @@ Function,-,arc4random_uniform,__uint32_t,__uint32_t Function,+,args_char_to_hex,_Bool,"char, char, uint8_t*" Function,+,args_get_first_word_length,size_t,FuriString* Function,+,args_length,size_t,FuriString* -Function,+,args_read_float_and_trim,_Bool,"FuriString*, float*" Function,+,args_read_duration,_Bool,"FuriString*, uint32_t*, const char*" +Function,+,args_read_float_and_trim,_Bool,"FuriString*, float*" Function,+,args_read_hex_bytes,_Bool,"FuriString*, uint8_t*, size_t" Function,+,args_read_int_and_trim,_Bool,"FuriString*, int*" Function,+,args_read_probably_quoted_string_and_trim,_Bool,"FuriString*, FuriString*" @@ -2065,6 +2069,16 @@ Function,-,infinity,double, Function,-,infinityf,float, Function,+,infrared_alloc_decoder,InfraredDecoderHandler*, Function,+,infrared_alloc_encoder,InfraredEncoderHandler*, +Function,-,infrared_brute_force_add_record,void,"InfraredBruteForce*, uint32_t, const char*" +Function,-,infrared_brute_force_alloc,InfraredBruteForce*, +Function,-,infrared_brute_force_calculate_messages,InfraredErrorCode,InfraredBruteForce* +Function,-,infrared_brute_force_free,void,InfraredBruteForce* +Function,-,infrared_brute_force_is_started,_Bool,const InfraredBruteForce* +Function,-,infrared_brute_force_reset,void,InfraredBruteForce* +Function,-,infrared_brute_force_send,_Bool,"InfraredBruteForce*, uint32_t" +Function,-,infrared_brute_force_set_db_filename,void,"InfraredBruteForce*, const char*" +Function,-,infrared_brute_force_start,_Bool,"InfraredBruteForce*, uint32_t, uint32_t*" +Function,-,infrared_brute_force_stop,void,InfraredBruteForce* Function,+,infrared_check_decoder_ready,const InfraredMessage*,InfraredDecoderHandler* Function,+,infrared_decode,const InfraredMessage*,"InfraredDecoderHandler*, _Bool, uint32_t" Function,+,infrared_encode,InfraredStatus,"InfraredEncoderHandler*, uint32_t*, _Bool*" @@ -2083,6 +2097,22 @@ Function,+,infrared_reset_encoder,void,"InfraredEncoderHandler*, const InfraredM Function,+,infrared_send,void,"const InfraredMessage*, int" Function,+,infrared_send_raw,void,"const uint32_t[], uint32_t, _Bool" Function,+,infrared_send_raw_ext,void,"const uint32_t[], uint32_t, _Bool, uint32_t, float" +Function,-,infrared_signal_alloc,InfraredSignal*, +Function,-,infrared_signal_free,void,InfraredSignal* +Function,-,infrared_signal_get_message,const InfraredMessage*,const InfraredSignal* +Function,-,infrared_signal_get_raw_signal,const InfraredRawSignal*,const InfraredSignal* +Function,-,infrared_signal_is_raw,_Bool,const InfraredSignal* +Function,-,infrared_signal_is_valid,_Bool,const InfraredSignal* +Function,-,infrared_signal_read,InfraredErrorCode,"InfraredSignal*, FlipperFormat*, FuriString*" +Function,-,infrared_signal_read_body,InfraredErrorCode,"InfraredSignal*, FlipperFormat*" +Function,-,infrared_signal_read_name,InfraredErrorCode,"FlipperFormat*, FuriString*" +Function,-,infrared_signal_save,InfraredErrorCode,"const InfraredSignal*, FlipperFormat*, const char*" +Function,-,infrared_signal_search_by_index_and_read,InfraredErrorCode,"InfraredSignal*, FlipperFormat*, size_t" +Function,-,infrared_signal_search_by_name_and_read,InfraredErrorCode,"InfraredSignal*, FlipperFormat*, const char*" +Function,-,infrared_signal_set_message,void,"InfraredSignal*, const InfraredMessage*" +Function,-,infrared_signal_set_raw_signal,void,"InfraredSignal*, const uint32_t*, size_t, uint32_t, float" +Function,-,infrared_signal_set_signal,void,"InfraredSignal*, const InfraredSignal*" +Function,-,infrared_signal_transmit,void,const InfraredSignal* Function,+,infrared_worker_alloc,InfraredWorker*, Function,+,infrared_worker_free,void,InfraredWorker* Function,+,infrared_worker_get_decoded_signal,const InfraredMessage*,const InfraredWorkerSignal* diff --git a/targets/f7/platform_specific/intrinsic_export.h b/targets/f7/platform_specific/intrinsic_export.h index d3c7be5e0..989bcafb2 100644 --- a/targets/f7/platform_specific/intrinsic_export.h +++ b/targets/f7/platform_specific/intrinsic_export.h @@ -8,6 +8,7 @@ extern "C" { void __clear_cache(void*, void*); void* __aeabi_uldivmod(uint64_t, uint64_t); +double __aeabi_f2d(float); #ifdef __cplusplus } From b54d63037b1764268cf16191d79dadc9e4fcd089 Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 6 Nov 2025 16:40:18 +0000 Subject: [PATCH 157/192] lib: infrared: fixed doxygen declarations to match implementation (#4305) --- lib/infrared/signal/infrared_brute_force.h | 9 +++++++-- lib/infrared/signal/infrared_signal.h | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/infrared/signal/infrared_brute_force.h b/lib/infrared/signal/infrared_brute_force.h index f6e31af98..b48d04533 100644 --- a/lib/infrared/signal/infrared_brute_force.h +++ b/lib/infrared/signal/infrared_brute_force.h @@ -55,11 +55,16 @@ void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const InfraredErrorCode infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force); /** - * @brief Start transmitting signals from a category stored in an InfraredBruteForce's instance dictionary. + * @brief Start transmitting signals from a category stored in the dictionary. + * + * The function locates the category identified by @p index, reports the number of + * records it contains via @p record_count, and prepares the brute-force instance + * to transmit those signals. On failure @p record_count is set to zero. * * @param[in,out] brute_force pointer to the instance to be started. * @param[in] index index of the signal category in the dictionary. - * @returns true on success, false otherwise. + * @param[out] record_count pointer that receives the number of records in the category. + * @returns true if the category is found and the backing database file is opened, false otherwise. */ bool infrared_brute_force_start( InfraredBruteForce* brute_force, diff --git a/lib/infrared/signal/infrared_signal.h b/lib/infrared/signal/infrared_signal.h index 96afd8c2e..93f1f88fe 100644 --- a/lib/infrared/signal/infrared_signal.h +++ b/lib/infrared/signal/infrared_signal.h @@ -163,7 +163,7 @@ InfraredErrorCode infrared_signal_read_name(FlipperFormat* ff, FuriString* name) * Same behaviour as infrared_signal_read(), but only the body is read. * * @param[in,out] ff pointer to the FlipperFormat file instance to read from. - * @param[out] body pointer to the InfraredSignal instance to hold the signal body. Must be properly allocated. + * @param[out] signal pointer to the InfraredSignal instance to hold the signal body. Must be properly allocated. * @returns InfraredErrorCodeNone if a signal body was successfully read, otherwise error code. */ InfraredErrorCode infrared_signal_read_body(InfraredSignal* signal, FlipperFormat* ff); From 6c83a67173858cda38ed66fef0d7c197d5afac2e Mon Sep 17 00:00:00 2001 From: Zinong Li <131403964+zinongli@users.noreply.github.com> Date: Thu, 6 Nov 2025 22:30:02 +0400 Subject: [PATCH 158/192] NFC FeliCa Improvement: Dump All Systems (#4293) * polling system code done * select service done * select system fixes. works fine now * clean up select sys by idx * reading, saving, loading done. render wrong * render first layer works * render fixed * fix Lite Render triage * regression Lite rendering * sync API with 1.40 release * clean up and lint --------- Co-authored-by: hedger --- .../helpers/protocol_support/felica/felica.c | 26 +- .../protocol_support/felica/felica_render.c | 23 +- .../protocol_support/felica/felica_render.h | 3 +- .../main/nfc/scenes/nfc_scene_config.h | 1 + .../nfc/scenes/nfc_scene_felica_more_info.c | 84 +--- .../main/nfc/scenes/nfc_scene_felica_system.c | 114 +++++ lib/nfc/protocols/felica/felica.c | 419 +++++++++--------- lib/nfc/protocols/felica/felica.h | 27 +- lib/nfc/protocols/felica/felica_i.c | 38 ++ lib/nfc/protocols/felica/felica_i.h | 5 + lib/nfc/protocols/felica/felica_poller.c | 91 +++- lib/nfc/protocols/felica/felica_poller_i.c | 32 +- lib/nfc/protocols/felica/felica_poller_i.h | 8 + targets/f7/api_symbols.csv | 2 +- 14 files changed, 557 insertions(+), 316 deletions(-) create mode 100644 applications/main/nfc/scenes/nfc_scene_felica_system.c diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica.c b/applications/main/nfc/helpers/protocol_support/felica/felica.c index 0996ca7a1..1b52b86bd 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica.c +++ b/applications/main/nfc/helpers/protocol_support/felica/felica.c @@ -102,19 +102,21 @@ static void nfc_scene_read_success_on_enter_felica(NfcApp* instance) { temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); nfc_render_felica_info(data, NfcProtocolFormatTypeShort, temp_str); } else { - bool all_unlocked = data->blocks_read == data->blocks_total; - furi_string_cat_printf( - temp_str, - "\e#%s\n", - all_unlocked ? "All Blocks Are Unlocked" : "Some Blocks Are Locked"); - nfc_render_felica_idm(data, NfcProtocolFormatTypeShort, temp_str); - uint8_t* ck_data = instance->felica_auth->card_key.data; - furi_string_cat_printf(temp_str, "Key:"); - for(uint8_t i = 0; i < 7; i++) { - furi_string_cat_printf(temp_str, " %02X", ck_data[i]); - if(i == 6) furi_string_cat_printf(temp_str, "..."); + if(data->workflow_type == FelicaLite) { + bool all_unlocked = data->blocks_read == data->blocks_total; + furi_string_cat_printf( + temp_str, + "\e#%s\n", + all_unlocked ? "All Blocks Are Unlocked" : "Some Blocks Are Locked"); + nfc_render_felica_idm(data, NfcProtocolFormatTypeShort, temp_str); + uint8_t* ck_data = instance->felica_auth->card_key.data; + furi_string_cat_printf(temp_str, "Key:"); + for(uint8_t i = 0; i < 7; i++) { + furi_string_cat_printf(temp_str, " %02X", ck_data[i]); + if(i == 6) furi_string_cat_printf(temp_str, "..."); + } + nfc_render_felica_blocks_count(data, temp_str, false); } - nfc_render_felica_blocks_count(data, temp_str, false); } felica_auth_reset(instance->felica_auth); diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica_render.c b/applications/main/nfc/helpers/protocol_support/felica/felica_render.c index 1ca992bcd..8773fa1f3 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica_render.c +++ b/applications/main/nfc/helpers/protocol_support/felica/felica_render.c @@ -6,14 +6,10 @@ void nfc_render_felica_blocks_count( bool render_auth_notification) { if(data->workflow_type == FelicaLite) { furi_string_cat_printf(str, "Blocks: %u\n", data->blocks_total); - furi_string_cat_printf(str, "\nBlocks Read: %u/%u", data->blocks_read, data->blocks_total); if(render_auth_notification && data->blocks_read != data->blocks_total) { furi_string_cat_printf(str, "\nAuth-protected blocks!"); } - } else if(data->workflow_type == FelicaStandard) { - furi_string_cat_printf( - str, "Public blocks Read: %lu", simple_array_get_count(data->public_blocks)); } } @@ -54,11 +50,7 @@ void nfc_render_felica_info( } furi_string_cat_printf(str, "\n"); - furi_string_cat_printf( - str, - "Services found: %lu \nAreas found: %lu\n", - simple_array_get_count(data->services), - simple_array_get_count(data->areas)); + furi_string_cat_printf(str, "Systems found: %lu \n", simple_array_get_count(data->systems)); nfc_render_felica_blocks_count(data, str, true); } @@ -136,9 +128,9 @@ void nfc_more_info_render_felica_lite_dump(const FelicaData* data, FuriString* s nfc_render_felica_block(&data->data.fs.crc_check, str, "CRC_CHCK", 15, 17); } -void nfc_more_info_render_felica_dir(const FelicaData* data, FuriString* str) { - const size_t area_count = simple_array_get_count(data->areas); - const size_t service_count = simple_array_get_count(data->services); +void nfc_more_info_render_felica_dir(const FelicaSystem* system, FuriString* str) { + const size_t area_count = simple_array_get_count(system->areas); + const size_t service_count = simple_array_get_count(system->services); furi_string_cat_printf(str, "\e#Directory Tree:\n"); @@ -150,11 +142,12 @@ void nfc_more_info_render_felica_dir(const FelicaData* data, FuriString* str) { furi_string_cat_printf( str, "::: ... are readable services\n||| ... are locked services\n"); } - felica_write_directory_tree(data, str); + felica_write_directory_tree(system, str); } void nfc_more_info_render_felica_blocks( const FelicaData* data, + const FelicaSystem* system, FuriString* str, const uint16_t service_code_key) { furi_string_cat_printf(str, "\n"); @@ -190,9 +183,9 @@ void nfc_more_info_render_felica_blocks( nfc_render_felica_block(&data->data.fs.crc_check, str, "CRC_CHCK", 15, 17); } else if(data->workflow_type == FelicaStandard) { - uint32_t public_blocks_count = simple_array_get_count(data->public_blocks); + uint32_t public_blocks_count = simple_array_get_count(system->public_blocks); for(size_t i = 0; i < public_blocks_count; i++) { - FelicaPublicBlock* public_block = simple_array_get(data->public_blocks, i); + FelicaPublicBlock* public_block = simple_array_get(system->public_blocks, i); if(public_block->service_code != service_code_key) { continue; // Skip blocks not matching the requested service code } diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica_render.h b/applications/main/nfc/helpers/protocol_support/felica/felica_render.h index 5c7c6d036..e0c270a18 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica_render.h +++ b/applications/main/nfc/helpers/protocol_support/felica/felica_render.h @@ -21,9 +21,10 @@ void nfc_render_felica_idm( NfcProtocolFormatType format_type, FuriString* str); -void nfc_more_info_render_felica_dir(const FelicaData* data, FuriString* str); +void nfc_more_info_render_felica_dir(const FelicaSystem* system, FuriString* str); void nfc_more_info_render_felica_blocks( const FelicaData* data, + const FelicaSystem* system, FuriString* str, const uint16_t service_code_key); diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index 1e3fd506f..a4b0185ee 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -75,5 +75,6 @@ ADD_SCENE(nfc, slix_unlock, SlixUnlock) ADD_SCENE(nfc, slix_unlock_success, SlixUnlockSuccess) ADD_SCENE(nfc, felica_more_info, FelicaMoreInfo) +ADD_SCENE(nfc, felica_system, FelicaSystem) ADD_SCENE(nfc, generate_info, GenerateInfo) diff --git a/applications/main/nfc/scenes/nfc_scene_felica_more_info.c b/applications/main/nfc/scenes/nfc_scene_felica_more_info.c index 7eee3d7b3..1401dcaf7 100644 --- a/applications/main/nfc/scenes/nfc_scene_felica_more_info.c +++ b/applications/main/nfc/scenes/nfc_scene_felica_more_info.c @@ -9,7 +9,6 @@ enum { }; enum SubmenuIndex { - SubmenuIndexDirectory, SubmenuIndexDynamic, // dynamic indices start here }; @@ -21,33 +20,22 @@ void nfc_scene_felica_more_info_on_enter(void* context) { scene_manager_get_scene_state(nfc->scene_manager, NfcSceneFelicaMoreInfo); const FelicaData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolFelica); - submenu_add_item( - submenu, - "Directory", - SubmenuIndexDirectory, - nfc_protocol_support_common_submenu_callback, - nfc); - - FuriString* label = furi_string_alloc(); - switch(data->workflow_type) { case FelicaLite: - furi_string_printf(label, "All blocks"); - submenu_add_item( - submenu, - furi_string_get_cstr(label), - SubmenuIndexDynamic, - nfc_protocol_support_common_submenu_callback, - nfc); + widget_reset(nfc->widget); + FuriString* temp_str = furi_string_alloc(); + nfc_more_info_render_felica_lite_dump(data, temp_str); + widget_add_text_scroll_element(nfc->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + return; break; case FelicaStandard: - for(uint32_t i = 0; i < simple_array_get_count(data->services); ++i) { - const FelicaService* service = simple_array_cget(data->services, i); - bool is_public = (service->attr & FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ) == 1; - if(!is_public) { - continue; - } - furi_string_printf(label, "Readable serv %04X", service->code); + FuriString* label = furi_string_alloc(); + + for(uint32_t i = 0; i < simple_array_get_count(data->systems); ++i) { + const FelicaSystem* system = simple_array_cget(data->systems, i); + furi_string_printf(label, "System %04X", system->system_code); submenu_add_item( submenu, furi_string_get_cstr(label), @@ -55,13 +43,12 @@ void nfc_scene_felica_more_info_on_enter(void* context) { nfc_protocol_support_common_submenu_callback, nfc); } + furi_string_free(label); break; default: break; } - furi_string_free(label); - if(state >= FelicaMoreInfoStateItem) { submenu_set_selected_item( nfc->submenu, state - FelicaMoreInfoStateItem + SubmenuIndexDynamic); @@ -78,49 +65,16 @@ bool nfc_scene_felica_more_info_on_event(void* context, SceneManagerEvent event) const uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneFelicaMoreInfo); - const FelicaData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolFelica); if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexDirectory) { - FuriString* temp_str = furi_string_alloc(); - nfc_more_info_render_felica_dir(data, temp_str); + const uint32_t index = event.event - SubmenuIndexDynamic; - widget_add_text_scroll_element( - nfc->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); - furi_string_free(temp_str); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneFelicaMoreInfo, FelicaMoreInfoStateItem + index); + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneFelicaSystem, index << 4); + scene_manager_next_scene(nfc->scene_manager, NfcSceneFelicaSystem); + consumed = true; - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); - scene_manager_set_scene_state( - nfc->scene_manager, - NfcSceneFelicaMoreInfo, - FelicaMoreInfoStateItem + SubmenuIndexDirectory); - consumed = true; - } else { - const uint16_t service_ind = event.event - 1; // offset the three enums above - - text_box_reset(nfc->text_box); - furi_string_reset(nfc->text_box_store); - - switch(data->workflow_type) { - case FelicaLite: - nfc_more_info_render_felica_lite_dump(data, nfc->text_box_store); - break; - case FelicaStandard: - const FelicaService* service = simple_array_cget(data->services, service_ind); - furi_string_cat_printf(nfc->text_box_store, "Service 0x%04X\n", service->code); - nfc_more_info_render_felica_blocks(data, nfc->text_box_store, service->code); - break; - default: - furi_string_set_str(nfc->text_box_store, "IC type not implemented yet"); - break; - } - text_box_set_font(nfc->text_box, TextBoxFontHex); - text_box_set_text(nfc->text_box, furi_string_get_cstr(nfc->text_box_store)); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneFelicaMoreInfo, FelicaMoreInfoStateItem + event.event); - consumed = true; - } } else if(event.type == SceneManagerEventTypeBack) { if(state >= FelicaMoreInfoStateItem) { widget_reset(nfc->widget); diff --git a/applications/main/nfc/scenes/nfc_scene_felica_system.c b/applications/main/nfc/scenes/nfc_scene_felica_system.c new file mode 100644 index 000000000..87ebbbc36 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_felica_system.c @@ -0,0 +1,114 @@ +#include "../nfc_app_i.h" + +#include "../helpers/protocol_support/nfc_protocol_support_gui_common.h" +#include "../helpers/protocol_support/felica/felica_render.h" + +enum SubmenuIndex { + SubmenuIndexDirectory, + SubmenuIndexDynamic, // dynamic indices start here +}; + +static void nfc_scene_felica_system_submenu_callback(void* context, uint32_t index) { + NfcApp* nfc = context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, index); +} + +void nfc_scene_felica_system_on_enter(void* context) { + NfcApp* nfc = context; + Submenu* submenu = nfc->submenu; + submenu_reset(nfc->submenu); + + const uint32_t system_index = + scene_manager_get_scene_state(nfc->scene_manager, NfcSceneFelicaSystem) >> 4; + const FelicaData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolFelica); + + submenu_add_item( + submenu, "Directory", SubmenuIndexDirectory, nfc_scene_felica_system_submenu_callback, nfc); + + FuriString* label = furi_string_alloc(); + + const FelicaSystem* system = simple_array_cget(data->systems, system_index); + for(uint32_t i = 0; i < simple_array_get_count(system->services); ++i) { + const FelicaService* service = simple_array_cget(system->services, i); + bool is_public = (service->attr & FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ) == 1; + if(!is_public) { + continue; + } + furi_string_printf(label, "Readable serv %04X", service->code); + submenu_add_item( + submenu, + furi_string_get_cstr(label), + i + SubmenuIndexDynamic, + nfc_protocol_support_common_submenu_callback, + nfc); + } + + furi_string_free(label); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_felica_system_on_event(void* context, SceneManagerEvent event) { + NfcApp* nfc = context; + bool consumed = false; + + const uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneFelicaSystem); + const FelicaData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolFelica); + + const uint32_t system_index = state >> 4; + const FelicaSystem* system = simple_array_cget(data->systems, system_index); + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventViewExit) { + consumed = scene_manager_previous_scene(nfc->scene_manager); + } else { + if(event.event == SubmenuIndexDirectory) { + FuriString* temp_str = furi_string_alloc(); + nfc_more_info_render_felica_dir(system, temp_str); + + widget_add_text_scroll_element( + nfc->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + } else { + const uint32_t service_ind = + event.event - SubmenuIndexDynamic; // offset the three enums above + + text_box_reset(nfc->text_box); + furi_string_reset(nfc->text_box_store); + + const FelicaService* service = simple_array_cget(system->services, service_ind); + furi_string_cat_printf(nfc->text_box_store, "Service 0x%04X\n", service->code); + nfc_more_info_render_felica_blocks( + data, system, nfc->text_box_store, service->code); + + text_box_set_font(nfc->text_box, TextBoxFontHex); + text_box_set_text(nfc->text_box, furi_string_get_cstr(nfc->text_box_store)); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); + } + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneFelicaSystem, state | 1); + consumed = true; + } + + } else if(event.type == SceneManagerEventTypeBack) { + if(state & 1) { + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneFelicaSystem, state & ~1); + consumed = true; + } + } + + return consumed; +} + +void nfc_scene_felica_system_on_exit(void* context) { + NfcApp* nfc = context; + + // Clear views + widget_reset(nfc->widget); + text_box_reset(nfc->text_box); + furi_string_reset(nfc->text_box_store); + submenu_reset(nfc->submenu); +} diff --git a/lib/nfc/protocols/felica/felica.c b/lib/nfc/protocols/felica/felica.c index 4e0ff7472..2e8d5f92a 100644 --- a/lib/nfc/protocols/felica/felica.c +++ b/lib/nfc/protocols/felica/felica.c @@ -42,26 +42,16 @@ FelicaData* felica_alloc(void) { FelicaData* data = malloc(sizeof(FelicaData)); furi_check(data); - data->services = simple_array_alloc(&felica_service_array_cfg); - data->areas = simple_array_alloc(&felica_area_array_cfg); - data->public_blocks = simple_array_alloc(&felica_public_block_array_cfg); - furi_check(data->services); - furi_check(data->areas); - furi_check(data->public_blocks); + data->systems = simple_array_alloc(&felica_system_array_cfg); + furi_check(data->systems); return data; } void felica_free(FelicaData* data) { furi_check(data); - furi_check(data->services); - simple_array_free(data->services); - - furi_check(data->areas); - simple_array_free(data->areas); - - furi_check(data->public_blocks); - simple_array_free(data->public_blocks); + furi_check(data->systems); + simple_array_free(data->systems); free(data); } @@ -69,16 +59,8 @@ void felica_free(FelicaData* data) { void felica_reset(FelicaData* data) { furi_check(data); - if(data->services) { - simple_array_reset(data->services); - } - - if(data->areas) { - simple_array_reset(data->areas); - } - - if(data->public_blocks) { - simple_array_reset(data->public_blocks); + if(data->systems) { + simple_array_reset(data->systems); } data->blocks_read = 0; @@ -102,9 +84,7 @@ void felica_copy(FelicaData* data, const FelicaData* other) { data->data = other->data; data->workflow_type = other->workflow_type; - simple_array_copy(data->services, other->services); - simple_array_copy(data->areas, other->areas); - simple_array_copy(data->public_blocks, other->public_blocks); + simple_array_copy(data->systems, other->systems); } bool felica_verify(FelicaData* data, const FuriString* device_type) { @@ -175,99 +155,125 @@ bool felica_load(FelicaData* data, FlipperFormat* ff, uint32_t version) { } while(false); break; case FelicaStandard: - // Areas - do { - uint32_t area_count = 0; - if(!flipper_format_read_uint32(ff, "Area found", &area_count, 1)) break; - simple_array_init(data->areas, area_count); + uint32_t systems_total = 0; + if(!flipper_format_read_uint32(ff, "System found", &systems_total, 1)) break; + simple_array_init(data->systems, systems_total); + + for(uint8_t sys_idx = 0; sys_idx < systems_total; sys_idx++) { + FelicaSystem* system = simple_array_get(data->systems, sys_idx); + uint16_t system_code = 0; furi_string_reset(str_key_buffer); furi_string_reset(str_data_buffer); - for(uint16_t i = 0; i < area_count; i++) { - furi_string_printf(str_key_buffer, "Area %03X", i); - if(!flipper_format_read_string( - ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) { - break; - } - FelicaArea* area = simple_array_get(data->areas, i); - if(sscanf( - furi_string_get_cstr(str_data_buffer), - "| Code %04hX | Services #%03hX-#%03hX |", - &area->code, - &area->first_idx, - &area->last_idx) != 3) { - break; - } - } - } while(false); - - // Services - do { - uint32_t service_count = 0; - if(!flipper_format_read_uint32(ff, "Service found", &service_count, 1)) break; - simple_array_init(data->services, service_count); - - furi_string_reset(str_key_buffer); - furi_string_reset(str_data_buffer); - for(uint16_t i = 0; i < service_count; i++) { - furi_string_printf(str_key_buffer, "Service %03X", i); - if(!flipper_format_read_string( - ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) { - break; - } - FelicaService* service = simple_array_get(data->services, i); - - // all unread in the beginning. reserved for future block load - if(!sscanf( - furi_string_get_cstr(str_data_buffer), "| Code %04hX |", &service->code)) { - break; - } - service->attr = service->code & 0x3F; - } - } while(false); - - // Public blocks - do { - furi_string_reset(str_data_buffer); - furi_string_reset(str_key_buffer); - uint32_t public_block_count = 0; - if(!flipper_format_read_uint32(ff, "Public blocks read", &public_block_count, 1)) + furi_string_printf(str_key_buffer, "System %02X", sys_idx); + if(!flipper_format_read_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) break; - simple_array_init(data->public_blocks, public_block_count); - for(uint16_t i = 0; i < public_block_count; i++) { - furi_string_printf(str_key_buffer, "Block %04X", i); - if(!flipper_format_read_string( - ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) { - break; - } - FelicaPublicBlock* public_block = simple_array_get(data->public_blocks, i); - if(sscanf( - furi_string_get_cstr(str_data_buffer), - "| Service code %04hX | Block index %02hhX |", - &public_block->service_code, - &public_block->block_idx) != 2) { - break; - } - - size_t needle = furi_string_search_str(str_data_buffer, "Data: "); - if(needle == FURI_STRING_FAILURE) { - break; - } - needle += 6; // length of "Data: " = 6 - furi_string_mid(str_data_buffer, needle, 3 * FELICA_DATA_BLOCK_SIZE); - furi_string_replace_all(str_data_buffer, " ", ""); - if(!hex_chars_to_uint8( - furi_string_get_cstr(str_data_buffer), public_block->block.data)) { - break; - } - - furi_string_reset(str_data_buffer); - for(size_t j = 0; j < FELICA_DATA_BLOCK_SIZE; j++) { - furi_string_cat_printf(str_data_buffer, "%02X ", public_block->block.data[j]); - } + if(!sscanf(furi_string_get_cstr(str_data_buffer), "%04hX", &system_code)) { + break; } - } while(false); + + system->system_code = system_code; + system->system_code_idx = sys_idx; + + // Areas + do { + uint32_t area_count = 0; + if(!flipper_format_read_uint32(ff, "Area found", &area_count, 1)) break; + simple_array_init(system->areas, area_count); + + furi_string_reset(str_key_buffer); + furi_string_reset(str_data_buffer); + for(uint16_t i = 0; i < area_count; i++) { + furi_string_printf(str_key_buffer, "Area %03X", i); + if(!flipper_format_read_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) { + break; + } + FelicaArea* area = simple_array_get(system->areas, i); + if(sscanf( + furi_string_get_cstr(str_data_buffer), + "| Code %04hX | Services #%03hX-#%03hX |", + &area->code, + &area->first_idx, + &area->last_idx) != 3) { + break; + } + } + } while(false); + + // Services + do { + uint32_t service_count = 0; + if(!flipper_format_read_uint32(ff, "Service found", &service_count, 1)) break; + simple_array_init(system->services, service_count); + + furi_string_reset(str_key_buffer); + furi_string_reset(str_data_buffer); + for(uint16_t i = 0; i < service_count; i++) { + furi_string_printf(str_key_buffer, "Service %03X", i); + if(!flipper_format_read_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) { + break; + } + FelicaService* service = simple_array_get(system->services, i); + + // all unread in the beginning. reserved for future block load + if(!sscanf( + furi_string_get_cstr(str_data_buffer), + "| Code %04hX |", + &service->code)) { + break; + } + service->attr = service->code & 0x3F; + } + } while(false); + + // Public blocks + do { + furi_string_reset(str_data_buffer); + furi_string_reset(str_key_buffer); + uint32_t public_block_count = 0; + if(!flipper_format_read_uint32(ff, "Public blocks read", &public_block_count, 1)) + break; + simple_array_init(system->public_blocks, public_block_count); + for(uint16_t i = 0; i < public_block_count; i++) { + furi_string_printf(str_key_buffer, "Block %04X", i); + if(!flipper_format_read_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) { + break; + } + + FelicaPublicBlock* public_block = simple_array_get(system->public_blocks, i); + if(sscanf( + furi_string_get_cstr(str_data_buffer), + "| Service code %04hX | Block index %02hhX |", + &public_block->service_code, + &public_block->block_idx) != 2) { + break; + } + + size_t needle = furi_string_search_str(str_data_buffer, "Data: "); + if(needle == FURI_STRING_FAILURE) { + break; + } + needle += 6; // length of "Data: " = 6 + furi_string_mid(str_data_buffer, needle, 3 * FELICA_DATA_BLOCK_SIZE); + furi_string_replace_all(str_data_buffer, " ", ""); + if(!hex_chars_to_uint8( + furi_string_get_cstr(str_data_buffer), public_block->block.data)) { + break; + } + + furi_string_reset(str_data_buffer); + for(size_t j = 0; j < FELICA_DATA_BLOCK_SIZE; j++) { + furi_string_cat_printf( + str_data_buffer, "%02X ", public_block->block.data[j]); + } + } + } while(false); + } break; default: break; @@ -334,88 +340,107 @@ bool felica_save(const FelicaData* data, FlipperFormat* ff) { case FelicaStandard: if(!flipper_format_write_comment_cstr(ff, "Felica Standard specific data")) break; + uint32_t systems_count = simple_array_get_count(data->systems); + if(!flipper_format_write_uint32(ff, "System found", &systems_count, 1)) break; + for(uint32_t sys_idx = 0; sys_idx < systems_count; sys_idx++) { + FelicaSystem* system = simple_array_get(data->systems, sys_idx); - do { - uint32_t area_count = simple_array_get_count(data->areas); - uint32_t service_count = simple_array_get_count(data->services); - // Note: The theoretical max area/service count is 2^10 - // So uint16_t is already enough for practical usage - // The following key index print will use %03X because 12 bits are enough to cover 0-1023 - - // Area count - if(!flipper_format_write_uint32(ff, "Area found", &area_count, 1)) break; - - // Area data furi_string_reset(str_data_buffer); furi_string_reset(str_key_buffer); - for(uint16_t i = 0; i < area_count; i++) { - FelicaArea* area = simple_array_get(data->areas, i); - furi_string_printf(str_key_buffer, "Area %03X", i); - furi_string_printf( - str_data_buffer, - "| Code %04X | Services #%03X-#%03X |", - area->code, - area->first_idx, - area->last_idx); - if(!flipper_format_write_string( - ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) - break; - } - if(!flipper_format_write_empty_line(ff)) break; - - // Service count - if(!flipper_format_write_uint32(ff, "Service found", &service_count, 1)) break; - - // Service data - furi_string_reset(str_data_buffer); - furi_string_reset(str_key_buffer); - for(uint16_t i = 0; i < service_count; i++) { - FelicaService* service = simple_array_get(data->services, i); - furi_string_printf(str_key_buffer, "Service %03X", i); - furi_string_printf( - str_data_buffer, "| Code %04X | Attrib. %02X ", service->code, service->attr); - felica_service_get_attribute_string(service, str_data_buffer); - if(!flipper_format_write_string( - ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) - break; - } - if(!flipper_format_write_empty_line(ff)) break; - - // Directory tree - furi_string_reset(str_data_buffer); - furi_string_reset(str_key_buffer); - furi_string_printf( - str_data_buffer, "\n::: ... are public services\n||| ... are private services"); - felica_write_directory_tree(data, str_data_buffer); - furi_string_replace_all(str_data_buffer, ":", "+"); - // We use a clearer marker in saved text files - if(!flipper_format_write_string(ff, "Directory Tree", str_data_buffer)) break; - } while(false); - - // Public blocks - do { - uint32_t public_block_count = simple_array_get_count(data->public_blocks); - if(!flipper_format_write_uint32(ff, "Public blocks read", &public_block_count, 1)) + furi_string_printf(str_key_buffer, "\n\nSystem %02X", (uint8_t)sys_idx); + furi_string_printf(str_data_buffer, "%04X", system->system_code); + if(!flipper_format_write_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) break; - furi_string_reset(str_data_buffer); - furi_string_reset(str_key_buffer); - for(uint16_t i = 0; i < public_block_count; i++) { - FelicaPublicBlock* public_block = simple_array_get(data->public_blocks, i); - furi_string_printf(str_key_buffer, "Block %04X", i); + if(!flipper_format_write_empty_line(ff)) break; + + do { + uint32_t area_count = simple_array_get_count(system->areas); + uint32_t service_count = simple_array_get_count(system->services); + // Note: The theoretical max area/service count is 2^10 + // So uint16_t is already enough for practical usage + // The following key index print will use %03X because 12 bits are enough to cover 0-1023 + + // Area count + if(!flipper_format_write_uint32(ff, "Area found", &area_count, 1)) break; + + // Area data + furi_string_reset(str_data_buffer); + furi_string_reset(str_key_buffer); + for(uint16_t i = 0; i < area_count; i++) { + FelicaArea* area = simple_array_get(system->areas, i); + furi_string_printf(str_key_buffer, "Area %03X", i); + furi_string_printf( + str_data_buffer, + "| Code %04X | Services #%03X-#%03X |", + area->code, + area->first_idx, + area->last_idx); + if(!flipper_format_write_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) + break; + } + if(!flipper_format_write_empty_line(ff)) break; + + // Service count + if(!flipper_format_write_uint32(ff, "Service found", &service_count, 1)) break; + + // Service data + furi_string_reset(str_data_buffer); + furi_string_reset(str_key_buffer); + for(uint16_t i = 0; i < service_count; i++) { + FelicaService* service = simple_array_get(system->services, i); + furi_string_printf(str_key_buffer, "Service %03X", i); + furi_string_printf( + str_data_buffer, + "| Code %04X | Attrib. %02X ", + service->code, + service->attr); + felica_service_get_attribute_string(service, str_data_buffer); + if(!flipper_format_write_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) + break; + } + if(!flipper_format_write_empty_line(ff)) break; + + // Directory tree + furi_string_reset(str_data_buffer); + furi_string_reset(str_key_buffer); furi_string_printf( str_data_buffer, - "| Service code %04X | Block index %02X | Data: ", - public_block->service_code, - public_block->block_idx); - for(uint8_t j = 0; j < FELICA_DATA_BLOCK_SIZE; j++) { - furi_string_cat_printf(str_data_buffer, "%02X ", public_block->block.data[j]); - } - furi_string_cat_printf(str_data_buffer, "|"); - if(!flipper_format_write_string( - ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) + "\n::: ... are public services\n||| ... are private services"); + felica_write_directory_tree(system, str_data_buffer); + furi_string_replace_all(str_data_buffer, ":", "+"); + // We use a clearer marker in saved text files + if(!flipper_format_write_string(ff, "Directory Tree", str_data_buffer)) break; + } while(false); + + // Public blocks + do { + uint32_t public_block_count = simple_array_get_count(system->public_blocks); + if(!flipper_format_write_uint32(ff, "Public blocks read", &public_block_count, 1)) break; - } - } while(false); + furi_string_reset(str_data_buffer); + furi_string_reset(str_key_buffer); + for(uint16_t i = 0; i < public_block_count; i++) { + FelicaPublicBlock* public_block = simple_array_get(system->public_blocks, i); + furi_string_printf(str_key_buffer, "Block %04X", i); + furi_string_printf( + str_data_buffer, + "| Service code %04X | Block index %02X | Data: ", + public_block->service_code, + public_block->block_idx); + for(uint8_t j = 0; j < FELICA_DATA_BLOCK_SIZE; j++) { + furi_string_cat_printf( + str_data_buffer, "%02X ", public_block->block.data[j]); + } + furi_string_cat_printf(str_data_buffer, "|"); + if(!flipper_format_write_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) + break; + } + } while(false); + } break; default: break; @@ -436,9 +461,7 @@ bool felica_is_equal(const FelicaData* data, const FelicaData* other) { memcmp(data->pmm.data, other->pmm.data, sizeof(FelicaPMm)) == 0 && data->blocks_total == other->blocks_total && data->blocks_read == other->blocks_read && memcmp(&data->data, &other->data, sizeof(data->data)) == 0 && - simple_array_is_equal(data->services, other->services) && - simple_array_is_equal(data->areas, other->areas) && - simple_array_is_equal(data->public_blocks, other->public_blocks); + simple_array_is_equal(data->systems, other->systems); } const char* felica_get_device_name(const FelicaData* data, NfcDeviceNameType name_type) { @@ -640,8 +663,8 @@ void felica_calculate_mac_write( felica_calculate_mac(ctx, session_swapped, rc, first_block, data, FELICA_DATA_BLOCK_SIZE, mac); } -void felica_write_directory_tree(const FelicaData* data, FuriString* str) { - furi_check(data); +void felica_write_directory_tree(const FelicaSystem* system, FuriString* str) { + furi_check(system); furi_check(str); furi_string_cat_str(str, "\n"); @@ -650,12 +673,12 @@ void felica_write_directory_tree(const FelicaData* data, FuriString* str) { uint8_t depth = 0; size_t area_iter = 0; - const size_t area_count = simple_array_get_count(data->areas); - const size_t service_count = simple_array_get_count(data->services); + const size_t area_count = simple_array_get_count(system->areas); + const size_t service_count = simple_array_get_count(system->services); for(size_t svc_idx = 0; svc_idx < service_count; ++svc_idx) { while(area_iter < area_count) { - const FelicaArea* next_area = simple_array_get(data->areas, area_iter); + const FelicaArea* next_area = simple_array_get(system->areas, area_iter); if(next_area->first_idx != svc_idx) break; for(uint8_t i = 0; i < depth - 1; ++i) @@ -667,7 +690,7 @@ void felica_write_directory_tree(const FelicaData* data, FuriString* str) { area_iter++; } - const FelicaService* service = simple_array_get(data->services, svc_idx); + const FelicaService* service = simple_array_get(system->services, svc_idx); bool is_public = (service->attr & FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ) != 0; for(uint8_t i = 0; i < depth - 1; ++i) diff --git a/lib/nfc/protocols/felica/felica.h b/lib/nfc/protocols/felica/felica.h index 5a5a75215..bc83ff846 100644 --- a/lib/nfc/protocols/felica/felica.h +++ b/lib/nfc/protocols/felica/felica.h @@ -50,8 +50,10 @@ extern "C" { #define FELICA_TIME_SLOT_8 (0x07U) #define FELICA_TIME_SLOT_16 (0x0FU) -#define FELICA_CMD_LIST_SERVICE_CODE 0x0A -#define FELICA_CMD_LIST_SERVICE_CODE_RESP 0x0B +#define FELICA_CMD_LIST_SERVICE_CODE 0x0A +#define FELICA_CMD_LIST_SERVICE_CODE_RESP 0x0B +#define FELICA_CMD_REQUEST_SYSTEM_CODE 0x0C +#define FELICA_CMD_REQUEST_SYSTEM_CODE_RESP 0x0D #define FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ (0b000001) #define FELICA_SERVICE_ATTRIBUTE_READ_ONLY (0b000010) @@ -182,6 +184,14 @@ typedef struct { uint8_t block_idx; } FelicaPublicBlock; +typedef struct { + uint8_t system_code_idx; + uint16_t system_code; + SimpleArray* services; + SimpleArray* areas; + SimpleArray* public_blocks; +} FelicaSystem; + /** @brief Structure used to store Felica data and additional values about reading */ typedef struct { FelicaIDm idm; @@ -190,9 +200,8 @@ typedef struct { uint8_t blocks_read; FelicaFSUnion data; - SimpleArray* services; - SimpleArray* areas; - SimpleArray* public_blocks; + SimpleArray* systems; + FelicaWorkflowType workflow_type; } FelicaData; @@ -248,6 +257,12 @@ typedef struct { uint8_t data[]; } FelicaListServiceCommandResponse; +typedef struct { + FelicaCommandHeaderRaw header; + uint8_t system_count; + uint8_t system_code[]; +} FelicaListSystemCodeCommandResponse; + typedef FelicaCommandResponseHeader FelicaListenerWriteCommandResponse; typedef FelicaCommandResponseHeader FelicaPollerWriteCommandResponse; @@ -309,7 +324,7 @@ void felica_calculate_mac_write( const uint8_t* data, uint8_t* mac); -void felica_write_directory_tree(const FelicaData* data, FuriString* str); +void felica_write_directory_tree(const FelicaSystem* system, FuriString* str); void felica_get_workflow_type(FelicaData* data); diff --git a/lib/nfc/protocols/felica/felica_i.c b/lib/nfc/protocols/felica/felica_i.c index e265ea862..c5ecdf121 100644 --- a/lib/nfc/protocols/felica/felica_i.c +++ b/lib/nfc/protocols/felica/felica_i.c @@ -1,5 +1,36 @@ #include "felica_i.h" +void felica_system_init(FelicaSystem* system) { + system->system_code = 0; + system->system_code_idx = 0; + system->services = simple_array_alloc(&felica_service_array_cfg); + system->areas = simple_array_alloc(&felica_area_array_cfg); + system->public_blocks = simple_array_alloc(&felica_public_block_array_cfg); +} + +void felica_system_reset(FelicaSystem* system) { + furi_check(system); + system->system_code = 0; + system->system_code_idx = 0; + furi_check(system->services); + furi_check(system->areas); + furi_check(system->public_blocks); + simple_array_free(system->services); + simple_array_free(system->areas); + simple_array_free(system->public_blocks); + memset(system, 0, sizeof(FelicaSystem)); +} + +void felica_system_copy(FelicaSystem* system, const FelicaSystem* other) { + furi_check(system); + furi_check(other); + system->system_code = other->system_code; + system->system_code_idx = other->system_code_idx; + simple_array_copy(system->services, other->services); + simple_array_copy(system->areas, other->areas); + simple_array_copy(system->public_blocks, other->public_blocks); +} + const SimpleArrayConfig felica_service_array_cfg = { .init = NULL, .copy = NULL, @@ -20,3 +51,10 @@ const SimpleArrayConfig felica_public_block_array_cfg = { .reset = NULL, .type_size = sizeof(FelicaPublicBlock), }; + +const SimpleArrayConfig felica_system_array_cfg = { + .init = (SimpleArrayInit)felica_system_init, + .copy = (SimpleArrayCopy)felica_system_copy, + .reset = (SimpleArrayReset)felica_system_reset, + .type_size = sizeof(FelicaSystem), +}; diff --git a/lib/nfc/protocols/felica/felica_i.h b/lib/nfc/protocols/felica/felica_i.h index a708aa729..d55077c5e 100644 --- a/lib/nfc/protocols/felica/felica_i.h +++ b/lib/nfc/protocols/felica/felica_i.h @@ -8,3 +8,8 @@ extern const SimpleArrayConfig felica_service_array_cfg; extern const SimpleArrayConfig felica_area_array_cfg; extern const SimpleArrayConfig felica_public_block_array_cfg; +extern const SimpleArrayConfig felica_system_array_cfg; + +void felica_system_init(FelicaSystem* system); +void felica_system_reset(FelicaSystem* system); +void felica_system_copy(FelicaSystem* system, const FelicaSystem* other); diff --git a/lib/nfc/protocols/felica/felica_poller.c b/lib/nfc/protocols/felica/felica_poller.c index a6964c685..98e07df42 100644 --- a/lib/nfc/protocols/felica/felica_poller.c +++ b/lib/nfc/protocols/felica/felica_poller.c @@ -12,6 +12,7 @@ ARRAY_DEF(felica_service_array, FelicaService, M_POD_OPLIST); // -V658 ARRAY_DEF(felica_area_array, FelicaArea, M_POD_OPLIST); // -V658 ARRAY_DEF(felica_public_block_array, FelicaPublicBlock, M_POD_OPLIST); // -V658 +ARRAY_DEF(felica_system_array, FelicaSystem, M_POD_OPLIST); // -V658 typedef NfcCommand (*FelicaPollerReadHandler)(FelicaPoller* instance); @@ -43,6 +44,9 @@ static FelicaPoller* felica_poller_alloc(Nfc* nfc) { instance->general_event.event_data = &instance->felica_event; instance->general_event.instance = instance; + instance->systems_read = 0; + instance->systems_total = 0; + return instance; } @@ -94,7 +98,7 @@ NfcCommand felica_poller_state_handler_activate(FelicaPoller* instance) { switch(instance->data->workflow_type) { case FelicaStandard: - instance->state = FelicaPollerStateTraverseStandardSystem; + instance->state = FelicaPollerStateListSystem; break; case FelicaLite: instance->state = FelicaPollerStateReadLiteBlocks; @@ -117,6 +121,44 @@ NfcCommand felica_poller_state_handler_activate(FelicaPoller* instance) { return command; } +NfcCommand felica_poller_state_handler_list_system(FelicaPoller* instance) { + FURI_LOG_D(TAG, "List System"); + + NfcCommand command = NfcCommandContinue; + + FelicaListSystemCodeCommandResponse* response_system_code; + FelicaError error = felica_poller_list_system_code(instance, &response_system_code); + + instance->systems_total = response_system_code->system_count; + simple_array_init(instance->data->systems, instance->systems_total); + uint8_t* system_codes = response_system_code->system_code; + + for(uint8_t i = 0; i < instance->systems_total; i++) { + FelicaSystem* system = simple_array_get(instance->data->systems, i); + system->system_code = system_codes[i * 2] << 8 | system_codes[i * 2 + 1]; + system->system_code_idx = i; + } + + if(error == FelicaErrorNone) { + instance->state = FelicaPollerStateSelectSystemIndex; + } else if(error != FelicaErrorTimeout) { + instance->felica_event.type = FelicaPollerEventTypeError; + instance->felica_event_data.error = error; + instance->state = FelicaPollerStateReadFailed; + } + return command; +} + +NfcCommand felica_poller_state_handler_select_system_idx(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Select System Index %d", instance->systems_read); + uint8_t system_index_mask = instance->systems_read << 4; + instance->data->idm.data[0] &= 0x0F; + instance->data->idm.data[0] |= system_index_mask; + instance->state = FelicaPollerStateTraverseStandardSystem; + + return NfcCommandContinue; +} + NfcCommand felica_poller_state_handler_auth_internal(FelicaPoller* instance) { FURI_LOG_D(TAG, "Auth Internal"); @@ -274,6 +316,8 @@ NfcCommand felica_poller_state_handler_traverse_standard_system(FelicaPoller* in service->code = code_begin; service->attr = (uint8_t)(code_begin & 0x3F); + FURI_LOG_D(TAG, "Service %04X", service->code); + if(felica_area_array_size(area_buffer)) { FelicaArea* current_area = felica_area_array_back(area_buffer); current_area->last_idx = (uint16_t)(felica_service_array_size(service_buffer) - 1); @@ -284,31 +328,30 @@ NfcCommand felica_poller_state_handler_traverse_standard_system(FelicaPoller* in const size_t service_num = felica_service_array_size(service_buffer); const size_t area_num = felica_area_array_size(area_buffer); + FelicaSystem* system = simple_array_get(instance->data->systems, instance->systems_read); if(service_num) { - simple_array_init(instance->data->services, (uint32_t)service_num); + simple_array_init(system->services, (uint32_t)service_num); memcpy( - simple_array_get(instance->data->services, 0), + simple_array_get(system->services, 0), service_buffer->ptr, service_num * sizeof(FelicaService)); } else { - simple_array_reset(instance->data->services); + simple_array_reset(system->services); } if(area_num) { - simple_array_init(instance->data->areas, (uint32_t)area_num); + simple_array_init(system->areas, (uint32_t)area_num); memcpy( - simple_array_get(instance->data->areas, 0), - area_buffer->ptr, - area_num * sizeof(FelicaArea)); + simple_array_get(system->areas, 0), area_buffer->ptr, area_num * sizeof(FelicaArea)); } else { - simple_array_reset(instance->data->areas); + simple_array_reset(system->areas); } FURI_LOG_I( TAG, "Services found: %lu, Areas found: %lu", - simple_array_get_count(instance->data->services), - simple_array_get_count(instance->data->areas)); + simple_array_get_count(system->services), + simple_array_get_count(system->areas)); felica_service_array_clear(service_buffer); felica_area_array_clear(area_buffer); @@ -320,22 +363,22 @@ NfcCommand felica_poller_state_handler_traverse_standard_system(FelicaPoller* in NfcCommand felica_poller_state_handler_read_standard_blocks(FelicaPoller* instance) { FURI_LOG_D(TAG, "Read Standard Blocks"); - const uint32_t service_count = simple_array_get_count(instance->data->services); + FelicaSystem* system = simple_array_get(instance->data->systems, instance->systems_read); + const uint32_t service_count = simple_array_get_count(system->services); felica_public_block_array_t public_block_buffer; felica_public_block_array_init(public_block_buffer); - instance->state = FelicaPollerStateReadSuccess; bool have_read_anything = false; + FelicaError error = FelicaErrorNone; for(uint32_t i = 0; i < service_count; i++) { - const FelicaService* service = simple_array_get(instance->data->services, i); + const FelicaService* service = simple_array_get(system->services, i); if((service->attr & FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ) == 0) continue; uint8_t block_count = 1; uint8_t block_list[1] = {0}; - FelicaError error = FelicaErrorNone; FelicaPollerReadCommandResponse* response; do { error = felica_poller_read_blocks( @@ -369,11 +412,20 @@ NfcCommand felica_poller_state_handler_read_standard_blocks(FelicaPoller* instan } } + if(error == FelicaErrorNone) { + instance->systems_read++; + if(instance->systems_read == instance->systems_total) { + instance->state = FelicaPollerStateReadSuccess; + } else { + instance->state = FelicaPollerStateSelectSystemIndex; + } + } + if(have_read_anything) { const size_t n = felica_public_block_array_size(public_block_buffer); - simple_array_init(instance->data->public_blocks, (uint32_t)n); + simple_array_init(system->public_blocks, (uint32_t)n); memcpy( - simple_array_get(instance->data->public_blocks, 0), + simple_array_get(system->public_blocks, 0), public_block_buffer->ptr, n * sizeof(FelicaPublicBlock)); } @@ -446,6 +498,8 @@ NfcCommand felica_poller_state_handler_read_success(FelicaPoller* instance) { instance->felica_event.type = FelicaPollerEventTypeReady; } + instance->data->idm.data[0] &= 0x0F; + instance->felica_event_data.error = FelicaErrorNone; return instance->callback(instance->general_event, instance->context); } @@ -453,6 +507,7 @@ NfcCommand felica_poller_state_handler_read_success(FelicaPoller* instance) { NfcCommand felica_poller_state_handler_read_failed(FelicaPoller* instance) { FURI_LOG_D(TAG, "Read Fail"); instance->callback(instance->general_event, instance->context); + instance->data->idm.data[0] &= 0x0F; return NfcCommandStop; } @@ -460,6 +515,8 @@ NfcCommand felica_poller_state_handler_read_failed(FelicaPoller* instance) { static const FelicaPollerReadHandler felica_poller_handler[FelicaPollerStateNum] = { [FelicaPollerStateIdle] = felica_poller_state_handler_idle, [FelicaPollerStateActivated] = felica_poller_state_handler_activate, + [FelicaPollerStateListSystem] = felica_poller_state_handler_list_system, + [FelicaPollerStateSelectSystemIndex] = felica_poller_state_handler_select_system_idx, [FelicaPollerStateAuthenticateInternal] = felica_poller_state_handler_auth_internal, [FelicaPollerStateAuthenticateExternal] = felica_poller_state_handler_auth_external, [FelicaPollerStateTraverseStandardSystem] = diff --git a/lib/nfc/protocols/felica/felica_poller_i.c b/lib/nfc/protocols/felica/felica_poller_i.c index 4ab9a8e4c..bbbb824d3 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.c +++ b/lib/nfc/protocols/felica/felica_poller_i.c @@ -233,7 +233,9 @@ static void felica_poller_prepare_tx_buffer_raw( cmd.length = sizeof(FelicaCommandHeaderRaw) + data_length; bit_buffer_reset(instance->tx_buffer); bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)&cmd, sizeof(FelicaCommandHeaderRaw)); - bit_buffer_append_bytes(instance->tx_buffer, data, data_length); + if(data_length > 0) { + bit_buffer_append_bytes(instance->tx_buffer, data, data_length); + } } FelicaError felica_poller_list_service_by_cursor( @@ -264,3 +266,31 @@ FelicaError felica_poller_list_service_by_cursor( *response_ptr = (FelicaListServiceCommandResponse*)bit_buffer_get_data(instance->rx_buffer); return error; } + +FelicaError felica_poller_list_system_code( + FelicaPoller* instance, + FelicaListSystemCodeCommandResponse** const response_ptr) { + furi_assert(instance); + furi_assert(response_ptr); + + uint8_t data[] = {0}; + + felica_poller_prepare_tx_buffer_raw(instance, FELICA_CMD_REQUEST_SYSTEM_CODE, data, 0); + + bit_buffer_reset(instance->rx_buffer); + + FelicaError error = felica_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, FELICA_POLLER_POLLING_FWT); + if(error != FelicaErrorNone) { + FURI_LOG_E(TAG, "Request system code failed with error: %d", error); + return error; + } + + size_t rx_len = bit_buffer_get_size_bytes(instance->rx_buffer); + if(rx_len < sizeof(FelicaCommandHeaderRaw) + 3) return FelicaErrorProtocol; + // at least 1 system code + the count being 0x01 + + // error is known to be FelicaErrorNone here + *response_ptr = (FelicaListSystemCodeCommandResponse*)bit_buffer_get_data(instance->rx_buffer); + return error; +} diff --git a/lib/nfc/protocols/felica/felica_poller_i.h b/lib/nfc/protocols/felica/felica_poller_i.h index 98948db86..446f34c43 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.h +++ b/lib/nfc/protocols/felica/felica_poller_i.h @@ -17,6 +17,8 @@ extern "C" { typedef enum { FelicaPollerStateIdle, FelicaPollerStateActivated, + FelicaPollerStateListSystem, + FelicaPollerStateSelectSystemIndex, FelicaPollerStateAuthenticateInternal, FelicaPollerStateAuthenticateExternal, FelicaPollerStateTraverseStandardSystem, @@ -42,6 +44,8 @@ struct FelicaPoller { FelicaPollerEventData felica_event_data; NfcGenericCallback callback; uint8_t block_index; + uint8_t systems_read; + uint8_t systems_total; void* context; }; @@ -116,6 +120,10 @@ FelicaError felica_poller_list_service_by_cursor( uint16_t cursor, FelicaListServiceCommandResponse** response_ptr); +FelicaError felica_poller_list_system_code( + FelicaPoller* instance, + FelicaListSystemCodeCommandResponse** response_ptr); + #ifdef __cplusplus } #endif diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 9963b3515..676340085 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1087,7 +1087,7 @@ Function,+,felica_save,_Bool,"const FelicaData*, FlipperFormat*" Function,+,felica_service_get_attribute_string,void,"const FelicaService*, FuriString*" Function,+,felica_set_uid,_Bool,"FelicaData*, const uint8_t*, size_t" Function,+,felica_verify,_Bool,"FelicaData*, const FuriString*" -Function,+,felica_write_directory_tree,void,"const FelicaData*, FuriString*" +Function,+,felica_write_directory_tree,void,"const FelicaSystem*, FuriString*" Function,-,feof,int,FILE* Function,-,feof_unlocked,int,FILE* Function,-,ferror,int,FILE* From 6d93a90a0a94a14674bda691e850c7d1f3f9e769 Mon Sep 17 00:00:00 2001 From: Zinong Li <131403964+zinongli@users.noreply.github.com> Date: Thu, 6 Nov 2025 22:37:33 +0400 Subject: [PATCH 159/192] NFC lib: Expose `nfc_common.h` (#4283) * expose nfc_common.h * Update api_symbols.csv --------- Co-authored-by: hedger --- lib/nfc/SConscript | 1 + targets/f7/api_symbols.csv | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index 585e98197..64a5dd466 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -14,6 +14,7 @@ env.Append( File("nfc_listener.h"), File("nfc_poller.h"), File("nfc_scanner.h"), + File("nfc_common.h"), # Protocols File("protocols/iso14443_3a/iso14443_3a.h"), File("protocols/iso14443_3b/iso14443_3b.h"), diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 676340085..2bb63b88e 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -138,6 +138,7 @@ Header,+,lib/nfc/helpers/iso14443_crc.h,, Header,+,lib/nfc/helpers/nfc_data_generator.h,, Header,+,lib/nfc/helpers/nfc_util.h,, Header,+,lib/nfc/nfc.h,, +Header,+,lib/nfc/nfc_common.h,, Header,+,lib/nfc/nfc_device.h,, Header,+,lib/nfc/nfc_listener.h,, Header,+,lib/nfc/nfc_poller.h,, From a42e30f394b809ad3df10586fc3148193f37cc41 Mon Sep 17 00:00:00 2001 From: Zinong Li <131403964+zinongli@users.noreply.github.com> Date: Thu, 6 Nov 2025 22:45:52 +0400 Subject: [PATCH 160/192] NFC FeliCa Minor Fix: `FelicaPollerEventType` should only be `Incomplete` if the tag is FeliCa Lite (#4279) * poller read is only incomp if in Lite workflow * another small fix for the "255/28 pages read" bug --------- Co-authored-by: hedger --- lib/nfc/protocols/felica/felica_poller.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/nfc/protocols/felica/felica_poller.c b/lib/nfc/protocols/felica/felica_poller.c index 98e07df42..056ed4320 100644 --- a/lib/nfc/protocols/felica/felica_poller.c +++ b/lib/nfc/protocols/felica/felica_poller.c @@ -486,9 +486,12 @@ NfcCommand felica_poller_state_handler_read_lite_blocks(FelicaPoller* instance) NfcCommand felica_poller_state_handler_read_success(FelicaPoller* instance) { FURI_LOG_D(TAG, "Read Success"); - if(!instance->auth.context.auth_status.internal || - !instance->auth.context.auth_status.external) { - instance->data->blocks_read--; + if((!instance->auth.context.auth_status.internal || + !instance->auth.context.auth_status.external) && + instance->data->workflow_type == FelicaLite) { + if(instance->data->blocks_read != 0) { + instance->data->blocks_read--; + } instance->felica_event.type = FelicaPollerEventTypeIncomplete; } else { memcpy( From c873eb5e3692c18369e732390c280288e2eae3cc Mon Sep 17 00:00:00 2001 From: Nathan N Date: Thu, 6 Nov 2025 14:05:16 -0500 Subject: [PATCH 161/192] Fix Ultralight EV1 regression (#4287) * Fix EV1 regression introduced in e7634d7 * ./fbt format --------- Co-authored-by: hedger Co-authored-by: hedger --- .../mf_ultralight/mf_ultralight.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index 5ed564897..3afca397b 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -152,13 +152,16 @@ static NfcCommand if(!mf_ultralight_event->data->auth_context.skip_auth) { mf_ultralight_event->data->auth_context.password = instance->mf_ul_auth->password; - // Only set tdes_key for Manual/Reader auth types, not for dictionary attacks - if(instance->mf_ul_auth->type == MfUltralightAuthTypeManual || - instance->mf_ul_auth->type == MfUltralightAuthTypeReader) { - mf_ultralight_event->data->key_request_data.key = instance->mf_ul_auth->tdes_key; - mf_ultralight_event->data->key_request_data.key_provided = true; - } else { - mf_ultralight_event->data->key_request_data.key_provided = false; + if(data->type == MfUltralightTypeMfulC) { + // Only set tdes_key for Manual/Reader auth types, not for dictionary attacks + if(instance->mf_ul_auth->type == MfUltralightAuthTypeManual || + instance->mf_ul_auth->type == MfUltralightAuthTypeReader) { + mf_ultralight_event->data->key_request_data.key = + instance->mf_ul_auth->tdes_key; + mf_ultralight_event->data->key_request_data.key_provided = true; + } else { + mf_ultralight_event->data->key_request_data.key_provided = false; + } } } } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthSuccess) { From f4c92dcd76d9c29e184d245cb493d0aa86809c40 Mon Sep 17 00:00:00 2001 From: Nathan N Date: Thu, 6 Nov 2025 14:34:21 -0500 Subject: [PATCH 162/192] Fix MIFARE Plus SL1 sector overrun issue in state machine (#4288) Co-authored-by: hedger --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index ec37c8015..b2d9b114a 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1921,7 +1921,8 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance sizeof(MfClassicKey)) : NULL; } - if((is_weak || is_last_iter_for_hard_key) && dict_attack_ctx->nested_nonce.count > 0) { + if((is_weak && (dict_attack_ctx->nested_nonce.count == 1)) || + (is_last_iter_for_hard_key && (dict_attack_ctx->nested_nonce.count == 8))) { // Key verify and reuse dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttackVerify; dict_attack_ctx->auth_passed = false; From 7518dc73c285faa3528095874bfefde3c1386cfd Mon Sep 17 00:00:00 2001 From: Mykhailo Shevchuk Date: Thu, 6 Nov 2025 21:54:50 +0200 Subject: [PATCH 163/192] Returning fix for reading PWD locked MFUL (#4295) Co-authored-by: hedger --- .../protocols/mf_ultralight/mf_ultralight_poller.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index 5f872952e..80518b1a8 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -566,16 +566,12 @@ static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* in instance->error = mf_ultralight_poller_read_page(instance, start_page, &data); } - // Regression review - const uint8_t read_cnt = instance->data->type == MfUltralightTypeMfulC ? 1 : 4; if(instance->error == MfUltralightErrorNone) { - for(size_t i = 0; i < read_cnt; i++) { - if(start_page + i < instance->pages_total) { - FURI_LOG_D(TAG, "Read page %d success", start_page + i); - instance->data->page[start_page + i] = data.page[i]; - instance->pages_read++; - instance->data->pages_read = instance->pages_read; - } + if(start_page < instance->pages_total) { + FURI_LOG_D(TAG, "Read page %d success", start_page); + instance->data->page[start_page] = data.page[0]; + instance->pages_read++; + instance->data->pages_read = instance->pages_read; } if(instance->pages_read == instance->pages_total) { From a1c48c82f8ae4a1acab48cf75b35da2cee176e1d Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Sat, 8 Nov 2025 01:30:07 +0700 Subject: [PATCH 164/192] subghz signal settings gui (read from file and parse) --- .../scenes/subghz_scene_signal_settings.c | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_signal_settings.c b/applications/main/subghz/scenes/subghz_scene_signal_settings.c index 3c3e83f6f..82e0ce6b2 100644 --- a/applications/main/subghz/scenes/subghz_scene_signal_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_signal_settings.c @@ -4,6 +4,11 @@ #include #include +#define TAG "SubGhzSceneSignalSettings" + +char * protocol_counter_allowed = ""; +uint8_t counter_mode = 0xff; + #define COUNTER_MODE_COUNT 7 const char* const counter_mode_text[COUNTER_MODE_COUNT] = { "System", @@ -15,8 +20,6 @@ const char* const counter_mode_text[COUNTER_MODE_COUNT] = { "Mode 6", }; -int8_t counter_mode = 0; - const int32_t counter_mode_value[COUNTER_MODE_COUNT] = { 0, 1, @@ -37,8 +40,42 @@ void subghz_scene_signal_settings_counter_mode_changed (VariableItem* item){ } void subghz_scene_signal_settings_on_enter(void* context) { + // when we open saved file we do some check and fill up subghz->filepath. So now we use it to check is CounterMode in file SubGhz* subghz = context; + FuriString* tmp_string = furi_string_alloc(); + const char* file_path = furi_string_get_cstr(subghz->file_path); + + furi_assert(subghz); + furi_assert(file_path); + + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); + uint32_t tmp_counter_mode = 0; + counter_mode = 0xff; + + if(!flipper_format_file_open_existing(fff_data_file, file_path)) { + FURI_LOG_E(TAG, "Error open file %s", file_path); + } else { + flipper_format_read_string(fff_data_file, "Protocol", tmp_string); + if((!strcmp(furi_string_get_cstr(tmp_string), "Nice FloR-S")) || + (!strcmp(furi_string_get_cstr(tmp_string), "CAME Atomo")) || + (!strcmp(furi_string_get_cstr(tmp_string), "Alutech AT-4N")) || + (!strcmp(furi_string_get_cstr(tmp_string), "KeeLoq")) ) { + + if(flipper_format_read_uint32(fff_data_file, "CounterMode", &tmp_counter_mode, 1)) { + counter_mode = (uint8_t)tmp_counter_mode; + } else { + counter_mode = 0; + } + } + } + FURI_LOG_I(TAG, "CounterMode %i", counter_mode); + + furi_string_free(tmp_string); + flipper_format_free(fff_data_file); + furi_record_close(RECORD_STORAGE); + VariableItemList* variable_item_list = subghz->variable_item_list; int32_t value_index; VariableItem* item; @@ -57,6 +94,7 @@ void subghz_scene_signal_settings_on_enter(void* context) { variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, counter_mode_text[value_index]); + variable_item_set_locked(item,(counter_mode == 0xff)," Not available \n for this \nprotocol !"); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList); } From b7d2ab7a0c5f13f138cb88040c6b653bec48fbfc Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Sat, 8 Nov 2025 04:51:23 +0700 Subject: [PATCH 165/192] Subghz special signail_settings interface --- .../scenes/subghz_scene_signal_settings.c | 81 +++++++++++++------ 1 file changed, 57 insertions(+), 24 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_signal_settings.c b/applications/main/subghz/scenes/subghz_scene_signal_settings.c index 82e0ce6b2..4433aa2b5 100644 --- a/applications/main/subghz/scenes/subghz_scene_signal_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_signal_settings.c @@ -6,8 +6,7 @@ #define TAG "SubGhzSceneSignalSettings" -char * protocol_counter_allowed = ""; -uint8_t counter_mode = 0xff; +uint32_t counter_mode = 0xff; #define COUNTER_MODE_COUNT 7 const char* const counter_mode_text[COUNTER_MODE_COUNT] = { @@ -30,20 +29,18 @@ const int32_t counter_mode_value[COUNTER_MODE_COUNT] = { 6, }; -void subghz_scene_signal_settings_counter_mode_changed (VariableItem* item){ +void subghz_scene_signal_settings_counter_mode_changed(VariableItem* item) { SubGhz* subghz = variable_item_get_context(item); - UNUSED (subghz); + UNUSED(subghz); 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_on_enter(void* context) { - // when we open saved file we do some check and fill up subghz->filepath. So now we use it to check is CounterMode in file + // 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; - FuriString* tmp_string = furi_string_alloc(); const char* file_path = furi_string_get_cstr(subghz->file_path); furi_assert(subghz); @@ -51,9 +48,14 @@ void subghz_scene_signal_settings_on_enter(void* context) { Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); + FuriString* tmp_string = furi_string_alloc(); + uint32_t tmp_counter_mode = 0; counter_mode = 0xff; + // open file and check is it contains allowed protocols and CounterMode variable - if not then CcounterMode will stay 0xff + // if file contain allowed protocols but not contain CounterMode - set it to default 0 + // if file contain CounterMode then load it if(!flipper_format_file_open_existing(fff_data_file, file_path)) { FURI_LOG_E(TAG, "Error open file %s", file_path); } else { @@ -61,21 +63,22 @@ void subghz_scene_signal_settings_on_enter(void* context) { if((!strcmp(furi_string_get_cstr(tmp_string), "Nice FloR-S")) || (!strcmp(furi_string_get_cstr(tmp_string), "CAME Atomo")) || (!strcmp(furi_string_get_cstr(tmp_string), "Alutech AT-4N")) || - (!strcmp(furi_string_get_cstr(tmp_string), "KeeLoq")) ) { - - if(flipper_format_read_uint32(fff_data_file, "CounterMode", &tmp_counter_mode, 1)) { - counter_mode = (uint8_t)tmp_counter_mode; - } else { - counter_mode = 0; - } + (!strcmp(furi_string_get_cstr(tmp_string), "KeeLoq"))) { + if(flipper_format_read_uint32(fff_data_file, "CounterMode", &tmp_counter_mode, 1)) { + counter_mode = (uint8_t)tmp_counter_mode; + } else { + counter_mode = 0; } + } } - FURI_LOG_I(TAG, "CounterMode %i", counter_mode); + FURI_LOG_I(TAG, "Loaded CounterMode value %li", counter_mode); furi_string_free(tmp_string); + flipper_format_file_close(fff_data_file); flipper_format_free(fff_data_file); furi_record_close(RECORD_STORAGE); + //Create and Enable/Disable variable_item_list depent from current CounterMode value VariableItemList* variable_item_list = subghz->variable_item_list; int32_t value_index; VariableItem* item; @@ -87,14 +90,12 @@ void subghz_scene_signal_settings_on_enter(void* context) { available_count, subghz_scene_signal_settings_counter_mode_changed, subghz); - value_index = value_index_int32( - counter_mode, - counter_mode_value, - available_count); + value_index = value_index_int32(counter_mode, counter_mode_value, available_count); variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, counter_mode_text[value_index]); - variable_item_set_locked(item,(counter_mode == 0xff)," Not available \n for this \nprotocol !"); + variable_item_set_locked( + item, (counter_mode == 0xff), " Not available \n for this \nprotocol !"); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList); } @@ -104,13 +105,45 @@ bool subghz_scene_signal_settings_on_event(void* context, SceneManagerEvent even if(event.type == SceneManagerEventTypeBack) { scene_manager_previous_scene(subghz->scene_manager); return true; - } - else return false; - } - + } else + return false; +} void subghz_scene_signal_settings_on_exit(void* context) { SubGhz* subghz = context; + const char* file_path = furi_string_get_cstr(subghz->file_path); + + furi_assert(subghz); + furi_assert(file_path); + + // if ConterMode was changed from 0xff then we must update or write new value to file + if(counter_mode != 0xff) { + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); + + // check is the file available for update or write brand new value and to it + if(flipper_format_file_open_existing(fff_data_file, file_path)) { + if(flipper_format_update_uint32(fff_data_file, "CounterMode", &counter_mode, 1)) { + FURI_LOG_I(TAG, "Successfully update CounterMode value to %li", counter_mode); + } else { + FURI_LOG_E(TAG, "Error update CounterMode value trying append and add .. "); + flipper_format_file_close(fff_data_file); + flipper_format_file_open_append(fff_data_file, file_path); + if(flipper_format_write_uint32(fff_data_file, "CounterMode", &counter_mode, 1)) { + FURI_LOG_I(TAG, "Successfully added CounterMode value %li", counter_mode); + } else { + FURI_LOG_E(TAG, "Error with adding CounterMode value %li", counter_mode); + } + } + } else { + FURI_LOG_E(TAG, "Error open file %s for writing", file_path); + } + + flipper_format_file_close(fff_data_file); + flipper_format_free(fff_data_file); + furi_record_close(RECORD_STORAGE); + } + variable_item_list_set_selected_item(subghz->variable_item_list, 0); variable_item_list_reset(subghz->variable_item_list); } From 9d1cee6d4c8740881927cf833bdca895ab185063 Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Sat, 8 Nov 2025 04:52:36 +0700 Subject: [PATCH 166/192] Subghz special signail_settings interface end of development --- .../scenes/subghz_scene_signal_settings.c | 49 +++++++++++++------ 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_signal_settings.c b/applications/main/subghz/scenes/subghz_scene_signal_settings.c index 4433aa2b5..33ce18783 100644 --- a/applications/main/subghz/scenes/subghz_scene_signal_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_signal_settings.c @@ -29,7 +29,22 @@ const int32_t counter_mode_value[COUNTER_MODE_COUNT] = { 6, }; -void subghz_scene_signal_settings_counter_mode_changed(VariableItem* item) { +typedef struct { + char* name; + uint8_t mode_count; +} Protocols; + +// List of protocols names and appropriate CounterMode counts +static Protocols protocols[] = { + {"Nice FloR-S", 3}, + {"CAME Atomo", 4}, + {"Alutech AT-4N", 3}, + {"KeeLoq", 7}, +}; + +#define PROTOCOLS_COUNT (sizeof(protocols) / sizeof(Protocols)); + +void subghz_scene_signal_settings_counter_mode_changed(VariableItem * item) { SubGhz* subghz = variable_item_get_context(item); UNUSED(subghz); uint8_t index = variable_item_get_current_value_index(item); @@ -52,26 +67,28 @@ void subghz_scene_signal_settings_on_enter(void* context) { uint32_t tmp_counter_mode = 0; counter_mode = 0xff; + uint8_t mode_count=1; - // open file and check is it contains allowed protocols and CounterMode variable - if not then CcounterMode will stay 0xff - // if file contain allowed protocols but not contain CounterMode - set it to default 0 - // if file contain CounterMode then load it + // open file and check is it contains allowed protocols and CounterMode variable - if not then CcounterMode will stay 0xff + // if file contain allowed protocol but not contain CounterMode value - set default CounterMode value = 0 and available CounterMode count for this protocol + // if file contain CounterMode value then load it if(!flipper_format_file_open_existing(fff_data_file, file_path)) { FURI_LOG_E(TAG, "Error open file %s", file_path); } else { flipper_format_read_string(fff_data_file, "Protocol", tmp_string); - if((!strcmp(furi_string_get_cstr(tmp_string), "Nice FloR-S")) || - (!strcmp(furi_string_get_cstr(tmp_string), "CAME Atomo")) || - (!strcmp(furi_string_get_cstr(tmp_string), "Alutech AT-4N")) || - (!strcmp(furi_string_get_cstr(tmp_string), "KeeLoq"))) { - if(flipper_format_read_uint32(fff_data_file, "CounterMode", &tmp_counter_mode, 1)) { - counter_mode = (uint8_t)tmp_counter_mode; - } else { - counter_mode = 0; + // compare available protocols names, load CounterMode value from file and setup variable_item_list values_count + for(uint8_t i = 0; i < PROTOCOLS_COUNT i++) { + if(!strcmp(furi_string_get_cstr(tmp_string), protocols[i].name)) { + mode_count = protocols[i].mode_count; + if(flipper_format_read_uint32(fff_data_file, "CounterMode", &tmp_counter_mode, 1)) { + counter_mode = (uint8_t)tmp_counter_mode; + } else { + counter_mode = 0; + } } } } - FURI_LOG_I(TAG, "Loaded CounterMode value %li", counter_mode); + FURI_LOG_I(TAG, "Current CounterMode value %li", counter_mode); furi_string_free(tmp_string); flipper_format_file_close(fff_data_file); @@ -82,15 +99,15 @@ void subghz_scene_signal_settings_on_enter(void* context) { VariableItemList* variable_item_list = subghz->variable_item_list; int32_t value_index; VariableItem* item; - int32_t available_count = COUNTER_MODE_COUNT; + //int32_t available_count = COUNTER_MODE_COUNT; item = variable_item_list_add( variable_item_list, "Counter Mode", - available_count, + mode_count, subghz_scene_signal_settings_counter_mode_changed, subghz); - value_index = value_index_int32(counter_mode, counter_mode_value, available_count); + value_index = value_index_int32(counter_mode, counter_mode_value, mode_count); variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, counter_mode_text[value_index]); From 48b9dd2cc82b4c76861ab4c1bb92e49247f16263 Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Sun, 9 Nov 2025 21:01:19 +0700 Subject: [PATCH 167/192] Hide additional settings under gebug mode (commented, not active) --- .../subghz/scenes/subghz_scene_saved_menu.c | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_saved_menu.c b/applications/main/subghz/scenes/subghz_scene_saved_menu.c index 011e719a7..5b6405b53 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); - - submenu_add_item( - subghz->submenu, - "Signal Settings", - SubmenuIndexSignalSettings, - subghz_scene_saved_menu_submenu_callback, - subghz); - + //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)); @@ -70,10 +70,10 @@ bool subghz_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { return true; } else if(event.event == SubmenuIndexSignalSettings) { scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexSignalSettings); + subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexSignalSettings); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSignalSettings); - return true; - } + return true; + } } return false; } From 7ee266752e6c3e2a22554638a3ed19ac82c11829 Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Sun, 9 Nov 2025 22:01:36 +0700 Subject: [PATCH 168/192] Subghz signal settings counter edit in progress..... --- .../scenes/subghz_scene_signal_settings.c | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/applications/main/subghz/scenes/subghz_scene_signal_settings.c b/applications/main/subghz/scenes/subghz_scene_signal_settings.c index 33ce18783..2c77a1776 100644 --- a/applications/main/subghz/scenes/subghz_scene_signal_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_signal_settings.c @@ -52,6 +52,15 @@ 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) { + UNUSED(context); + SubGhz* subghz = context; + + if(index == 1) { + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdByteInput); + } +} + 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; @@ -99,8 +108,9 @@ void subghz_scene_signal_settings_on_enter(void* context) { VariableItemList* variable_item_list = subghz->variable_item_list; int32_t value_index; VariableItem* item; - //int32_t available_count = COUNTER_MODE_COUNT; + 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", @@ -114,6 +124,14 @@ void subghz_scene_signal_settings_on_enter(void* context) { variable_item_set_locked( item, (counter_mode == 0xff), " Not available \n for this \nprotocol !"); + item = variable_item_list_add( + variable_item_list, + "Counter Value", + 1, + NULL, + subghz); + variable_item_set_current_value_text(item, "FFFFFF"); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList); } From 01cc4cc3da53d6b339c09e826a881dd23ffaa8c3 Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Mon, 10 Nov 2025 23:13:28 +0700 Subject: [PATCH 169/192] finita la comedia --- .../main/subghz/scenes/subghz_scene_saved_menu.c | 4 ++-- .../subghz/scenes/subghz_scene_signal_settings.c | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_saved_menu.c b/applications/main/subghz/scenes/subghz_scene_saved_menu.c index 5b6405b53..0e753dcaf 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 2c77a1776..865b3507e 100644 --- a/applications/main/subghz/scenes/subghz_scene_signal_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_signal_settings.c @@ -124,13 +124,13 @@ void subghz_scene_signal_settings_on_enter(void* context) { variable_item_set_locked( item, (counter_mode == 0xff), " Not available \n for this \nprotocol !"); - item = variable_item_list_add( - variable_item_list, - "Counter Value", - 1, - NULL, - subghz); - variable_item_set_current_value_text(item, "FFFFFF"); + // item = variable_item_list_add( + // variable_item_list, + // "Counter Value", + // 1, + // NULL, + // subghz); + // variable_item_set_current_value_text(item, "FFFFFF"); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList); } From d1f8ddc033ad7f1935b024fa6218fb67006af8e9 Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Mon, 10 Nov 2025 23:23:34 +0700 Subject: [PATCH 170/192] i dont know WTF --- targets/f7/api_symbols.csv | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 0b6f7fae0..71c0106ea 100755 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -63,9 +63,6 @@ Header,+,lib/ibutton/ibutton_protocols.h,, Header,+,lib/ibutton/ibutton_worker.h,, Header,+,lib/ieee754_parse_wrap/wrappers.h,, Header,+,lib/infrared/encoder_decoder/infrared.h,, -Header,+,lib/infrared/signal/infrared_brute_force.h,, -Header,+,lib/infrared/signal/infrared_error_code.h,, -Header,+,lib/infrared/signal/infrared_signal.h,, Header,+,lib/infrared/worker/infrared_transmit.h,, Header,+,lib/infrared/worker/infrared_worker.h,, Header,+,lib/lfrfid/lfrfid_dict_file.h,, @@ -2119,16 +2116,6 @@ Function,-,infinity,double, Function,-,infinityf,float, Function,+,infrared_alloc_decoder,InfraredDecoderHandler*, Function,+,infrared_alloc_encoder,InfraredEncoderHandler*, -Function,-,infrared_brute_force_add_record,void,"InfraredBruteForce*, uint32_t, const char*" -Function,-,infrared_brute_force_alloc,InfraredBruteForce*, -Function,-,infrared_brute_force_calculate_messages,InfraredErrorCode,InfraredBruteForce* -Function,-,infrared_brute_force_free,void,InfraredBruteForce* -Function,-,infrared_brute_force_is_started,_Bool,const InfraredBruteForce* -Function,-,infrared_brute_force_reset,void,InfraredBruteForce* -Function,-,infrared_brute_force_send,_Bool,"InfraredBruteForce*, uint32_t" -Function,-,infrared_brute_force_set_db_filename,void,"InfraredBruteForce*, const char*" -Function,-,infrared_brute_force_start,_Bool,"InfraredBruteForce*, uint32_t, uint32_t*" -Function,-,infrared_brute_force_stop,void,InfraredBruteForce* Function,+,infrared_check_decoder_ready,const InfraredMessage*,InfraredDecoderHandler* Function,+,infrared_decode,const InfraredMessage*,"InfraredDecoderHandler*, _Bool, uint32_t" Function,+,infrared_encode,InfraredStatus,"InfraredEncoderHandler*, uint32_t*, _Bool*" @@ -2147,22 +2134,6 @@ Function,+,infrared_reset_encoder,void,"InfraredEncoderHandler*, const InfraredM Function,+,infrared_send,void,"const InfraredMessage*, int" Function,+,infrared_send_raw,void,"const uint32_t[], uint32_t, _Bool" Function,+,infrared_send_raw_ext,void,"const uint32_t[], uint32_t, _Bool, uint32_t, float" -Function,-,infrared_signal_alloc,InfraredSignal*, -Function,-,infrared_signal_free,void,InfraredSignal* -Function,-,infrared_signal_get_message,const InfraredMessage*,const InfraredSignal* -Function,-,infrared_signal_get_raw_signal,const InfraredRawSignal*,const InfraredSignal* -Function,-,infrared_signal_is_raw,_Bool,const InfraredSignal* -Function,-,infrared_signal_is_valid,_Bool,const InfraredSignal* -Function,-,infrared_signal_read,InfraredErrorCode,"InfraredSignal*, FlipperFormat*, FuriString*" -Function,-,infrared_signal_read_body,InfraredErrorCode,"InfraredSignal*, FlipperFormat*" -Function,-,infrared_signal_read_name,InfraredErrorCode,"FlipperFormat*, FuriString*" -Function,-,infrared_signal_save,InfraredErrorCode,"const InfraredSignal*, FlipperFormat*, const char*" -Function,-,infrared_signal_search_by_index_and_read,InfraredErrorCode,"InfraredSignal*, FlipperFormat*, size_t" -Function,-,infrared_signal_search_by_name_and_read,InfraredErrorCode,"InfraredSignal*, FlipperFormat*, const char*" -Function,-,infrared_signal_set_message,void,"InfraredSignal*, const InfraredMessage*" -Function,-,infrared_signal_set_raw_signal,void,"InfraredSignal*, const uint32_t*, size_t, uint32_t, float" -Function,-,infrared_signal_set_signal,void,"InfraredSignal*, const InfraredSignal*" -Function,-,infrared_signal_transmit,void,const InfraredSignal* Function,+,infrared_worker_alloc,InfraredWorker*, Function,+,infrared_worker_free,void,InfraredWorker* Function,+,infrared_worker_get_decoded_signal,const InfraredMessage*,const InfraredWorkerSignal* From d320c4a46b3a1c4fbaa5407bbd51cba8683c9438 Mon Sep 17 00:00:00 2001 From: Dmitry422 <118886774+Dmitry422@users.noreply.github.com> Date: Tue, 11 Nov 2025 08:14:08 +0700 Subject: [PATCH 171/192] Recover accidentally deleted --- applications_user/README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 applications_user/README.md diff --git a/applications_user/README.md b/applications_user/README.md new file mode 100644 index 000000000..8bb7823c1 --- /dev/null +++ b/applications_user/README.md @@ -0,0 +1 @@ +Put your custom applications in this folder. \ No newline at end of file From da3a2834d49adecd25f9e6a8b021de82ec3bb48e Mon Sep 17 00:00:00 2001 From: Dmitry422 <118886774+Dmitry422@users.noreply.github.com> Date: Tue, 11 Nov 2025 08:15:02 +0700 Subject: [PATCH 172/192] Recover accidentally deleted --- targets/f7/api_symbols.csv | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 71c0106ea..0b6f7fae0 100755 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -63,6 +63,9 @@ Header,+,lib/ibutton/ibutton_protocols.h,, Header,+,lib/ibutton/ibutton_worker.h,, Header,+,lib/ieee754_parse_wrap/wrappers.h,, Header,+,lib/infrared/encoder_decoder/infrared.h,, +Header,+,lib/infrared/signal/infrared_brute_force.h,, +Header,+,lib/infrared/signal/infrared_error_code.h,, +Header,+,lib/infrared/signal/infrared_signal.h,, Header,+,lib/infrared/worker/infrared_transmit.h,, Header,+,lib/infrared/worker/infrared_worker.h,, Header,+,lib/lfrfid/lfrfid_dict_file.h,, @@ -2116,6 +2119,16 @@ Function,-,infinity,double, Function,-,infinityf,float, Function,+,infrared_alloc_decoder,InfraredDecoderHandler*, Function,+,infrared_alloc_encoder,InfraredEncoderHandler*, +Function,-,infrared_brute_force_add_record,void,"InfraredBruteForce*, uint32_t, const char*" +Function,-,infrared_brute_force_alloc,InfraredBruteForce*, +Function,-,infrared_brute_force_calculate_messages,InfraredErrorCode,InfraredBruteForce* +Function,-,infrared_brute_force_free,void,InfraredBruteForce* +Function,-,infrared_brute_force_is_started,_Bool,const InfraredBruteForce* +Function,-,infrared_brute_force_reset,void,InfraredBruteForce* +Function,-,infrared_brute_force_send,_Bool,"InfraredBruteForce*, uint32_t" +Function,-,infrared_brute_force_set_db_filename,void,"InfraredBruteForce*, const char*" +Function,-,infrared_brute_force_start,_Bool,"InfraredBruteForce*, uint32_t, uint32_t*" +Function,-,infrared_brute_force_stop,void,InfraredBruteForce* Function,+,infrared_check_decoder_ready,const InfraredMessage*,InfraredDecoderHandler* Function,+,infrared_decode,const InfraredMessage*,"InfraredDecoderHandler*, _Bool, uint32_t" Function,+,infrared_encode,InfraredStatus,"InfraredEncoderHandler*, uint32_t*, _Bool*" @@ -2134,6 +2147,22 @@ Function,+,infrared_reset_encoder,void,"InfraredEncoderHandler*, const InfraredM Function,+,infrared_send,void,"const InfraredMessage*, int" Function,+,infrared_send_raw,void,"const uint32_t[], uint32_t, _Bool" Function,+,infrared_send_raw_ext,void,"const uint32_t[], uint32_t, _Bool, uint32_t, float" +Function,-,infrared_signal_alloc,InfraredSignal*, +Function,-,infrared_signal_free,void,InfraredSignal* +Function,-,infrared_signal_get_message,const InfraredMessage*,const InfraredSignal* +Function,-,infrared_signal_get_raw_signal,const InfraredRawSignal*,const InfraredSignal* +Function,-,infrared_signal_is_raw,_Bool,const InfraredSignal* +Function,-,infrared_signal_is_valid,_Bool,const InfraredSignal* +Function,-,infrared_signal_read,InfraredErrorCode,"InfraredSignal*, FlipperFormat*, FuriString*" +Function,-,infrared_signal_read_body,InfraredErrorCode,"InfraredSignal*, FlipperFormat*" +Function,-,infrared_signal_read_name,InfraredErrorCode,"FlipperFormat*, FuriString*" +Function,-,infrared_signal_save,InfraredErrorCode,"const InfraredSignal*, FlipperFormat*, const char*" +Function,-,infrared_signal_search_by_index_and_read,InfraredErrorCode,"InfraredSignal*, FlipperFormat*, size_t" +Function,-,infrared_signal_search_by_name_and_read,InfraredErrorCode,"InfraredSignal*, FlipperFormat*, const char*" +Function,-,infrared_signal_set_message,void,"InfraredSignal*, const InfraredMessage*" +Function,-,infrared_signal_set_raw_signal,void,"InfraredSignal*, const uint32_t*, size_t, uint32_t, float" +Function,-,infrared_signal_set_signal,void,"InfraredSignal*, const InfraredSignal*" +Function,-,infrared_signal_transmit,void,const InfraredSignal* Function,+,infrared_worker_alloc,InfraredWorker*, Function,+,infrared_worker_free,void,InfraredWorker* Function,+,infrared_worker_get_decoded_signal,const InfraredMessage*,const InfraredWorkerSignal* From b391cfc71dab8ccd659f4d301de6c3af2a30e9fb Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Tue, 11 Nov 2025 08:46:32 +0700 Subject: [PATCH 173/192] Rework by review --- .../scenes/subghz_scene_signal_settings.c | 52 ++++++------------- 1 file changed, 15 insertions(+), 37 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_signal_settings.c b/applications/main/subghz/scenes/subghz_scene_signal_settings.c index 865b3507e..0c9cb6ba1 100644 --- a/applications/main/subghz/scenes/subghz_scene_signal_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_signal_settings.c @@ -1,15 +1,14 @@ #include "../subghz_i.h" #include "subghz/types.h" #include "../helpers/subghz_custom_event.h" -#include #include #define TAG "SubGhzSceneSignalSettings" -uint32_t counter_mode = 0xff; +static uint32_t counter_mode = 0xff; #define COUNTER_MODE_COUNT 7 -const char* const counter_mode_text[COUNTER_MODE_COUNT] = { +static const char* const counter_mode_text[COUNTER_MODE_COUNT] = { "System", "Mode 1", "Mode 2", @@ -19,7 +18,7 @@ const char* const counter_mode_text[COUNTER_MODE_COUNT] = { "Mode 6", }; -const int32_t counter_mode_value[COUNTER_MODE_COUNT] = { +static const int32_t counter_mode_value[COUNTER_MODE_COUNT] = { 0, 1, 2, @@ -44,25 +43,15 @@ static Protocols protocols[] = { #define PROTOCOLS_COUNT (sizeof(protocols) / sizeof(Protocols)); -void subghz_scene_signal_settings_counter_mode_changed(VariableItem * item) { - SubGhz* subghz = variable_item_get_context(item); - UNUSED(subghz); +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_variable_item_list_enter_callback(void* context, uint32_t index) { - UNUSED(context); - SubGhz* subghz = context; - - if(index == 1) { - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdByteInput); - } -} - 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 + // 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; const char* file_path = furi_string_get_cstr(subghz->file_path); @@ -73,13 +62,13 @@ void subghz_scene_signal_settings_on_enter(void* context) { Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); FuriString* tmp_string = furi_string_alloc(); - + uint32_t tmp_counter_mode = 0; counter_mode = 0xff; - uint8_t mode_count=1; + uint8_t mode_count = 1; - // open file and check is it contains allowed protocols and CounterMode variable - if not then CcounterMode will stay 0xff - // if file contain allowed protocol but not contain CounterMode value - set default CounterMode value = 0 and available CounterMode count for this protocol + // Open file and check is it contains allowed protocols and CounterMode variable - if not then CcounterMode will stay 0xff + // if file contain allowed protocol but not contain CounterMode value then setup default CounterMode value = 0 and available CounterMode count for this protocol // if file contain CounterMode value then load it if(!flipper_format_file_open_existing(fff_data_file, file_path)) { FURI_LOG_E(TAG, "Error open file %s", file_path); @@ -97,7 +86,7 @@ void subghz_scene_signal_settings_on_enter(void* context) { } } } - FURI_LOG_I(TAG, "Current CounterMode value %li", counter_mode); + FURI_LOG_D(TAG, "Current CounterMode value %li", counter_mode); furi_string_free(tmp_string); flipper_format_file_close(fff_data_file); @@ -109,8 +98,6 @@ 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", @@ -121,16 +108,7 @@ void subghz_scene_signal_settings_on_enter(void* context) { variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, counter_mode_text[value_index]); - variable_item_set_locked( - item, (counter_mode == 0xff), " Not available \n for this \nprotocol !"); - - // item = variable_item_list_add( - // variable_item_list, - // "Counter Value", - // 1, - // NULL, - // subghz); - // variable_item_set_current_value_text(item, "FFFFFF"); + variable_item_set_locked(item, (counter_mode == 0xff), "Not available\nfor this\nprotocol!"); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList); } @@ -156,16 +134,16 @@ void subghz_scene_signal_settings_on_exit(void* context) { Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); - // check is the file available for update or write brand new value and to it + // check is the file available for update or write brand new value to it if(flipper_format_file_open_existing(fff_data_file, file_path)) { if(flipper_format_update_uint32(fff_data_file, "CounterMode", &counter_mode, 1)) { - FURI_LOG_I(TAG, "Successfully update CounterMode value to %li", counter_mode); + FURI_LOG_D(TAG, "Successfully update CounterMode value to %li", counter_mode); } else { FURI_LOG_E(TAG, "Error update CounterMode value trying append and add .. "); flipper_format_file_close(fff_data_file); flipper_format_file_open_append(fff_data_file, file_path); if(flipper_format_write_uint32(fff_data_file, "CounterMode", &counter_mode, 1)) { - FURI_LOG_I(TAG, "Successfully added CounterMode value %li", counter_mode); + FURI_LOG_D(TAG, "Successfully added CounterMode value %li", counter_mode); } else { FURI_LOG_E(TAG, "Error with adding CounterMode value %li", counter_mode); } From 0351818b75ced86cc642771e99c29df9570d9692 Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Tue, 11 Nov 2025 11:52:42 +0700 Subject: [PATCH 174/192] Rework by review finished --- .../scenes/subghz_scene_signal_settings.c | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_signal_settings.c b/applications/main/subghz/scenes/subghz_scene_signal_settings.c index 0c9cb6ba1..6c0b71acf 100644 --- a/applications/main/subghz/scenes/subghz_scene_signal_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_signal_settings.c @@ -108,7 +108,7 @@ void subghz_scene_signal_settings_on_enter(void* context) { variable_item_set_current_value_index(item, value_index); 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!"); + variable_item_set_locked(item, (counter_mode == 0xff), "Not available\nfor this\nprotocol !"); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList); } @@ -134,19 +134,14 @@ void subghz_scene_signal_settings_on_exit(void* context) { Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); - // check is the file available for update or write brand new value to it + // check is the file available for update/insert CounterMode value if(flipper_format_file_open_existing(fff_data_file, file_path)) { - if(flipper_format_update_uint32(fff_data_file, "CounterMode", &counter_mode, 1)) { - FURI_LOG_D(TAG, "Successfully update CounterMode value to %li", counter_mode); + if(flipper_format_insert_or_update_uint32( + fff_data_file, "CounterMode", &counter_mode, 1)) { + FURI_LOG_D( + TAG, "Successfully updated/inserted CounterMode value %li", counter_mode); } else { - FURI_LOG_E(TAG, "Error update CounterMode value trying append and add .. "); - flipper_format_file_close(fff_data_file); - flipper_format_file_open_append(fff_data_file, file_path); - if(flipper_format_write_uint32(fff_data_file, "CounterMode", &counter_mode, 1)) { - FURI_LOG_D(TAG, "Successfully added CounterMode value %li", counter_mode); - } else { - FURI_LOG_E(TAG, "Error with adding CounterMode value %li", counter_mode); - } + FURI_LOG_E(TAG, "Error update/insert CounterMode value"); } } else { FURI_LOG_E(TAG, "Error open file %s for writing", file_path); From b0c1931cafbe9b2ef2e5e6dd940aa7fb79a8a9ab Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 11 Nov 2025 15:46:15 +0300 Subject: [PATCH 175/192] upd changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1180cb352..deb9905cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## Main changes -- Current API: 87.0 +- 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: Add AN-Motors AT4 button on arrow keys (0xC) * SubGHz: Add IL-100 Smart support for Add manually From cd79d4f689857c1cfcef6114f39606c07e180469 Mon Sep 17 00:00:00 2001 From: RebornedBrain Date: Tue, 25 Nov 2025 16:58:10 +0300 Subject: [PATCH 176/192] Fix Felica standard loading from nfc file (#4311) --- lib/nfc/protocols/felica/felica.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/nfc/protocols/felica/felica.c b/lib/nfc/protocols/felica/felica.c index 2e8d5f92a..c292d2cb5 100644 --- a/lib/nfc/protocols/felica/felica.c +++ b/lib/nfc/protocols/felica/felica.c @@ -157,6 +157,8 @@ bool felica_load(FelicaData* data, FlipperFormat* ff, uint32_t version) { case FelicaStandard: uint32_t systems_total = 0; if(!flipper_format_read_uint32(ff, "System found", &systems_total, 1)) break; + if(systems_total == 0) break; + simple_array_init(data->systems, systems_total); for(uint8_t sys_idx = 0; sys_idx < systems_total; sys_idx++) { @@ -181,6 +183,8 @@ bool felica_load(FelicaData* data, FlipperFormat* ff, uint32_t version) { do { uint32_t area_count = 0; if(!flipper_format_read_uint32(ff, "Area found", &area_count, 1)) break; + if(area_count == 0) break; + simple_array_init(system->areas, area_count); furi_string_reset(str_key_buffer); @@ -207,6 +211,8 @@ bool felica_load(FelicaData* data, FlipperFormat* ff, uint32_t version) { do { uint32_t service_count = 0; if(!flipper_format_read_uint32(ff, "Service found", &service_count, 1)) break; + if(service_count == 0) break; + simple_array_init(system->services, service_count); furi_string_reset(str_key_buffer); @@ -237,6 +243,8 @@ bool felica_load(FelicaData* data, FlipperFormat* ff, uint32_t version) { uint32_t public_block_count = 0; if(!flipper_format_read_uint32(ff, "Public blocks read", &public_block_count, 1)) break; + if(public_block_count == 0) break; + simple_array_init(system->public_blocks, public_block_count); for(uint16_t i = 0; i < public_block_count; i++) { furi_string_printf(str_key_buffer, "Block %04X", i); From c12d8bae39c9a8a5553db2c328f27fcc006ce5c3 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sat, 29 Nov 2025 16:45:58 +0100 Subject: [PATCH 177/192] im fucking done --nobuild --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..ddd36f989 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1 @@ +# ANY AND ALL USE OF AI, EVEN SLIGHTLY SUSPECTED, EVEN IF JUST TO ASK QUESTIONS AND GET CONFIRMATION HERE, WILL GET YOU BANNED, YOUR MESSAGES DELETED, AND YOUR ISSUES / PULL REQUESTS CLOSED. YOU ARE A HUMAN, ACT LIKE IT From 33165fdde8702888f8b625ea04f1ef574e70aab5 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sat, 29 Nov 2025 20:07:25 +0100 Subject: [PATCH 178/192] Infrared: Add compatibility polyfill now that infrared_brute_force is in library --nobuild --- applications/main/infrared/infrared_cli.c | 2 +- .../common/infrared_scene_universal_common.c | 3 +-- .../scenes/infrared_scene_universal_from_file.c | 2 +- lib/infrared/signal/infrared_brute_force.c | 6 +++++- lib/infrared/signal/infrared_brute_force.h | 14 +++++++++++++- targets/f7/api_symbols.csv | 3 +++ 6 files changed, 24 insertions(+), 6 deletions(-) diff --git a/applications/main/infrared/infrared_cli.c b/applications/main/infrared/infrared_cli.c index b01185b15..843a8afd3 100644 --- a/applications/main/infrared/infrared_cli.c +++ b/applications/main/infrared/infrared_cli.c @@ -470,7 +470,7 @@ static void infrared_cli_brute_force_signals( printf("Missing signal name.\r\n"); break; } - if(infrared_brute_force_calculate_messages(brute_force, false, true) != + if(infrared_brute_force_calculate_messages_ex(brute_force, false, true) != InfraredErrorCodeNone) { printf("Invalid remote name.\r\n"); break; diff --git a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c index 28f039fec..4e5a965a7 100644 --- a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c +++ b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c @@ -49,8 +49,7 @@ static void infrared_scene_universal_common_hide_popup(InfraredApp* infrared) { static int32_t infrared_scene_universal_common_task_callback(void* context) { InfraredApp* infrared = context; - const InfraredErrorCode error = - infrared_brute_force_calculate_messages(infrared->brute_force, false, false); + const InfraredErrorCode error = infrared_brute_force_calculate_messages(infrared->brute_force); view_dispatcher_send_custom_event( infrared->view_dispatcher, infrared_custom_event_pack(InfraredCustomEventTypeTaskFinished, 0)); diff --git a/applications/main/infrared/scenes/infrared_scene_universal_from_file.c b/applications/main/infrared/scenes/infrared_scene_universal_from_file.c index f1f188464..d2638d59f 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_from_file.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_from_file.c @@ -16,7 +16,7 @@ static int32_t infrared_scene_universal_from_file_task_callback(void* context) { ButtonMenu* button_menu = infrared->button_menu; InfraredBruteForce* brute_force = infrared->brute_force; const InfraredErrorCode error = - infrared_brute_force_calculate_messages(infrared->brute_force, true, false); + infrared_brute_force_calculate_messages_ex(infrared->brute_force, true, false); if(!INFRARED_ERROR_PRESENT(error)) { // add btns diff --git a/lib/infrared/signal/infrared_brute_force.c b/lib/infrared/signal/infrared_brute_force.c index 1beda1977..6f094e5b0 100644 --- a/lib/infrared/signal/infrared_brute_force.c +++ b/lib/infrared/signal/infrared_brute_force.c @@ -93,7 +93,7 @@ void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const brute_force->db_filename = db_filename; } -InfraredErrorCode infrared_brute_force_calculate_messages( +InfraredErrorCode infrared_brute_force_calculate_messages_ex( InfraredBruteForce* brute_force, bool auto_detect_buttons, bool ignore_unknown_buttons) { @@ -175,6 +175,10 @@ InfraredErrorCode infrared_brute_force_calculate_messages( return error; } +InfraredErrorCode infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) { + return infrared_brute_force_calculate_messages_ex(brute_force, false, false); +} + bool infrared_brute_force_start( InfraredBruteForce* brute_force, uint32_t index, diff --git a/lib/infrared/signal/infrared_brute_force.h b/lib/infrared/signal/infrared_brute_force.h index fcf85210c..c1148242e 100644 --- a/lib/infrared/signal/infrared_brute_force.h +++ b/lib/infrared/signal/infrared_brute_force.h @@ -52,13 +52,25 @@ void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const * * @param[in,out] brute_force pointer to the instance to be updated. * @param[in] auto_detect_buttons bool whether to automatically register newly discovered buttons. + * @param[in] ignore_unknown_buttons bool whether to ignore unknown buttons when auto_detect_buttons is false. * @returns InfraredErrorCodeNone on success, otherwise error code. */ -InfraredErrorCode infrared_brute_force_calculate_messages( +InfraredErrorCode infrared_brute_force_calculate_messages_ex( InfraredBruteForce* brute_force, bool auto_detect_buttons, bool ignore_unknown_buttons); +/** + * @brief Build a signal dictionary from a previously set database file. + * + * This function must be called each time after setting the database via + * a infrared_brute_force_set_db_filename() call. + * + * @param[in,out] brute_force pointer to the instance to be updated. + * @returns InfraredErrorCodeNone on success, otherwise error code. + */ +InfraredErrorCode infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force); + /** * @brief Start transmitting signals from a category stored in the dictionary. * diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 342671059..0179b6ad1 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -2154,7 +2154,10 @@ Function,+,infrared_alloc_encoder,InfraredEncoderHandler*, Function,-,infrared_brute_force_add_record,void,"InfraredBruteForce*, uint32_t, const char*" Function,-,infrared_brute_force_alloc,InfraredBruteForce*, Function,-,infrared_brute_force_calculate_messages,InfraredErrorCode,InfraredBruteForce* +Function,-,infrared_brute_force_calculate_messages_ex,InfraredErrorCode,"InfraredBruteForce*, _Bool, _Bool" Function,-,infrared_brute_force_free,void,InfraredBruteForce* +Function,-,infrared_brute_force_get_button_count,size_t,const InfraredBruteForce* +Function,-,infrared_brute_force_get_button_name,const char*,"const InfraredBruteForce*, size_t" Function,-,infrared_brute_force_is_started,_Bool,const InfraredBruteForce* Function,-,infrared_brute_force_reset,void,InfraredBruteForce* Function,-,infrared_brute_force_send,_Bool,"InfraredBruteForce*, uint32_t" From c6de44177e4e6bf24593367e2c5a0ef270d04d6f Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sat, 29 Nov 2025 20:23:47 +0100 Subject: [PATCH 179/192] Infrared: Fix infrared CLI plugin MissingImports --nobuild --- 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 6c43c5dd85d58b57816c593dc2b3f8c0d8473ebc Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sat, 29 Nov 2025 20:54:56 +0100 Subject: [PATCH 180/192] Update apps --nobuild - Seader: Distinguish SIO SE/SR (by bettse) - Seos Compatible: Improve logging (by bettse) --- CHANGELOG.md | 4 ++-- applications/external | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1380676e..1a7ddf007 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -129,9 +129,9 @@ - NMEA GPS: Moved to GPIO/GPS subfolder (by @WillyJL) - Passy: Misc memory management bugfixes, misc UI improvements (by @qistoph) - RFID/iButton Fuzzer: Fix prev navigation for custom UIDs (by @ahnilica) - - Seader: Fix ATS handling (by @NVX), reset SAM on error, support config card, code optimizations, use same commands as Proxmark3 (by @bettse) + - Seader: Fix ATS handling (by @NVX), reset SAM on error, support config card, code optimizations, use same commands as Proxmark3, distinguish SIO SE/SR (by @bettse) - Sentry Safe: New interface, settings & help page (by @H4ckd4ddy) - - Seos Compatible: Add keys v2 support with per-device encryption (by @bettse) + - Seos Compatible: Add keys v2 support with per-device encryption, improve logging (by @bettse) - Sub-GHz Playlist: Fix crash on disallowed frequencies (by @WillyJL) - Weather Station: Added support for solight TE44 (by @fersingb) - Weebo: Prevent 0x88 in UID[3], add more figures to the database (by @bettse) diff --git a/applications/external b/applications/external index d923e694e..99d38fe69 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit d923e694e35b41d70de41f5a1556daecfc7013a3 +Subproject commit 99d38fe69c4c20df5d0046f839cc06f2ec27485a From 0f35f4461299efa7024212f5c400f62803065c16 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sat, 29 Nov 2025 21:16:51 +0100 Subject: [PATCH 181/192] Apps: Add SaFlip (by aaronjamt) --- CHANGELOG.md | 4 +++- applications/external | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a7ddf007..805a20d89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,7 +39,9 @@ - Image Viewer (by @polioan) - Space Playground (by @alanfortlink) - Video Player (by @LTVA1) - - NFC: NFC-Eink (by @RebornedBrain) + - NFC: + - NFC-Eink (by @RebornedBrain) + - SaFlip (by @aaronjamt) - RFID: Simultaneous UHF RFID Reader (by @haffnerriley) - Sub-GHz: - Chief Cooker (by @denr01) diff --git a/applications/external b/applications/external index 99d38fe69..2892ba67c 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit 99d38fe69c4c20df5d0046f839cc06f2ec27485a +Subproject commit 2892ba67c4cca4cc0c8c4eb47f5fbfc87dece9fd From ef5b98c22a7aafc65c8701f57456766133af2ffd Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sat, 29 Nov 2025 21:45:25 +0100 Subject: [PATCH 182/192] Apps: Add IconEdit (by rdefeo) --- CHANGELOG.md | 1 + applications/external | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 805a20d89..5c89aca39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ - Sub-GHz Scheduler (by @shalebridge, fixes by @xMasterX) - Tools: - FlipCrypt (by @Tyl3rA) + - IconEdit (by @rdefeo) - Programmer Calculator (by @armixz) - Resistance Calculator (by @instantiator) - Tasks (by @MadLadSquad) diff --git a/applications/external b/applications/external index 2892ba67c..c7a0ed190 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit 2892ba67c4cca4cc0c8c4eb47f5fbfc87dece9fd +Subproject commit c7a0ed1904857a9c0b2e6c5579bb3e0821cbd7af From aee16c1855795638fc507a6b69c2524aaf3b23ff Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sat, 29 Nov 2025 21:55:03 +0100 Subject: [PATCH 183/192] Update changelog --nobuild --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c89aca39..d21f9147a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,6 +86,8 @@ - UL: Add support for Came Atomo TOP44RBN remotes (by @xMasterX & @mishamyte) - UL: Add IL-100 Smart support for Add manually (by @xMasterX) - UL: 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! (by @xMasterX) + - UL: Counter modes settings per-file for Keeloq, CAME Atomo, Nice Flor S, AlutechAT4N (by @xMasterX & @Dmitry422) + - UL: Add AN-Motors AT4 button on arrow keys (0xC) (by @xMasterX) - RFID: - Support writing Securakey, Jablotron and FDX-B to EM4305 cards (#434 by @jamisonderek) - OFW: Show ISO-3166 Country Names For Pet Chips (by @zinongli) @@ -147,13 +149,16 @@ - Infrared: - OFW: Add an old JVC model to universal remotes (by @zgracem) - OFW: Add Daikin FTXN25LV1B9 and Toyotomi KTN22-12R32 to universal remotes (by @minchogaydarov) + - OFW: Infrared Universal remote DBs unit test & move infrared_signal / infrared_brute_force into lib (by @xMasterX) - OFW: BLE: Improved pairing security (by @hedger) - JS: Expose button event type in `gui/widget` button callback, see breaking changes above (by @WillyJL) - NFC: - OFW: Synchronise mf_classic_dict.nfc with mfc_default_keys.dic from Proxmark3 Iceman fork, 164 new MFC keys (by @ry4000) - OFW: Expose nfc_common.h (by @zinongli) - OFW: GUI: Store View by value inViewStack to save memory (by @CookiePLMonster) -- UL: Docs: Update Sub-GHz DoorHan programming instructions (by @li0ard) +- Docs: + - UL: Update Sub-GHz DoorHan programming instructions (by @li0ard) + - OFW: Update devboard docs (by @alexeyzakh) ### Fixed: - CLI: Fix long delay with quick connect/disconnect, qFlipper should connect faster as expected again (by @WillyJL) From ca80bb68d997d6fe9ebba35338f366dd4fe12ee3 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sat, 29 Nov 2025 22:16:49 +0100 Subject: [PATCH 184/192] CI: Fix PR cleanup --nobuild --- .github/workflows/pr-cleanup.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-cleanup.yml b/.github/workflows/pr-cleanup.yml index 9ecd9b5cd..b8cf09cbc 100644 --- a/.github/workflows/pr-cleanup.yml +++ b/.github/workflows/pr-cleanup.yml @@ -15,7 +15,7 @@ jobs: exit fi if ${{ !!github.event.pull_request }} ; then - SHOULD_REINDEX=${{ github.event.pull_request.action == 'closed' }} + SHOULD_REINDEX=${{ github.event.action == 'closed' }} else echo "What?" exit 1 From 945766642f337ee221ffa4bbbcb55f168ce1b671 Mon Sep 17 00:00:00 2001 From: jay candel Date: Sun, 30 Nov 2025 08:39:33 +0800 Subject: [PATCH 185/192] Infrared: Update Universals (#465) * update ir universals * add the actual files * Delete applications/main/infrared/resources/infrared/assets/bluray-dvd.ir * oops * Update fans.ir * Remove single duplicate :) * Update changelog * misc fixes * one more fix tm * Infrared: Yellow doesn't exist in LEDs remote, fix Power_on/off capitalization * Update changelog --------- Co-authored-by: WillyJL --- CHANGELOG.md | 1 + .../infrared/resources/infrared/assets/ac.ir | 4 +- .../resources/infrared/assets/audio.ir | 212 ++++- .../resources/infrared/assets/bluray_dvd.ir | 235 ++++- .../resources/infrared/assets/digital_sign.ir | 3 +- .../resources/infrared/assets/fans.ir | 3 +- .../resources/infrared/assets/leds.ir | 817 +++++++++++++++++- .../resources/infrared/assets/monitor.ir | 22 +- .../resources/infrared/assets/projectors.ir | 4 +- .../infrared/resources/infrared/assets/tv.ir | 140 ++- 10 files changed, 1422 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d21f9147a..97eceabd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -147,6 +147,7 @@ - UL: Tune Linear (add better EZCode support), Dickert MAHS decoders (by @xMasterX) - UL: Some fixes and improvements to Honeywell Sec protocol (by @xMasterX) - Infrared: + - Update universal remotes from IRDB (#465 by @jaylikesbunda) - OFW: Add an old JVC model to universal remotes (by @zgracem) - OFW: Add Daikin FTXN25LV1B9 and Toyotomi KTN22-12R32 to universal remotes (by @minchogaydarov) - OFW: Infrared Universal remote DBs unit test & move infrared_signal / infrared_brute_force into lib (by @xMasterX) diff --git a/applications/main/infrared/resources/infrared/assets/ac.ir b/applications/main/infrared/resources/infrared/assets/ac.ir index b8e454ff8..6620ac8f9 100644 --- a/applications/main/infrared/resources/infrared/assets/ac.ir +++ b/applications/main/infrared/resources/infrared/assets/ac.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 -# Last Updated 21st Feb, 2024 -# Last Checked 21st Feb, 2024 +# Last Updated 29th Aug, 2025 +# Last Checked 7th Sept, 2025 # # Model: Electrolux EACM-16 HP/N3 name: Off diff --git a/applications/main/infrared/resources/infrared/assets/audio.ir b/applications/main/infrared/resources/infrared/assets/audio.ir index 4872f5a75..4e2d576b8 100644 --- a/applications/main/infrared/resources/infrared/assets/audio.ir +++ b/applications/main/infrared/resources/infrared/assets/audio.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 -# Last Updated 21st Jan, 2025 -# Last Checked 21st Jan, 2025 +# Last Updated 29th Aug, 2025 +# Last Checked 29th Aug, 2025 # name: Power type: parsed @@ -5110,6 +5110,7 @@ address: 00 00 00 00 command: 15 00 00 00 # # Model: Sony MHC_GSX75 +# name: Pause type: parsed protocol: SIRC20 @@ -5117,6 +5118,7 @@ address: 3A 07 00 00 command: 38 00 00 00 # # Model: Philips FW750C +# name: Pause type: parsed protocol: RC5 @@ -5160,6 +5162,7 @@ address: A2 00 00 00 command: 1B 00 00 00 # # Model: GPX cd_radio +# name: Pause type: parsed protocol: Samsung32 @@ -5173,6 +5176,7 @@ address: 81 00 00 00 command: 11 00 00 00 # # Model: GPX HC221B +# name: Prev type: parsed protocol: NEC @@ -5216,6 +5220,7 @@ address: D2 2C 00 00 command: 01 FE 00 00 # # Model: Panasonic SC-HC58 +# name: Pause type: parsed protocol: Kaseikyo @@ -5223,6 +5228,7 @@ address: AC 02 20 00 command: 01 00 00 00 # # Model: Philips CD_Player_723 +# name: Prev type: parsed protocol: RC5 @@ -5308,6 +5314,7 @@ address: 00 EF 00 00 command: 17 E8 00 00 # # Model: Winnes KC809 +# name: Pause type: parsed protocol: NECext @@ -5321,6 +5328,7 @@ address: 41 54 32 00 command: 22 00 00 00 # # Model: Grundig CMS_5000 +# name: Pause type: parsed protocol: NECext @@ -5328,6 +5336,7 @@ address: 30 FC 00 00 command: 05 FA 00 00 # # Model: DTR-7 +# name: Power type: parsed protocol: NECext @@ -5407,6 +5416,7 @@ address: D2 14 00 00 command: 8E 71 00 00 # # Model: Panasonic N2QAYB000210 +# name: Pause type: parsed protocol: Kaseikyo @@ -5438,6 +5448,7 @@ address: AA 00 00 00 command: 5B 00 00 00 # # Model: Portta Toslink_Audio_Switcher +# name: Power type: parsed protocol: NEC @@ -5445,6 +5456,7 @@ address: 00 00 00 00 command: 17 00 00 00 # # Model: Rega IO +# name: Prev type: parsed protocol: NEC @@ -5452,6 +5464,7 @@ address: 6E 00 00 00 command: 08 00 00 00 # # Model: Sony RMT-AA320U_AV +# name: Pause type: parsed protocol: SIRC20 @@ -5465,6 +5478,7 @@ address: 10 01 00 00 command: 7D 00 00 00 # # Model: Sony STR-DH590 +# name: Prev type: parsed protocol: SIRC20 @@ -5496,6 +5510,7 @@ address: 00 00 00 00 command: 1F 00 00 00 # # Model: Hisense HS215 +# name: Power type: parsed protocol: NEC @@ -5533,6 +5548,7 @@ address: 04 00 00 00 command: 23 00 00 00 # # Model: Hitachi HSB40B16 +# name: Power type: parsed protocol: NEC @@ -5570,6 +5586,7 @@ address: 00 00 00 00 command: 45 00 00 00 # # Model: JVC THBY370A +# name: Power type: parsed protocol: NEC @@ -5607,6 +5624,7 @@ address: C8 91 00 00 command: 26 D9 00 00 # # Model: RCA RTS7110B2 +# name: Power type: parsed protocol: NEC @@ -5650,6 +5668,7 @@ address: 80 00 00 00 command: 0A 00 00 00 # # Model: Toshiba SBX4250 +# name: Power type: parsed protocol: NECext @@ -5687,6 +5706,7 @@ address: 45 BD 00 00 command: 61 9E 00 00 # # Model: Edifier R1280DB +# name: Prev type: parsed protocol: NECext @@ -5694,6 +5714,7 @@ address: 10 E7 00 00 command: 06 F9 00 00 # # Model: Edifier R1700BTs +# name: Prev type: parsed protocol: NECext @@ -5701,6 +5722,7 @@ address: 10 E7 00 00 command: 44 BB 00 00 # # Model: Quacker LED_Speaker +# name: Prev type: parsed protocol: NECext @@ -5762,6 +5784,7 @@ address: 78 00 00 00 command: 01 00 00 00 # # Model: Yamaha ZP45780 +# name: Prev type: parsed protocol: NECext @@ -5781,6 +5804,7 @@ address: 87 7C 00 00 command: 1A E5 00 00 # # Model: AEG +# name: Prev type: parsed protocol: NECext @@ -5788,6 +5812,7 @@ address: 87 7C 00 00 command: 1D E2 00 00 # # Model: Panasonic RXDS750 +# name: Power type: parsed protocol: Kaseikyo @@ -5825,18 +5850,20 @@ address: AA 02 20 00 command: 60 00 00 00 # # Model: YAMAHA CDC600 +# name: Pause type: raw frequency: 38400 duty_cycle: 0.330000 data: 9024 4512 564 1692 564 564 564 564 564 1692 564 1692 564 1692 564 1692 564 564 564 564 564 1692 564 1692 564 564 564 564 564 564 564 564 564 1692 564 1692 564 564 564 1692 564 564 564 1692 564 564 564 1692 564 564 564 564 564 1692 564 564 564 1692 564 564 564 1692 564 564 564 1692 564 # +# Model: Adastra WA215 +# name: Play type: raw frequency: 38400 duty_cycle: 0.330000 data: 9024 4512 564 1692 564 564 564 564 564 1692 564 1692 564 1692 564 1692 564 564 564 564 564 1692 564 1692 564 564 564 564 564 564 564 564 564 1692 564 564 564 1692 564 564 564 564 564 564 564 564 564 564 564 564 564 1692 564 564 564 1692 564 1692 564 1692 564 1692 564 1692 564 1692 564 -# Model: Adastra WA215 # name: Prev type: raw @@ -5845,6 +5872,7 @@ duty_cycle: 0.330000 data: 1009 1483 514 482 516 479 509 486 512 1481 516 1478 509 487 511 484 514 480 508 1486 511 1483 514 1479 518 478 510 485 513 1480 517 1477 510 1485 512 50922 1011 1481 516 480 508 487 511 484 514 1479 518 1476 511 485 513 481 517 479 509 1484 513 1480 517 1477 510 486 512 483 515 1478 509 1485 512 1483 514 50920 1013 1479 518 478 510 485 513 482 516 1477 510 1484 513 483 515 480 508 487 511 1482 515 1479 518 1475 594 401 515 481 517 1476 511 1483 514 1481 516 50918 1015 1478 509 486 512 483 515 480 518 1475 512 1483 514 481 517 478 510 485 513 1480 517 1477 510 1484 513 482 516 479 509 1485 512 1482 515 1479 518 50916 1008 1485 512 483 515 480 518 477 511 1483 514 1479 518 478 510 485 513 482 516 1477 510 1484 513 1481 516 479 519 476 512 1482 515 1479 518 1477 510 50924 1009 1484 513 482 516 478 510 486 512 1481 516 1478 509 486 512 483 515 480 508 1486 511 1483 514 1480 507 487 511 510 488 1480 517 1477 510 1485 512 # # Model: Bowers and_Wilkins_Zeppelin_Air_(with_30-pin_connector) +# name: Power type: parsed protocol: RC6 @@ -5869,12 +5897,13 @@ protocol: RC6 address: D2 00 00 00 command: 10 00 00 00 # +# Model: Firetv EVG487 +# name: Vol_dn type: parsed protocol: RC6 address: D2 00 00 00 command: 11 00 00 00 -# Model: Firetv EVG487 # name: Vol_dn type: parsed @@ -5919,6 +5948,7 @@ duty_cycle: 0.330000 data: 4552 4473 529 503 501 477 527 503 501 503 500 1481 527 1480 528 502 501 502 501 1506 502 1506 501 1506 526 1482 525 478 525 479 524 480 523 467 523 4484 522 481 522 481 523 481 523 481 523 481 523 481 523 1485 523 1485 523 481 523 481 522 481 523 1486 522 1485 522 1486 522 481 523 481 523 1486 522 1486 522 1486 522 481 522 55471 4548 4476 525 478 525 479 524 480 523 480 524 1485 523 1485 523 480 523 481 523 1485 523 1485 523 1485 523 1485 523 481 523 481 523 481 523 468 522 4484 522 481 523 481 522 481 523 481 522 481 523 481 523 1485 523 1486 522 481 523 481 523 481 523 1486 522 1486 522 1486 522 481 523 481 523 1486 522 1485 522 1486 521 481 523 # # Model: Samsung AH59-02758A +# name: Power type: raw frequency: 38000 @@ -5955,6 +5985,8 @@ frequency: 38000 duty_cycle: 0.330000 data: 4550 4464 585 420 584 420 584 420 612 392 529 1478 531 1479 555 451 554 452 552 1455 554 1479 529 1481 527 1482 526 478 526 479 525 478 526 479 525 4498 526 478 526 478 526 479 525 479 526 1484 525 479 525 479 525 479 525 1484 525 1483 526 1483 526 479 525 479 525 1484 525 1484 525 1483 526 479 525 479 525 479 525 1484 525 55494 4546 4495 525 478 526 479 525 479 525 479 526 1484 525 1483 526 479 526 479 525 1484 525 1484 525 1484 525 1484 525 479 525 479 525 479 525 479 526 4498 525 478 526 479 526 479 525 479 525 1484 526 479 525 479 526 479 525 1484 525 1484 525 1484 525 479 525 479 525 1484 525 1484 525 1484 525 479 525 479 526 479 525 1484 526 # +# Model: Adastra WA215 +# name: Prev type: raw frequency: 38000 @@ -5992,4 +6024,176 @@ type: raw frequency: 38000 duty_cycle: 0.333333 data: 8400 4200 526 1578 526 1578 526 1578 526 1578 526 1578 526 526 526 526 526 1578 526 526 526 1578 526 1578 526 1578 526 1578 526 1578 526 526 526 526 526 25248 526 1578 526 1578 526 1578 526 1578 526 1578 526 526 526 526 526 1578 526 526 526 1578 526 1578 526 1578 526 1578 526 1578 526 526 526 526 526 33664 +## +name: Prev +type: parsed +protocol: Kaseikyo +address: 41 54 32 00 +command: E3 03 00 00 +# +# Model: Denon AVR-2113CI_Zone_2 +# +name: Next +type: parsed +protocol: Kaseikyo +address: 41 54 32 00 +command: E4 03 00 00 +# +name: Power +type: parsed +protocol: Kaseikyo +address: 43 54 32 00 +command: 05 00 00 00 +# +name: Vol_up +type: parsed +protocol: Kaseikyo +address: 43 54 32 00 +command: 70 01 00 00 +# +name: Vol_dn +type: parsed +protocol: Kaseikyo +address: 43 54 32 00 +command: 71 01 00 00 +# +name: Mute +type: parsed +protocol: Kaseikyo +address: 43 54 32 00 +command: 72 01 00 00 +# +name: Prev +type: parsed +protocol: Kaseikyo +address: 43 54 32 00 +command: E3 03 00 00 +# +# Model: Denon RC1253 +# +name: Next +type: parsed +protocol: Kaseikyo +address: 43 54 32 00 +command: E4 03 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 86 76 00 00 +command: D1 2E 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 86 76 00 00 +command: D2 2D 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 86 76 00 00 +command: D3 2C 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 86 76 00 00 +command: B0 4F 00 00 +# +name: Play +type: parsed +protocol: NECext +address: 86 76 00 00 +command: CA 35 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: 86 76 00 00 +command: CB 34 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: 86 76 00 00 +command: CD 32 00 00 +# +# Model: Harman Kardon AVR130 +# +name: Next +type: parsed +protocol: NECext +address: 86 76 00 00 +command: CE 31 00 00 +# +# Model: MCM Volume_Control_50-8394 +# +name: Prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1871 1673 983 791 1871 789 982 790 956 816 981 4470 981 1702 1837 1710 945 830 944 830 1833 828 944 829 944 1714 1832 829 943 76228 1868 1703 950 824 1832 831 941 832 940 833 939 4536 916 1741 1805 1741 915 858 916 858 1804 857 915 857 915 1742 1803 857 915 +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0D 00 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: BA 4B 00 00 +command: 56 A9 00 00 +# +# Model: Philips BTD2180 +# +name: Next +type: parsed +protocol: NECext +address: BA 4B 00 00 +command: 58 A7 00 00 +# +name: Power +type: parsed +protocol: RC5 +address: 12 00 00 00 +command: 0C 00 00 00 +# +name: Pause +type: parsed +protocol: RC5 +address: 12 00 00 00 +command: 30 00 00 00 +# +name: Play +type: parsed +protocol: RC5 +address: 12 00 00 00 +command: 35 00 00 00 +# +name: Power +type: parsed +protocol: RC6 +address: 15 00 00 00 +command: 0C 00 00 00 +# +name: Pause +type: parsed +protocol: RC6 +address: 04 00 00 00 +command: 30 00 00 00 +# +name: Prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8504 4194 579 468 524 1584 584 469 523 1586 582 466 526 1585 583 491 501 1584 584 1536 502 540 583 1513 525 541 582 1512 526 539 638 1460 524 538 584 1512 526 540 637 1459 525 541 636 412 526 538 639 1459 524 540 637 417 521 1586 637 414 524 1585 638 1482 501 1583 640 414 524 1584 638 27293 8451 4243 638 436 625 1460 639 431 630 1461 638 436 623 1462 639 436 621 1463 640 1482 623 415 641 1481 625 414 640 1462 643 418 637 1482 621 420 638 1482 623 421 635 1478 628 416 638 437 622 419 637 1482 623 416 640 436 625 1464 635 436 624 1461 639 1462 643 1462 639 413 646 1462 639 27294 8566 4132 633 431 639 1458 526 539 637 1458 633 433 636 1458 633 432 637 1457 634 1477 638 411 527 1584 638 412 633 1477 638 411 634 1480 635 411 635 1476 639 412 526 1584 638 413 632 431 639 411 634 1478 637 411 634 432 638 1457 527 540 636 1458 633 1478 637 1456 635 432 638 1459 632 +# +name: Prev +type: parsed +protocol: NEC +address: 7A 00 00 00 +command: AA 00 00 00 # \ No newline at end of file diff --git a/applications/main/infrared/resources/infrared/assets/bluray_dvd.ir b/applications/main/infrared/resources/infrared/assets/bluray_dvd.ir index 691ad7090..6556603ec 100644 --- a/applications/main/infrared/resources/infrared/assets/bluray_dvd.ir +++ b/applications/main/infrared/resources/infrared/assets/bluray_dvd.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 # -# Last Updated: 15th October 2024 +# Last Updated: 29th Aug, 2025 # name: Power type: parsed @@ -28,6 +28,7 @@ address: 2D 00 00 00 command: 50 00 00 00 # # Model: LG AKB73775801 +# name: Power type: parsed protocol: Samsung32 @@ -83,6 +84,7 @@ address: 2D 00 00 00 command: 36 00 00 00 # # Model: OPPO BDP103 +# name: Power type: parsed protocol: NEC @@ -132,6 +134,7 @@ address: 49 00 00 00 command: 4B 00 00 00 # # Model: Panasonic DMPBDT167 +# name: Power type: parsed protocol: Kaseikyo @@ -187,6 +190,7 @@ address: B0 02 20 02 command: 30 01 00 00 # # Model: Philips BDP2501/F7 +# name: Power type: parsed protocol: RC6 @@ -230,6 +234,7 @@ address: 46 00 00 00 command: 2C 00 00 00 # # Model: Philips BDP2700 +# name: Power type: parsed protocol: RC6 @@ -261,6 +266,7 @@ address: 46 00 00 00 command: 31 00 00 00 # # Model: Pioneer BDP150 +# name: Power type: parsed protocol: Pioneer @@ -322,6 +328,7 @@ address: AF 00 00 00 command: 39 00 00 00 # # Model: Samsung AK59_00149A +# name: Power type: raw frequency: 38000 @@ -353,6 +360,7 @@ duty_cycle: 0.330000 data: 4539 4519 488 516 489 489 516 515 490 514 491 514 491 1491 520 485 520 511 494 511 484 520 485 520 485 493 512 519 486 518 487 491 514 504 491 4491 516 1492 519 1490 521 1488 513 519 486 518 487 1495 516 1493 518 487 518 513 492 1517 494 484 511 520 485 1498 513 491 514 517 488 1495 516 1493 518 486 519 1491 510 1499 512 57491 4537 4495 512 492 513 519 486 493 512 520 485 521 494 1491 520 486 519 513 492 514 491 515 490 516 489 517 488 517 488 518 487 519 486 506 489 4502 516 1523 488 1498 513 1500 521 511 494 512 493 1519 492 1520 491 515 490 516 489 1522 489 491 514 518 487 1498 513 520 485 521 484 1528 493 1492 519 514 491 1521 490 1523 488 # # Model: Samsung B59-01301A +# name: Power type: parsed protocol: Samsung32 @@ -372,6 +380,7 @@ address: 07 00 00 00 command: 4A 00 00 00 # # Model: Samsung BD-D5300 +# name: Power type: raw frequency: 38000 @@ -403,6 +412,7 @@ duty_cycle: 0.330000 data: 4510 4508 537 470 535 467 538 470 535 468 537 471 534 1475 534 467 538 470 535 467 538 469 536 467 538 470 535 467 538 470 535 468 537 469 536 4472 537 1472 537 1472 537 1472 537 470 535 467 538 470 535 1475 534 1475 534 1474 535 467 538 470 535 468 537 1472 537 1473 536 469 536 468 537 469 536 1474 535 1473 536 1473 536 # # Model: Samsung BDD7500 +# name: Power type: raw frequency: 38000 @@ -452,6 +462,7 @@ duty_cycle: 0.330000 data: 4489 4495 514 428 571 485 514 486 513 486 513 486 513 1482 512 487 512 488 511 486 488 510 489 512 487 511 488 511 488 512 487 506 493 512 487 4507 487 1507 487 1507 487 1508 486 510 489 513 486 1506 488 1507 487 513 486 458 541 1507 487 512 487 512 487 1507 487 512 487 512 487 1507 487 1506 488 512 487 1426 568 1506 488 # # Model: Samsung BDE5300 +# name: Power type: raw frequency: 38000 @@ -501,6 +512,7 @@ duty_cycle: 0.330000 data: 4489 4478 516 481 517 483 515 483 515 482 516 484 514 1456 515 481 517 484 514 482 516 484 514 483 515 483 515 481 517 482 516 367 632 482 516 4451 515 1457 514 1457 514 1455 516 483 515 483 515 1456 515 1412 559 483 515 484 514 1456 515 483 515 486 512 1458 513 483 515 482 516 1456 515 1455 516 484 514 1457 514 1457 514 57918 4486 4476 514 484 514 483 515 484 514 484 514 484 514 1458 513 483 515 483 515 484 514 484 514 483 515 484 514 483 515 483 515 484 514 483 515 4452 514 1457 514 1457 514 1456 515 482 516 484 514 1457 514 1457 514 484 514 486 512 1456 515 483 515 484 514 1456 515 485 513 484 514 1457 514 1457 514 483 515 1457 514 1456 515 # # Model: Sanyo Bluray_NC088 +# name: Power type: parsed protocol: NECext @@ -538,6 +550,7 @@ address: 87 22 00 00 command: 7A 85 00 00 # # Model: Sharp BD-HP20 +# name: Power type: raw frequency: 38000 @@ -587,6 +600,7 @@ duty_cycle: 0.330000 data: 3341 1723 380 454 380 1298 381 454 380 1298 381 454 380 1298 381 455 379 1298 381 454 380 1299 380 453 381 1299 380 1299 380 453 381 1298 381 453 381 1298 381 1299 380 1272 407 1214 465 453 381 452 382 453 381 1299 380 453 381 454 380 453 381 453 381 1298 381 1232 447 453 381 454 380 1298 381 1299 380 453 381 1301 378 1299 380 1299 380 454 380 452 382 1298 381 453 381 453 381 453 381 454 380 1202 477 453 381 453 381 20534 3341 1723 380 453 381 1298 381 453 381 1299 380 453 381 1298 381 453 381 1298 381 453 381 1298 381 453 381 1298 381 1299 380 453 381 1299 380 453 381 1299 405 1274 380 1298 381 1299 380 453 381 454 380 452 382 1298 381 453 381 453 381 454 380 453 381 1298 381 1298 381 453 381 453 381 1298 381 1298 381 453 381 1299 380 1299 380 1299 380 453 381 453 381 1298 381 453 381 453 381 453 381 454 380 1298 381 454 380 453 381 20535 3340 1723 380 454 380 1299 380 454 380 1299 380 454 380 1263 416 454 380 1299 380 454 380 1299 380 454 380 1300 379 1299 380 454 380 1299 380 454 380 1299 380 1299 380 1299 380 1300 379 454 380 455 379 455 379 1300 379 455 379 454 380 455 379 454 380 1299 380 1218 462 454 379 455 379 1300 379 1301 378 455 379 1301 378 1301 378 1300 500 332 381 455 402 1277 402 433 401 432 402 432 402 432 402 1277 402 431 403 431 403 # # Model: RMT VB201U +# name: Power type: parsed protocol: SIRC20 @@ -636,6 +650,7 @@ address: 5A 1C 00 00 command: 1B 00 00 00 # # Model: Toshiba SE-R0398 +# name: Power type: parsed protocol: NECext @@ -667,6 +682,7 @@ address: 45 B5 00 00 command: 28 D7 00 00 # # Model: Vizio VBR220 +# name: Power type: parsed protocol: NEC @@ -722,6 +738,7 @@ address: 00 00 00 00 command: 33 00 00 00 # # Model: Bose 3-2-1_Series_1 +# name: Power type: parsed protocol: NECext @@ -741,6 +758,7 @@ address: BA 4B 00 00 command: 55 AA 00 00 # # Model: Brandt DVDP-7R +# name: Ok type: parsed protocol: NEC @@ -748,6 +766,7 @@ address: 00 00 00 00 command: 07 00 00 00 # # Model: GPX D2816 +# name: Power type: parsed protocol: NEC @@ -779,6 +798,7 @@ address: 00 00 00 00 command: 03 00 00 00 # # Model: LG DKS-6100Q +# name: Power type: parsed protocol: Samsung32 @@ -804,6 +824,7 @@ address: B0 02 20 02 command: 10 01 00 00 # # Model: Philips RC_5610 +# name: Power type: parsed protocol: RC6 @@ -829,6 +850,7 @@ address: 04 00 00 00 command: 4B 00 00 00 # # Model: PIONEER-DVD PLAYER-VXX2702 +# name: Power type: parsed protocol: NEC @@ -848,6 +870,7 @@ address: AF 00 00 00 command: 9E 00 00 00 # # Model: Prinz DVD_Player_T182 +# name: Power type: raw frequency: 38000 @@ -861,6 +884,7 @@ duty_cycle: 0.330000 data: 8362 4520 567 599 540 547 603 564 544 595 544 543 596 544 575 592 547 566 573 1680 568 1685 574 1681 598 1709 550 1704 544 1710 569 1712 546 1708 540 1714 576 1705 543 544 595 1711 547 540 599 541 567 573 566 1687 571 569 570 544 595 1711 547 540 599 1681 567 1686 573 1682 597 570 549 40168 8377 2261 567 # # Model: Sony DVD_RMT-D197A +# name: Power type: parsed protocol: SIRC20 @@ -886,6 +910,7 @@ address: 3A 09 00 00 command: 39 00 00 00 # # Model: Strato DVD507 +# name: Power type: parsed protocol: NEC @@ -899,6 +924,7 @@ address: 00 00 00 00 command: 1E 00 00 00 # # Model: Toshiba SE_R0108 +# name: Power type: parsed protocol: NEC @@ -924,6 +950,7 @@ address: 45 00 00 00 command: 15 00 00 00 # # Model: TOSHIBA SE_R0420 +# name: Power type: raw frequency: 38000 @@ -931,6 +958,7 @@ duty_cycle: 0.330000 data: 9072 4364 664 1566 664 451 663 1567 663 453 661 454 660 478 636 1594 636 479 635 1595 635 480 635 1596 634 481 634 1597 633 1597 634 482 633 1598 632 482 633 482 633 482 633 482 633 482 633 482 633 1597 633 1598 632 1598 632 1598 632 1598 632 1598 632 1598 632 1598 632 482 633 482 633 39968 9043 2203 634 95675 9067 2203 634 95675 9067 2203 634 # # Model: Dvd tv_player +# name: Power type: parsed protocol: NEC @@ -938,6 +966,7 @@ address: 00 00 00 00 command: 03 00 00 00 # # Model: Dvd tv_player_2 +# name: Subtitle type: parsed protocol: NEC @@ -957,6 +986,7 @@ address: 00 00 00 00 command: 53 00 00 00 # # Model: APEX RM_3800 +# name: Power type: parsed protocol: NEC @@ -970,6 +1000,7 @@ address: 01 00 00 00 command: 19 00 00 00 # # Model: RVR-4000 +# name: Power type: parsed protocol: NEC @@ -989,6 +1020,7 @@ address: 35 00 00 00 command: 1B 00 00 00 # # Model: JVC HR-A591U +# name: Power type: raw frequency: 38000 @@ -1008,6 +1040,7 @@ duty_cycle: 0.330000 data: 8414 4228 507 1601 506 1600 507 547 506 548 505 546 507 547 506 1601 506 547 506 547 506 546 507 1600 507 1599 508 547 506 547 506 546 507 546 507 23151 505 1600 507 1601 506 547 506 547 506 547 506 547 506 1600 507 546 507 547 506 547 506 1600 507 1600 507 546 507 548 505 547 506 546 507 23150 506 1600 507 1601 506 545 508 547 506 547 506 545 508 1601 506 548 505 547 506 546 507 1599 508 1600 507 547 506 547 506 546 507 547 506 # # Model: JVC HR-J700E +# name: Power type: raw frequency: 38000 @@ -1033,6 +1066,7 @@ duty_cycle: 0.330000 data: 8422 4235 547 1565 547 1564 548 507 548 507 549 507 549 508 548 1565 547 510 545 507 549 509 547 1563 549 1563 549 1567 545 1564 548 507 549 506 550 20891 546 1566 545 1564 548 508 548 507 548 509 547 506 549 1565 547 509 547 507 548 509 547 1563 549 1562 550 1563 549 1564 548 508 548 508 548 21245 548 1565 547 1565 547 507 549 509 547 509 546 508 548 1566 546 508 548 508 547 507 549 1566 546 1564 548 1564 548 1562 549 508 548 508 548 21246 547 1563 549 1563 549 509 547 509 547 508 547 508 547 1565 547 508 548 507 548 507 549 1564 548 1565 547 1564 548 1565 546 507 549 507 549 21246 547 1564 548 1566 546 506 549 511 545 508 547 508 548 1564 548 508 548 508 548 507 549 1563 549 1564 548 1564 548 1563 548 509 547 508 547 21246 547 1564 548 1564 548 509 547 509 547 508 548 508 548 1564 548 507 549 507 548 508 548 1564 548 1566 546 1564 548 1564 548 508 548 509 547 21245 548 1565 547 1563 549 509 547 508 547 508 548 506 550 1566 546 511 545 507 549 508 547 1565 547 1563 549 1564 548 1566 546 508 548 508 548 # # Model: JVC HR-S2902U +# name: Power type: raw frequency: 38000 @@ -1058,6 +1092,7 @@ duty_cycle: 0.330000 data: 8376 4181 504 1565 505 1592 504 543 505 570 504 570 504 544 504 1592 505 571 503 544 504 570 504 1566 504 1591 505 1591 505 1566 504 570 504 570 504 21772 505 1592 504 1565 505 570 504 569 505 543 505 570 504 1566 504 571 503 569 505 544 504 1592 504 1592 504 1565 505 1591 505 544 504 570 504 21773 504 1591 505 1565 505 570 504 570 504 543 505 569 505 1565 505 572 502 570 504 542 506 1591 505 1591 505 1565 505 1592 504 544 504 568 506 21773 505 1592 504 1567 503 570 504 568 506 543 505 569 505 1566 504 570 504 570 504 543 505 1591 505 1591 505 1565 505 1591 506 543 505 569 505 # # Model: LG GC260W +# name: Power type: parsed protocol: NEC @@ -1077,6 +1112,7 @@ address: 6E 00 00 00 command: 08 00 00 00 # # Model: Magnavox N9377 +# name: Power type: raw frequency: 38000 @@ -1102,6 +1138,7 @@ duty_cycle: 0.330000 data: 3489 3523 881 865 876 886 882 888 880 2607 875 885 883 2628 881 2607 875 2627 882 887 881 2607 875 888 880 886 882 2607 875 2628 881 2627 882 864 877 2627 882 887 881 865 876 886 881 2628 881 867 874 2629 880 2628 881 33917 3494 3502 874 888 880 887 881 865 876 2628 881 888 880 2606 876 2629 880 2629 880 864 876 2629 880 889 879 867 873 2629 880 2629 880 2606 876 888 880 2630 879 865 876 889 879 890 878 2607 875 889 879 2631 878 2608 874 # # Model: Panasonic Light_Tower_LSSQ0342 +# name: Play type: parsed protocol: Kaseikyo @@ -1127,6 +1164,7 @@ address: 90 02 20 00 command: 60 00 00 00 # # Model: Panasonic NV-FJ606 +# name: Power type: raw frequency: 38000 @@ -1134,6 +1172,7 @@ duty_cycle: 0.330000 data: 3538 1702 463 407 461 1273 463 406 462 405 463 405 463 405 463 405 487 382 487 407 460 408 459 410 457 411 457 411 457 1280 457 411 457 411 457 411 457 411 457 411 457 411 457 1280 457 411 457 412 456 1280 457 412 457 412 456 411 457 411 457 412 456 412 456 412 456 412 456 1280 457 412 456 1281 456 1281 456 1281 456 1281 456 412 456 412 456 1281 456 413 455 1281 456 1282 455 413 455 1282 455 413 455 1282 455 74229 3532 1709 457 411 457 1280 457 411 457 412 456 412 456 412 457 411 457 412 456 412 456 412 456 412 456 412 456 412 456 1281 456 412 457 412 456 412 456 412 456 412 456 412 456 1281 456 412 456 412 456 1281 456 413 455 413 455 413 455 413 455 413 455 413 455 413 455 413 455 1282 455 413 455 1282 455 1282 455 1282 455 1282 455 414 454 414 454 1283 454 414 454 1283 454 1283 454 414 454 1283 454 414 454 1283 454 # # Model: Panasonic VCR_PV9662 +# name: Power type: raw frequency: 38000 @@ -1159,6 +1198,7 @@ duty_cycle: 0.330000 data: 3510 1694 450 416 454 1280 450 417 453 414 445 422 448 418 452 415 455 412 447 420 450 416 454 413 446 420 450 417 453 1282 448 419 451 416 454 412 447 420 450 417 453 414 445 1288 452 415 444 422 448 1286 454 413 446 420 450 417 453 414 445 422 448 418 452 415 455 412 447 419 451 1284 446 1288 452 414 445 422 448 419 451 416 454 413 446 420 450 1284 446 1288 452 415 444 1290 450 416 454 413 446 1288 452 74520 3511 1688 446 422 448 1286 454 412 447 420 450 416 454 413 446 421 449 418 452 415 444 422 448 419 451 415 455 412 447 1287 453 414 445 421 449 418 452 415 444 422 448 419 451 1283 447 420 450 417 453 1281 449 418 452 415 444 422 448 419 451 416 454 413 446 420 450 417 453 414 445 1289 451 1283 447 420 450 417 453 414 445 421 449 418 452 415 455 1279 451 1284 446 421 449 1285 445 422 448 419 451 1283 447 # # Model: Philips-RC2K16 +# name: Power type: parsed protocol: RC6 @@ -1172,6 +1212,7 @@ address: 04 00 00 00 command: 2C 00 00 00 # # Model: Sony RM-Y126 +# name: Power type: parsed protocol: SIRC @@ -1197,6 +1238,7 @@ address: 0B 00 00 00 command: 1A 00 00 00 # # Model: Sony SLV-SE610B +# name: Power type: parsed protocol: SIRC @@ -1204,6 +1246,7 @@ address: 0B 00 00 00 command: 15 00 00 00 # # Model: Unknown1 +# name: Power type: parsed protocol: NEC @@ -1265,6 +1308,7 @@ address: BA 4B 00 00 command: E0 1F 00 00 # # Model: Brandt DVDP-7R +# name: Subtitle type: parsed protocol: NEC @@ -1302,6 +1346,7 @@ duty_cycle: 0.330000 data: 3484 3464 907 829 908 830 907 804 906 2596 906 804 933 2569 878 858 879 832 878 2622 880 831 879 858 879 860 877 2596 878 2623 879 2597 877 858 879 2597 877 857 880 2595 879 2594 880 857 880 2595 907 2595 879 2595 880 33544 3485 3437 907 831 906 831 906 830 907 2568 906 830 907 2567 907 831 906 830 907 2567 907 830 907 805 932 802 908 2594 908 2568 907 2567 907 832 905 2568 906 831 906 2568 879 2622 879 831 906 2596 878 2596 879 2596 906 # # Model: Prinz DVD_Player_T182 +# name: Eject type: raw frequency: 38000 @@ -1321,6 +1366,7 @@ duty_cycle: 0.330000 data: 8328 4524 563 592 527 569 550 545 553 596 533 565 554 568 530 595 534 566 553 1683 565 1684 564 1684 647 1624 562 1685 563 1685 646 1625 561 1687 561 598 531 567 552 571 527 1687 592 567 531 596 533 567 552 1687 572 1678 570 1680 589 1685 563 572 557 1681 619 1630 566 1684 595 565 533 40169 8337 2267 572 # # Model: Sony DVD_RMT-D197A +# name: Eject type: parsed protocol: SIRC20 @@ -1508,6 +1554,7 @@ address: 2D 00 00 00 command: 33 00 00 00 # # Model: NAD T557 +# name: Power type: parsed protocol: NECext @@ -1557,6 +1604,7 @@ address: 86 0F 00 00 command: CD 32 00 00 # # Model: Samsung UBDK8500 +# name: Power type: raw frequency: 38000 @@ -1600,6 +1648,7 @@ duty_cycle: 0.330000 data: 4486 4499 483 516 483 515 484 515 484 514 485 515 484 1510 484 514 485 516 483 516 483 515 484 515 484 516 483 516 483 515 484 516 483 516 483 4450 544 1511 461 1533 461 1534 482 516 461 539 460 539 460 1534 460 539 460 1534 460 539 460 538 461 539 460 1533 461 1534 460 538 461 1534 460 538 461 1534 460 1534 460 1533 461 # # Model: Sylvania SDVD1111 +# name: Power type: parsed protocol: NEC @@ -1661,6 +1710,7 @@ address: 07 00 00 00 command: 45 00 00 00 # # Model: Marantz BD8002 +# name: Power type: raw frequency: 38000 @@ -1704,6 +1754,7 @@ duty_cycle: 0.330000 data: 874 930 1764 942 874 931 873 1833 873 931 873 931 873 4448 873 930 1764 1846 1762 1846 1763 943 873 931 873 931 873 931 873 931 873 75040 873 931 1763 944 872 931 873 1833 873 931 873 931 873 4449 872 931 1763 1846 1762 1845 1764 943 873 931 873 931 873 931 873 931 873 75040 873 931 1763 944 872 931 873 1834 872 931 873 931 873 4449 872 931 1763 1846 1763 1846 1763 943 873 931 873 931 873 931 873 931 873 # # Model: Yamaha BDS667 +# name: Power type: parsed protocol: NEC @@ -1741,6 +1792,7 @@ address: 7C 00 00 00 command: 83 00 00 00 # # Model: JVC XVBP1 +# name: Play type: parsed protocol: NEC @@ -1796,6 +1848,7 @@ address: 2A 03 01 02 command: E1 01 00 00 # # Model: Soniq B100 +# name: Power type: parsed protocol: NECext @@ -1839,6 +1892,7 @@ address: 00 DF 00 00 command: 1E E1 00 00 # # Model: Bose 3-2-1_Series_1 +# name: Subtitle type: parsed protocol: NECext @@ -1846,6 +1900,7 @@ address: 00 DF 00 00 command: 5A A5 00 00 # # Model: Pioneer GGF1381 +# name: Fast_fo type: parsed protocol: NEC @@ -1915,6 +1970,7 @@ duty_cycle: 0.330000 data: 8381 4221 460 1643 461 1643 461 543 461 543 486 518 461 543 461 1643 485 520 460 543 485 519 485 1619 485 1619 485 1597 507 1619 486 518 486 518 486 20819 486 1617 487 1617 487 517 487 517 487 517 487 517 487 1617 487 517 487 517 487 517 487 1617 487 1617 487 1617 487 1617 487 516 488 517 487 20817 488 1617 487 1616 488 516 488 516 488 516 488 516 488 1616 488 517 487 516 488 517 487 1616 488 1617 487 1618 486 1617 487 517 487 516 488 # # Model: Memorex MVR4040A +# name: Power type: parsed protocol: NECext @@ -1994,6 +2050,7 @@ address: BA 00 00 00 command: 0E 00 00 00 # # Model: Sony SLVD201P +# name: Power type: parsed protocol: SIRC20 @@ -2048,8 +2105,182 @@ protocol: SIRC15 address: BA 00 00 00 command: 18 00 00 00 # +# Model: Insignia NSWBRDVD2 +# name: Pause type: parsed protocol: NEC address: 45 00 00 00 -command: 00 00 00 00 \ No newline at end of file +command: 00 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 85 ED 00 00 +command: 06 F9 00 00 +# +name: Eject +type: parsed +protocol: NECext +address: 85 ED 00 00 +command: 26 D9 00 00 +# +name: Ok +type: parsed +protocol: NECext +address: 85 ED 00 00 +command: 17 E8 00 00 +# +name: Play +type: parsed +protocol: NECext +address: 85 ED 00 00 +command: 1A E5 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: 85 ED 00 00 +command: 1A E5 00 00 +# +name: Fast_ba +type: parsed +protocol: NECext +address: 85 ED 00 00 +command: 1E E1 00 00 +# +# Model: JVC XVBP1 +# +name: Fast_fo +type: parsed +protocol: NECext +address: 85 ED 00 00 +command: 1F E0 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3340 1722 405 430 404 1275 404 431 403 1275 404 430 404 1275 404 431 403 1275 404 430 404 1275 404 429 405 1275 404 1274 405 429 405 1275 404 429 405 1275 404 1273 406 1274 405 1274 405 429 405 429 405 429 405 1274 405 429 405 429 405 429 405 429 405 1274 405 1274 405 428 406 429 405 1274 405 429 405 429 405 429 405 429 405 429 405 1274 405 429 405 1274 405 428 406 432 402 429 405 1275 404 1275 404 1274 405 1274 405 20510 3341 1722 405 430 404 1212 467 429 405 1275 404 430 404 1275 404 430 404 1275 404 430 404 1276 403 430 505 1174 404 1276 403 431 403 1276 403 432 380 1299 380 1299 380 1299 380 1299 380 455 379 454 380 455 379 1299 380 455 379 455 379 455 379 455 379 1299 380 1300 379 455 379 454 380 1300 379 455 379 454 380 454 380 455 379 454 380 1300 379 455 379 1299 380 455 379 454 380 455 379 1299 380 1300 379 1300 379 1300 379 20535 3340 1726 378 454 402 1277 402 432 402 1277 402 432 402 1277 402 431 403 1277 402 432 402 1278 401 432 402 1277 402 1277 402 432 402 1276 403 432 402 1277 402 1277 402 1277 402 1277 402 432 402 431 403 407 427 1276 403 433 401 431 403 431 403 431 403 1276 403 1278 401 431 403 432 402 1276 403 431 403 431 403 431 403 431 403 431 403 1276 403 432 402 1276 403 431 403 430 404 431 403 1276 403 1276 403 1276 403 1276 403 +# +name: Eject +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3343 1722 381 452 382 1298 381 453 381 1297 382 452 382 1298 381 452 382 1298 381 453 381 1297 382 452 382 1298 381 1298 381 453 381 1297 382 452 382 1297 382 1301 378 1297 382 1298 381 452 382 453 381 452 382 1297 382 452 382 452 382 453 381 453 381 1298 381 1286 393 452 382 452 382 453 381 1298 381 452 382 452 382 453 381 453 381 1298 381 452 382 1298 381 452 382 452 382 453 381 452 382 452 382 1298 381 1297 382 20533 3342 1722 381 453 381 1297 382 453 381 1297 382 452 382 1298 381 453 381 1299 380 452 382 1297 382 453 381 1298 381 1298 381 452 382 1298 381 379 455 1296 383 1297 382 1297 382 1297 382 453 381 452 382 452 382 1298 381 452 382 453 381 452 382 453 381 1297 405 1274 381 453 405 428 382 452 406 1248 407 453 406 428 406 428 406 428 406 1272 407 428 406 1273 406 427 407 427 407 428 406 427 407 427 407 1272 407 1273 406 20508 3342 1722 406 428 406 1273 406 428 381 1298 406 428 407 1272 406 428 406 1273 405 428 407 1272 407 427 407 1272 407 1273 406 428 406 1272 407 428 406 1230 449 1272 407 1272 406 1273 407 427 407 427 407 427 407 1272 407 427 407 428 406 427 407 427 407 1272 407 1272 407 426 408 427 407 427 407 1272 407 427 407 427 407 427 407 427 407 1271 408 427 407 1272 407 427 407 426 408 426 408 427 407 427 407 1272 407 1271 408 +# +name: Ok +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3341 1723 380 453 381 1298 381 452 382 1298 381 453 381 1298 381 453 381 1298 381 452 382 1298 381 453 381 1298 381 1298 381 453 381 1298 381 453 381 1298 381 1298 381 1298 381 1298 381 453 381 453 381 453 381 1299 380 453 381 453 381 453 381 453 381 1298 381 1299 380 453 381 454 380 453 381 453 381 1299 380 1298 381 1298 381 453 381 413 421 453 381 1298 381 453 381 455 379 453 381 1299 380 1298 381 1298 381 453 381 20534 3341 1724 380 453 381 1299 380 453 381 1299 380 453 381 1299 380 454 380 1299 380 453 381 1303 380 449 381 1298 381 1298 381 453 381 1299 380 453 381 1298 381 1299 380 1298 381 1298 381 453 381 451 383 453 381 1327 352 454 380 453 381 453 381 454 380 1298 381 1299 380 454 380 453 381 454 380 453 381 1299 380 1298 381 1299 380 454 380 454 380 453 405 1275 380 454 380 454 380 453 381 1299 380 1299 380 1299 380 453 381 20534 3341 1723 381 453 381 1299 380 453 381 1298 381 454 380 1299 380 454 380 1299 380 454 380 1298 381 453 381 1299 380 1299 380 453 381 1299 380 454 380 1298 381 1298 381 1298 381 1299 380 454 380 421 413 453 381 1300 379 453 381 452 382 453 381 454 380 1297 382 1299 380 454 380 454 380 453 381 453 381 1291 388 1298 381 1298 381 454 380 481 353 453 381 1299 380 453 381 453 381 453 381 1299 380 1299 380 1298 381 453 381 +# +name: Pause +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3366 1696 408 426 433 1247 407 426 408 1271 433 402 432 1246 433 401 433 1246 433 401 433 1247 432 401 433 1246 433 1247 432 401 408 1271 433 401 433 1246 408 1272 431 1247 433 1247 432 401 432 401 434 401 432 1246 409 426 408 426 408 425 433 402 432 1246 409 1271 433 401 433 401 433 401 433 1246 409 426 432 1246 409 426 433 1246 433 401 433 401 433 1246 433 401 433 401 433 401 433 401 433 1246 433 402 432 401 433 20481 3370 1695 433 401 433 1247 432 401 433 1246 433 401 433 1247 432 401 433 1246 433 401 433 1247 432 398 436 1246 433 1246 433 401 433 1246 433 401 433 1246 433 1246 433 1246 433 1246 433 401 433 401 433 402 432 1246 433 402 432 401 433 401 433 401 433 1246 433 1246 433 401 433 401 433 402 432 1246 433 401 433 1246 433 402 432 1246 433 402 432 401 433 1246 433 401 434 401 432 401 433 401 433 1247 432 401 433 401 433 20482 3368 1697 431 402 432 1247 432 401 433 1247 432 401 433 1247 432 401 433 1247 432 401 433 1246 433 401 433 1247 432 1247 432 401 433 1247 432 401 433 1247 432 1247 432 1246 433 1247 432 402 432 402 432 401 433 1247 432 401 433 402 432 401 433 401 433 1247 432 1247 432 401 433 402 432 401 433 1230 449 402 433 1247 432 401 433 1247 432 402 432 403 431 1246 433 402 432 402 432 402 432 402 432 1247 432 402 432 402 432 +# +name: Play +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3342 1723 380 453 381 1299 380 453 381 1298 381 453 381 1299 380 412 422 1298 381 449 385 1298 381 453 381 1326 353 1298 381 453 381 1298 381 454 380 1298 381 1299 380 1298 381 1298 381 453 381 453 381 454 380 1298 381 453 381 453 381 453 381 453 381 1298 381 1299 380 453 381 453 381 454 380 1299 380 1298 381 453 381 453 381 1282 397 453 381 453 381 1298 381 453 381 453 381 454 380 453 381 1299 380 1298 381 1300 381 20532 3341 1723 380 453 381 1299 380 454 380 1299 380 454 380 1298 381 433 401 1298 381 453 381 1298 381 454 380 1299 380 1298 381 453 381 1299 380 454 380 1299 380 1298 381 1297 382 1298 381 453 381 453 381 454 380 1299 380 453 381 454 380 455 379 454 380 1299 380 1299 380 454 380 453 381 453 381 1298 381 1298 381 454 380 453 381 1299 380 453 381 453 381 1298 381 453 381 453 381 454 380 453 381 1299 380 1299 380 1298 381 20534 3341 1723 380 453 381 1299 380 453 381 1299 380 369 465 1298 381 393 441 1298 381 453 381 1298 381 458 380 1295 380 1299 380 453 381 1299 380 453 381 1299 380 1299 380 1298 381 1299 380 452 382 454 380 454 380 1299 380 454 380 454 380 454 380 454 380 1299 380 1299 380 453 381 453 381 454 380 1298 381 1299 380 457 381 449 381 1299 380 453 381 454 380 1299 380 453 381 453 381 454 380 453 381 1298 381 1299 380 1298 381 +# +name: Fast_ba +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3343 1722 381 452 382 1299 380 453 381 1299 380 453 381 1298 381 454 380 1298 381 455 379 1299 380 454 380 1299 380 1299 380 454 380 1300 379 454 380 1299 380 1300 379 1301 378 1300 379 455 379 455 379 455 379 1300 379 455 379 455 379 455 379 455 379 1301 378 1299 380 455 379 455 379 1299 380 1300 379 1301 378 1300 379 349 485 1299 380 455 379 455 379 1300 379 455 379 455 379 455 379 1300 379 1300 379 1299 380 454 380 20535 3340 1725 379 455 379 1299 380 455 379 1300 379 454 380 1300 379 454 380 1300 379 454 380 1300 379 455 379 1300 379 1300 379 457 377 1300 379 455 379 1300 379 1300 379 1301 378 1299 380 455 379 376 458 455 379 1299 380 454 380 454 380 455 379 455 379 1301 378 1299 380 455 379 455 379 1300 379 1300 379 1300 379 1300 379 455 379 1299 380 455 379 456 378 1300 379 455 379 455 379 455 379 1300 379 1299 380 1299 380 455 379 20535 3340 1725 379 455 379 1299 380 455 379 1299 380 455 379 1298 381 454 380 1327 352 455 379 1300 379 455 379 1299 380 1300 379 456 378 1301 378 455 379 1300 379 1300 379 1299 380 1301 378 454 380 455 379 455 379 1300 379 455 379 455 379 455 379 456 378 1301 378 1300 379 455 379 455 379 1300 379 1300 379 1300 379 1300 379 455 379 1300 379 457 377 455 379 1300 379 455 379 455 379 454 380 1300 379 1299 380 1299 380 455 379 +# +# Model: Soniq B100 +# +# Model: Bose 3-2-1_Series_1 +name: Fast_fo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3342 1722 381 452 382 1298 381 453 382 1296 382 453 381 1298 381 452 382 1297 382 452 382 1298 381 453 381 1297 382 1297 382 452 382 1298 381 453 381 1297 382 1298 381 1297 382 1298 381 452 382 452 382 453 381 1297 382 452 382 452 382 452 382 454 381 1296 382 1297 382 453 381 452 382 453 381 453 381 453 381 453 381 1298 381 1298 381 452 382 452 382 1297 382 453 381 453 381 453 381 1297 382 453 381 453 381 1298 381 20532 3343 1722 381 453 381 1297 382 429 405 1297 382 447 387 1298 381 449 385 1297 382 452 382 1298 381 454 380 1298 381 1298 381 452 382 1297 382 452 382 1298 381 1298 381 1296 383 1298 381 453 381 452 382 453 381 1298 381 453 381 452 382 453 381 452 382 1298 381 1298 381 453 381 452 382 453 381 453 381 452 382 453 381 1297 382 1298 381 452 382 453 381 1298 382 453 380 452 382 453 381 1297 382 452 382 453 381 1298 381 20533 3342 1722 381 453 381 1298 381 453 381 1296 383 453 381 1298 381 452 382 1297 382 453 381 1298 381 452 382 1297 382 1298 381 453 381 1298 381 452 382 1297 382 1297 382 1298 381 1298 381 453 381 452 382 453 381 1298 381 452 382 453 381 453 381 453 381 1297 382 1297 382 452 382 452 382 453 381 453 381 453 381 452 382 1298 381 1298 405 429 405 428 406 1274 406 428 406 428 406 428 406 1274 405 428 406 429 405 1273 406 +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 4A 00 00 00 +# +name: Eject +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 52 00 00 00 +# +name: Subtitle +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 53 00 00 00 +# +name: Fast_ba +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 50 00 00 00 +# +name: Fast_fo +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 58 00 00 00 +# +# Model: Magnavox NB098 +# +name: Ok +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 19 00 00 00 +# +name: Power +type: parsed +protocol: RC6 +address: 30 00 00 00 +command: 0C 00 00 00 +# +name: Eject +type: parsed +protocol: RC6 +address: 30 00 00 00 +command: 45 00 00 00 +# +name: Ok +type: parsed +protocol: RC6 +address: 30 00 00 00 +command: 5C 00 00 00 +# +name: Play +type: parsed +protocol: RC6 +address: 30 00 00 00 +command: 2C 00 00 00 +# +name: Pause +type: parsed +protocol: RC6 +address: 30 00 00 00 +command: 30 00 00 00 +# +name: Fast_ba +type: parsed +protocol: RC6 +address: 30 00 00 00 +command: 29 00 00 00 +# +# Model: Philips RC_5610 +# +name: Fast_fo +type: parsed +protocol: RC6 +address: 30 00 00 00 +command: 28 00 00 00 +# \ No newline at end of file diff --git a/applications/main/infrared/resources/infrared/assets/digital_sign.ir b/applications/main/infrared/resources/infrared/assets/digital_sign.ir index 7c7c81fef..308209d70 100644 --- a/applications/main/infrared/resources/infrared/assets/digital_sign.ir +++ b/applications/main/infrared/resources/infrared/assets/digital_sign.ir @@ -1,8 +1,7 @@ Filetype: IR library file Version: 1 # -# Last Updated: 2nd October 2024 -# +# Last Updated: 29th Aug, 2025 name: POWER type: parsed protocol: NEC diff --git a/applications/main/infrared/resources/infrared/assets/fans.ir b/applications/main/infrared/resources/infrared/assets/fans.ir index d04e43b7d..7704a779f 100644 --- a/applications/main/infrared/resources/infrared/assets/fans.ir +++ b/applications/main/infrared/resources/infrared/assets/fans.ir @@ -1,7 +1,6 @@ Filetype: IR library file Version: 1 -# Last Updated 5th Oct, 2024 -# Last Checked 5th Oct, 2024 +# Last Updated 29th Aug, 2025 # name: Power type: raw diff --git a/applications/main/infrared/resources/infrared/assets/leds.ir b/applications/main/infrared/resources/infrared/assets/leds.ir index 7778356bf..3086521d0 100644 --- a/applications/main/infrared/resources/infrared/assets/leds.ir +++ b/applications/main/infrared/resources/infrared/assets/leds.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 -# Last Updated 5th Oct, 2024 -# Last Checked 5th Oct, 2024 +# Last Updated 7th Sept, 2025 +# Last Checked 7th Sept, 2025 # name: Power_on type: parsed @@ -2931,8 +2931,821 @@ protocol: NEC address: 00 00 00 00 command: 43 00 00 00 # +# Model: AIRUEEK Sonic_LED_Lamp +# name: Brightness_dn type: parsed protocol: NEC address: 00 00 00 00 command: 0C 00 00 00 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9227 4429 632 505 656 479 656 480 655 482 654 484 652 488 649 488 649 487 650 1612 626 1612 627 1612 627 1612 627 512 626 1612 626 1612 626 1612 627 512 627 512 627 1613 626 512 625 512 626 512 625 512 625 512 626 1613 625 1613 625 512 626 1614 626 1613 627 1613 626 1614 625 1613 624 40639 9235 2184 625 95445 9231 2184 625 95422 9250 2159 650 95420 9259 2159 651 95415 9248 2159 650 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9233 4403 685 479 658 479 657 480 656 482 654 485 651 486 651 486 651 486 651 1588 651 1587 652 1587 651 1587 651 487 650 1588 650 1588 650 1588 649 1588 649 487 649 1588 650 488 649 488 649 488 649 488 649 487 649 488 649 1589 648 488 649 1589 648 1589 649 1589 649 1590 648 1590 648 40649 9223 2160 649 95425 9235 2160 649 95430 9241 2159 650 95435 9236 2160 649 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9213 4427 633 504 633 504 658 479 658 481 655 486 650 511 626 512 626 512 625 1613 625 1613 626 1613 626 1613 626 512 626 1613 625 1613 625 1612 626 511 627 1612 626 1612 627 512 626 512 626 512 626 511 627 512 626 1612 628 512 627 489 649 1588 651 1589 649 1589 649 1589 650 1588 651 40647 9223 2160 650 95428 9248 2160 650 95434 9234 2160 649 95437 9240 2160 650 +# +# Model: Amazon LED_Lights +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9224 4425 632 503 632 504 656 479 656 481 653 484 651 510 625 511 625 511 625 1612 624 1612 624 1613 625 1613 626 513 624 1615 620 1642 597 1641 574 1665 598 1642 596 1642 571 565 571 565 571 565 571 565 571 565 571 565 571 565 571 565 571 1666 570 1666 571 1641 596 1641 596 1641 596 40681 9202 2182 624 95465 9213 2159 650 95436 9244 2159 650 95454 9225 2159 650 95459 9233 2160 650 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9029 4463 592 540 598 534 594 539 599 533 595 538 600 532 596 537 601 531 597 1642 593 1647 598 1640 595 1643 592 1647 598 1640 595 1644 601 1637 598 534 594 539 599 1639 596 1642 593 1646 599 532 596 1643 592 540 598 1641 594 1644 601 531 649 431 707 477 599 1641 594 538 600 1638 597 39704 9026 2222 598 95884 9034 2217 603 +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9223 4493 632 512 632 512 632 512 632 513 631 513 631 513 631 514 630 514 630 1629 629 1629 629 1630 628 1631 627 518 626 1632 626 1633 625 1634 624 1635 633 1625 633 1626 632 513 631 513 631 513 631 512 632 512 632 512 632 512 632 512 622 1635 623 1634 624 1634 624 1633 625 1632 626 41178 9189 2219 627 +# +# Model: Baltimore +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9239 4490 626 519 652 495 651 496 649 524 621 527 618 529 617 529 617 528 618 1642 618 1642 618 1642 618 1642 618 1643 617 1642 618 1642 618 1642 618 528 618 528 618 1642 618 1642 617 529 617 528 618 528 618 528 618 1642 617 1643 617 529 617 529 617 1642 618 1643 617 1643 617 1643 616 40036 9262 2218 618 96635 9259 2221 617 +# +# Model: BeamZ ShowBar_Laser +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9226 4494 624 521 624 522 648 498 647 526 618 528 618 528 618 528 618 528 618 1642 618 1642 617 1642 617 1642 617 1642 617 1642 618 1642 617 1642 617 1642 617 528 617 528 618 1642 617 528 618 528 617 528 618 528 618 528 617 1642 617 1642 618 528 618 1642 618 1642 617 1643 617 1642 618 40047 9252 2220 618 96654 9252 2221 617 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9245 4492 605 542 605 1676 605 542 605 1675 604 542 604 542 604 542 604 542 604 1675 604 542 629 517 628 519 626 1654 624 1655 624 1655 624 1655 623 1655 623 521 624 522 623 1655 623 522 623 521 624 521 623 522 623 521 624 1655 622 1655 623 522 622 1655 621 1655 622 1655 622 1655 622 40679 9209 2205 621 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9230 4483 659 487 658 1620 605 541 604 1674 604 541 604 540 605 540 605 540 605 1673 605 541 628 518 626 545 599 1680 598 1680 598 1680 598 1680 598 547 597 1680 598 547 597 1681 596 548 596 548 596 548 572 573 596 1682 571 574 595 1683 569 574 569 1732 544 1733 543 1733 543 1734 543 40712 9182 2224 597 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9202 4490 602 541 603 1675 602 541 603 1674 602 541 602 541 602 542 602 541 602 1674 602 541 602 542 626 518 625 1652 624 1654 623 1653 623 1653 623 1654 623 1653 623 520 623 1654 623 520 623 520 623 520 623 520 623 520 623 520 623 1654 622 520 623 1654 622 1653 623 1653 623 1653 623 +# +# Model: BeamZ Skynight +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9236 4438 652 517 627 1624 654 516 627 1649 603 541 602 541 602 540 627 516 627 1649 626 517 624 520 623 521 622 1654 622 1654 622 1654 622 1654 622 521 621 1654 622 521 621 1654 622 1654 622 521 622 521 621 522 621 1654 621 521 621 1654 621 521 621 521 621 1655 620 1655 620 1655 620 40677 9178 2202 621 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9222 4483 612 564 580 566 578 567 577 568 576 569 586 560 584 561 583 562 582 1666 613 1666 613 1667 612 1668 611 1669 610 1670 609 1671 608 1672 617 561 583 563 581 565 579 1670 609 1671 618 560 584 1665 614 564 580 1669 610 1671 618 1663 616 563 581 567 577 1672 617 561 583 1666 613 39760 9190 2248 609 96373 9190 2248 609 96376 9198 2242 615 96367 9198 2245 612 96366 9199 2241 616 96360 9194 2246 611 96367 9197 2244 613 96366 9190 2251 616 96362 9194 2248 609 96369 9197 2244 613 96365 9192 2249 608 96370 9199 2242 615 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9236 4456 639 507 637 508 606 540 604 541 603 542 603 543 601 543 601 544 611 1668 611 1667 612 1667 612 1667 612 1668 611 1667 612 1668 611 1668 663 1618 609 537 607 539 605 1674 605 1675 604 543 612 1668 611 536 608 539 605 1675 614 1666 613 535 609 538 606 1673 616 533 611 1667 612 39741 9202 2239 618 96334 9195 2244 613 96331 9198 2241 616 96330 9198 2244 613 96333 9196 2248 609 96334 9205 2240 617 96330 9200 2238 619 96325 9193 2247 610 96338 9200 2241 616 96339 9209 2248 609 96401 9199 2243 614 96340 9209 2240 617 96351 9208 2249 608 96360 9210 2250 669 96302 9205 2243 614 +# +# Model: 44 LED_Strip +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4082 4497 331 539 335 535 339 530 333 539 335 535 339 532 331 540 334 538 336 1609 378 1605 372 1609 378 1609 378 1607 380 1609 378 1634 343 1603 374 1639 338 534 340 1641 336 537 337 536 338 536 338 533 341 533 341 531 343 1636 341 499 343 1664 344 1635 342 1636 362 1618 338 1642 335 59711 13365 2274 275 98192 13430 2281 278 98226 13355 2284 276 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4070 4492 336 532 331 539 335 536 338 531 332 537 337 535 339 534 340 531 332 1648 339 1641 336 1645 332 1648 339 1641 336 1646 331 1653 334 1647 340 1643 334 539 335 539 335 1648 339 534 340 535 339 535 339 534 340 531 332 1649 338 1646 331 538 336 1646 331 1649 338 1642 335 1645 332 59768 13221 2279 280 98376 13242 2198 330 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4115 4504 324 582 281 538 336 535 339 530 333 537 337 505 369 502 372 531 332 1648 339 1643 334 1646 331 1650 337 1643 334 1649 338 1641 315 1689 179 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 201 8424 275 576 298 572 291 599 296 558 285 603 271 599 275 580 294 596 278 1684 293 3677 277 1711 276 1691 286 1640 337 1639 348 1635 342 546 338 1698 279 585 299 1636 341 545 339 537 347 541 353 509 344 1635 404 536 348 1638 349 539 376 1638 328 79436 203 3212 773 4525 282 548 357 506 357 530 354 542 342 506 409 515 411 587 276 545 349 1677 289 1690 266 1709 268 1710 267 1714 263 1717 291 1693 263 1718 269 607 267 1722 265 613 172 1814 261 616 268 607 267 612 262 615 259 1721 297 579 263 1720 267 606 268 1715 272 1714 263 1723 264 1720 267 +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 175 8455 280 593 301 565 288 585 278 583 270 597 297 576 298 563 181 1065 303 5651 280 3674 301 1698 279 7614 346 532 342 1710 277 539 345 535 339 539 345 584 279 598 276 2584 308 567 275 3701 294 1688 278 1709 278 59644 176 11236 179 1178 174 4633 263 97280 248 10633 4435 2254 253 +# +# Model: DMX Light +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9362 4515 612 541 613 540 614 538 606 547 607 545 609 544 610 542 612 1652 615 1648 609 1682 585 1679 577 1685 582 1682 585 1679 588 1676 580 544 610 542 612 1651 606 547 607 1655 612 541 613 539 615 538 616 540 614 1651 616 538 606 1659 608 546 608 1656 611 1654 613 1651 606 1659 608 40015 9283 2235 604 96515 9276 2238 611 96503 9299 2232 607 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9417 4514 613 545 609 548 616 541 613 543 611 546 608 548 616 541 613 1657 620 1677 590 1651 616 1653 614 1654 613 1655 612 1655 612 1655 612 544 610 1656 611 1684 583 545 609 1657 610 1684 583 546 608 548 616 540 614 546 608 547 607 1659 618 539 615 542 612 1655 612 1683 584 1657 610 40015 9385 2240 620 96496 9356 2233 616 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9421 4508 619 541 613 545 609 547 607 549 615 539 615 540 614 540 614 1652 615 1654 613 1681 586 1681 586 1680 587 1678 589 1677 579 1685 582 545 609 1654 613 1677 580 1682 585 1678 579 1656 611 539 605 546 608 542 602 548 606 543 611 538 606 544 610 540 604 1656 611 1650 607 1682 585 40013 9274 2239 610 96511 9281 2234 605 +# +# Model: LED 44Key +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9419 4510 617 544 620 539 615 545 619 540 614 546 618 539 615 541 613 1654 613 1653 613 1652 615 1650 606 1657 610 1654 613 1650 606 1659 608 546 608 547 617 538 616 541 613 544 610 547 617 538 616 541 613 544 610 1659 618 1650 617 1650 617 1649 607 1658 609 1656 611 1656 611 1655 612 39870 9281 2236 613 96484 9266 2234 605 96493 9287 2236 613 96178 9362 2238 611 +# +# Model: LED Cat +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9200 4477 611 556 573 565 575 535 605 532 597 539 601 535 605 531 598 538 602 1636 602 1638 600 1640 598 1641 607 556 573 1639 599 1640 608 1631 607 557 572 1639 599 1641 607 556 573 536 604 533 607 529 600 536 604 1635 602 561 579 531 598 1641 607 1632 606 1633 605 1635 603 1636 602 40899 9139 2210 608 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9752 4512 678 519 629 574 636 569 631 571 629 572 628 572 628 571 629 573 627 1689 629 1683 625 1685 623 1686 622 1687 621 1688 630 1678 619 1686 622 1683 625 1681 627 567 623 575 625 571 619 576 624 572 628 1679 629 567 623 573 627 1681 627 1677 620 1686 622 1680 617 1681 616 571 619 39161 9413 2260 622 96457 9493 2267 615 96491 9491 2260 622 99522 2889 75674 9757 4519 630 573 627 578 632 572 628 578 632 573 627 578 632 572 628 575 635 1675 622 1676 621 1677 610 1687 621 1677 610 1687 621 1678 609 1688 620 1683 614 1687 631 568 621 575 625 577 633 568 632 569 631 1682 626 573 627 573 627 1684 624 1687 631 1677 631 1678 630 1680 628 569 631 39165 9531 2265 617 97240 9673 2263 629 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9526 4512 627 568 622 572 628 567 623 571 619 575 625 568 622 570 620 571 619 1683 625 1678 619 1682 626 1676 621 1681 627 1675 623 1679 619 1683 625 565 625 1674 623 596 593 595 595 594 596 594 596 593 586 1685 623 1677 621 596 594 1677 621 1679 618 1681 616 1682 615 1683 614 600 590 39182 9434 2264 618 96516 9414 2260 612 96510 9460 2258 614 96482 9561 2269 634 96516 9856 2270 633 96479 9862 2271 642 96516 9898 2271 642 96486 9876 2273 691 96432 9879 2265 638 96485 9866 2273 640 96480 9858 2266 637 96530 9891 2271 642 96726 9890 2271 642 96496 9884 2271 642 96493 9886 2265 638 96502 9888 2267 646 96494 9886 2267 646 96494 9898 2268 645 96500 9911 2271 642 96513 9921 2266 647 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9512 4513 626 592 587 599 591 596 594 592 587 598 592 594 585 600 590 595 584 1684 624 1676 622 1677 621 1675 612 1682 616 1679 608 1684 614 1680 607 601 589 564 615 564 615 565 614 565 614 565 614 593 586 1676 611 1681 606 1686 612 1680 607 1685 612 1680 607 1685 613 1681 616 567 612 39181 9353 2262 610 96521 9346 2265 607 96503 9407 2263 609 96555 9468 2267 615 96789 10035 2284 639 98340 9868 2281 642 96680 10006 2271 642 96640 10007 2278 645 96746 10013 2277 646 96773 10027 2281 642 96844 10049 2275 648 96842 10063 2279 644 96641 9494 2270 622 96592 9459 2262 620 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 10190 4555 656 579 652 583 648 587 654 581 650 584 647 586 655 578 653 581 649 1701 658 1695 654 1699 650 1702 647 1704 655 1693 645 1703 656 1694 655 1698 651 586 655 583 658 581 650 589 652 584 657 579 652 1700 649 586 655 1699 650 1704 655 1698 651 1703 656 1699 650 1705 654 585 656 39849 10069 2284 650 54703 10223 4549 651 586 655 584 657 581 660 581 660 581 660 581 660 581 660 581 660 1699 660 1699 660 1701 658 1704 655 1706 653 1708 651 1708 661 1699 660 1701 658 584 657 585 656 587 654 588 653 588 653 588 653 1706 653 588 653 1705 654 1705 654 1705 654 1703 656 1701 658 1705 654 586 655 63890 10114 4552 649 588 653 584 657 580 651 586 655 582 649 588 653 583 658 579 651 1705 654 1706 653 1708 651 1706 653 1708 651 1709 650 1711 648 1710 649 1714 655 583 648 587 654 581 650 585 645 589 652 585 707 1652 656 583 658 1701 658 1701 658 1699 660 1699 650 1708 651 1707 652 586 655 39618 10155 2286 658 97127 10219 2288 656 97523 9965 2290 644 96905 10101 2281 653 97091 10070 2285 648 97100 10070 2293 651 97024 10167 2284 650 97065 10218 2285 659 97001 10200 2289 655 96997 10141 2284 650 97121 9976 2292 641 97312 10070 2289 644 97238 10094 2289 644 97251 10195 2288 656 97168 10270 2227 655 96954 10206 2282 651 96927 10190 2289 655 96922 10159 2283 651 96890 10159 2285 649 96941 10230 2283 651 96897 10119 2281 652 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9643 4513 626 570 630 567 633 566 634 567 633 597 603 601 599 577 633 574 626 1680 617 1684 613 1687 631 1683 635 1681 627 1679 618 1681 627 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2277 135 6297 131 648 31215 9397 13890 9345 4513 616 566 613 566 613 567 612 568 611 569 610 569 610 570 609 572 607 1682 615 1680 618 1676 611 1678 609 1682 615 1675 612 1679 608 1682 615 1676 611 568 611 1676 611 570 609 571 608 574 615 565 614 1675 612 566 613 1675 612 566 613 1678 609 1682 615 1676 611 1679 608 572 607 60519 9399 4514 604 572 607 569 610 566 613 564 605 572 607 569 610 567 612 564 605 1681 606 1684 613 1674 613 1675 448 1852 610 1678 609 1681 647 1646 621 1678 609 573 606 1682 615 565 501 682 610 569 610 569 610 1680 607 573 606 1683 614 565 583 1705 418 84 177 1618 613 1678 619 1681 616 566 613 39179 621 499 3912 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 7235 513 882 81 483 106 335 4464 624 568 642 550 619 570 609 573 616 567 622 568 622 568 621 571 629 1675 622 1679 629 1679 629 1685 623 1684 624 1676 621 1679 629 1678 619 576 624 1677 620 1682 615 573 616 570 619 569 621 567 612 1682 615 1681 668 520 618 565 624 1678 619 1676 621 1683 625 1679 618 572 607 39193 9276 2260 612 96574 9516 2265 617 +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9300 4515 603 574 605 573 606 571 608 570 609 567 602 574 605 571 608 567 602 1687 610 1678 609 1680 607 1682 605 1685 612 1678 609 1680 607 1682 605 1684 613 1677 610 1680 607 571 608 569 610 567 612 565 614 1676 611 567 612 564 605 572 607 1681 606 1683 604 1684 603 1686 612 564 605 39200 9291 2258 604 96551 9262 2259 613 96529 9264 2259 613 96522 9284 2257 605 96540 9285 2264 608 96536 9286 2266 606 +# +# Model: Magic Lighting_Remote +# +# Model: RGB +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9054 4465 597 566 571 537 600 535 602 532 595 567 570 538 599 535 602 533 594 1649 595 1649 595 1649 595 1649 678 1566 594 1650 594 1650 594 1650 594 1651 603 559 568 541 596 1646 598 564 573 536 601 534 593 568 569 539 598 1644 600 1643 601 561 576 1640 604 1640 604 1641 603 1640 604 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9046 4474 599 564 573 535 602 533 594 541 596 539 598 537 600 535 602 532 595 1649 595 1649 678 1567 604 1641 603 1642 602 1643 601 1644 600 1645 599 564 573 536 601 534 593 1651 603 559 568 541 596 539 598 537 600 1643 601 1645 599 1645 599 564 573 1644 600 1645 599 1647 597 1648 596 39826 9048 2232 596 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9417 4469 615 538 617 534 610 540 615 537 607 543 612 539 616 535 609 541 614 1639 609 1670 588 1664 584 1668 580 545 610 1670 588 1637 611 1640 608 543 612 540 615 538 606 546 609 541 614 538 617 536 608 545 610 1670 588 1665 583 1643 615 1641 617 1635 613 1638 610 1670 588 1637 611 40788 9415 2218 618 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9403 4480 614 543 612 545 609 545 609 544 610 544 611 543 612 541 614 539 616 1637 611 1643 615 1666 582 1672 586 537 607 1646 612 1641 617 1636 612 1641 607 543 612 538 606 544 611 539 616 535 609 541 614 536 608 542 613 1641 607 1645 613 1640 608 1645 613 1639 609 1644 614 1639 609 40849 9390 2218 607 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9408 4476 618 560 584 567 587 563 581 569 586 565 590 562 582 568 587 565 590 1637 611 1643 615 1639 609 1646 612 566 588 1638 610 1644 614 1641 617 535 609 570 585 1642 616 536 608 543 612 540 615 537 618 534 610 1644 614 1641 617 563 581 1645 613 1642 616 1639 609 1647 611 1644 614 40816 9393 2219 616 95827 9399 2216 609 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9397 4479 615 538 617 536 608 544 611 541 613 538 616 536 608 543 612 539 616 1638 610 1644 614 1640 608 1646 612 539 615 1638 609 1647 611 1672 586 1670 588 536 608 1645 613 539 616 537 607 544 610 541 614 539 616 536 608 1645 613 540 614 1639 609 1646 612 1642 616 1637 611 1644 614 40776 9391 2225 610 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9392 4479 615 535 609 540 615 536 608 542 612 537 607 543 612 539 616 535 609 1644 614 1639 609 1644 614 1639 609 541 614 1640 608 1645 613 1640 608 542 613 1640 608 1645 613 536 608 541 614 536 608 542 613 537 607 1646 612 536 608 541 614 1640 608 1645 613 1639 609 1644 614 1639 609 40856 9373 2226 609 +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 10023 4480 634 546 629 547 628 543 622 547 628 545 630 538 627 538 616 543 622 1639 619 1642 626 1638 620 1640 618 568 586 1644 624 1639 629 1642 647 1644 645 1648 630 1646 632 569 596 573 602 567 598 571 594 567 587 540 614 540 614 540 625 1637 621 1644 624 1640 618 1646 622 1643 625 40664 9594 2221 624 +# +# Model: Briloner Ceiling_Light +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9229 4346 717 457 636 513 634 515 633 516 633 516 633 517 632 517 632 520 632 1630 632 1630 632 1630 631 1630 632 1630 631 1630 632 1630 631 1634 631 1630 632 517 632 517 632 1630 631 517 632 517 631 517 632 521 631 518 631 1630 631 1630 631 518 630 1631 630 1631 630 1631 630 1631 630 39733 9131 2189 631 96229 9149 2187 633 96282 9165 2184 634 95966 9160 2186 634 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4511 4460 576 551 576 551 576 1655 575 1654 576 1654 576 551 576 1655 575 551 576 1653 577 1655 575 551 576 551 576 1655 575 1654 576 1655 575 551 576 552 575 551 576 551 576 552 575 551 576 552 575 551 576 551 576 1655 575 1655 575 1654 576 1655 576 551 576 552 575 1655 575 1655 575 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4511 4462 597 1632 598 530 597 530 597 529 598 1632 598 529 598 1633 597 530 597 1632 598 1633 597 529 598 529 598 1632 598 1633 597 1633 597 530 597 529 598 530 597 530 597 530 597 530 597 529 597 530 597 530 597 530 597 529 598 1633 597 530 597 530 597 529 598 1633 596 1632 597 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4482 4493 566 560 567 1663 567 559 568 1664 566 1663 567 559 568 1663 567 559 568 1663 567 1663 568 561 566 560 567 560 567 1662 568 1664 566 559 568 1664 567 560 567 560 567 560 567 559 568 559 568 560 567 561 566 561 566 1664 566 1662 568 1662 568 1662 568 1664 566 560 567 1663 567 +# +# Model: Cadrim Puck_Lights +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4482 4492 567 560 567 1663 568 559 568 1664 566 1662 568 560 567 1662 568 560 567 1663 567 1664 566 560 567 559 568 1663 567 1663 568 1663 567 560 567 1663 567 559 568 560 567 560 567 560 567 560 567 560 567 560 567 560 567 1662 568 1663 567 1664 566 560 567 560 567 1663 567 1664 566 +# +# Model: CLPFLAT1REMOTE +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9139 4357 677 451 677 1567 702 426 702 1543 702 427 701 450 677 451 676 453 674 1570 674 454 674 455 673 455 673 1572 673 1571 673 1571 673 1571 673 1571 673 455 673 455 673 1572 673 455 673 455 673 455 673 455 673 455 673 1572 673 1572 672 456 672 1572 673 1572 672 1572 673 1572 673 40605 9112 2134 673 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9152 4345 707 420 708 1536 733 394 734 1510 734 395 706 421 707 422 705 423 704 1564 679 448 679 449 678 450 677 1567 677 1568 676 1569 675 1569 675 452 676 1569 675 453 675 1569 675 453 675 452 676 453 675 453 675 1569 675 453 675 1569 675 453 675 1570 674 1569 675 1570 674 1570 674 40596 9119 2125 676 95865 9137 2131 674 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9124 4375 679 449 679 1566 679 449 679 1567 678 451 677 475 676 451 678 450 678 1568 676 452 676 452 676 453 675 1570 675 1570 675 1570 675 1570 675 1570 675 1570 675 453 675 1570 674 453 675 453 675 453 675 453 675 453 675 453 675 1570 675 454 674 1570 675 1570 675 1571 674 1571 674 40606 9122 2126 676 95916 9118 2129 676 +# +# Model: COM-T070 +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9127 4377 679 449 680 1567 678 475 653 1592 654 475 653 475 653 476 677 451 677 1569 677 452 676 453 676 453 676 1570 676 1571 675 1571 675 1571 675 453 676 1571 675 453 675 1571 675 1571 675 453 675 453 675 453 676 1571 675 454 675 1571 675 454 675 454 675 1571 675 1571 675 1571 675 40549 9125 2129 676 95891 9120 2129 675 +# +name: Power_off +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 00 FF 00 00 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9219 4556 604 549 609 545 602 551 607 546 601 551 607 546 601 552 606 547 600 1675 610 1693 581 1667 607 1669 605 548 610 1666 608 1668 606 1669 606 547 611 543 604 548 610 543 604 548 610 544 603 549 609 544 603 1672 603 1673 602 1674 601 1676 609 1666 608 1668 606 1669 605 1671 603 41545 9245 2262 602 97493 9216 2260 604 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9217 4542 605 543 604 545 602 547 600 549 598 551 606 542 605 544 603 546 601 1667 596 1670 604 1665 598 1669 605 544 603 1665 598 1669 605 1663 600 548 599 549 598 1670 603 545 602 547 600 548 599 549 608 541 606 1661 602 1666 607 541 606 1661 602 1666 597 1670 604 1664 599 1668 606 41407 9212 2254 598 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9207 4546 601 548 599 550 607 541 606 541 606 542 605 545 602 549 608 542 605 1665 608 1663 600 1670 603 1667 606 543 604 1667 607 1664 599 1671 602 1668 606 545 602 1668 605 545 602 548 609 541 606 545 602 548 609 541 606 1665 608 542 605 1666 607 1664 609 1662 601 1670 603 1668 605 +# +# Model: Dasinko LED +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9213 4539 608 541 606 543 604 544 603 546 601 548 599 551 606 543 604 545 602 1667 606 1663 600 1669 604 1665 609 541 606 1663 600 1669 605 1665 609 568 579 1664 599 1671 603 574 573 550 607 543 604 546 601 549 608 1661 602 575 572 578 579 1664 599 1671 603 1668 605 1665 608 1662 601 41472 9215 2289 636 +# +# Model: Deluxe Homeart_FB-00001 +# +name: White +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 0e 00 00 00 +# +name: Power_on +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 5E 00 00 00 +# +name: Power_off +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0C 00 00 00 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9819 4494 610 570 607 566 612 567 610 567 610 568 609 568 609 568 609 569 607 1677 609 1677 608 1677 608 1677 607 568 608 1677 608 1678 606 1676 609 568 608 569 607 569 606 568 608 567 609 569 607 568 607 570 603 1677 606 1678 606 1677 604 1677 605 1679 603 1677 605 1680 602 1678 604 41202 9733 2226 608 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9843 4498 609 568 612 568 612 568 612 570 609 569 610 569 610 569 609 569 609 1677 610 1678 609 1678 609 1677 610 567 611 1677 609 1677 609 1678 608 1678 608 568 610 567 610 569 608 568 609 570 607 568 609 569 608 568 609 1677 609 1677 609 1678 608 1678 608 1678 607 1679 607 1677 608 41183 9760 2227 607 +# +# Model: Globo Connor_Ceiling_LED_Lamp +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9694 4504 600 568 603 568 603 570 601 569 602 568 604 570 602 568 604 570 602 1679 601 1680 599 1680 600 1678 605 569 605 1680 600 1681 600 1682 598 1679 602 1679 601 1680 600 570 601 568 604 569 601 567 602 569 600 569 601 568 602 569 601 1678 602 1680 600 1681 600 1679 605 1681 601 41241 9662 2228 603 +# +# Model: Govee Striplights +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4802 4475 622 538 629 1672 600 534 623 1652 620 1653 619 543 624 1650 622 541 626 539 628 1647 625 538 629 536 631 1644 628 1645 627 1646 626 537 630 536 631 1644 628 535 632 534 633 533 634 533 634 532 625 542 625 542 625 1651 621 1653 630 1644 628 536 631 535 632 1645 627 1646 626 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9205 4358 644 516 645 516 645 516 674 488 672 490 670 491 669 515 645 516 644 1591 642 1592 641 1594 639 1596 638 1596 638 1597 637 1597 637 1597 637 523 638 523 638 523 637 1597 637 523 637 524 637 523 638 523 637 1597 637 1598 636 1598 636 524 637 1598 636 1623 611 1623 611 1623 611 39226 9175 2164 637 +# +# Model: HGomx Saturn_LED_Lamp +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9264 4384 673 493 670 493 669 496 668 519 644 497 666 519 644 520 643 522 642 1596 641 1596 640 1596 641 1596 640 1596 641 1597 641 1597 640 1596 641 523 641 1596 640 523 639 1596 640 523 639 523 640 1596 640 523 640 1597 639 523 639 1597 639 523 640 1597 640 1597 639 523 640 1597 639 39232 9209 2161 640 +# +# Model: Jialine +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8984 4436 676 448 676 450 674 452 672 450 674 448 676 448 676 475 649 449 676 1572 675 1599 648 1575 673 1570 677 1571 676 1573 675 1572 675 1570 677 1571 676 447 677 447 677 1569 678 449 675 448 676 448 676 448 676 447 677 1572 675 1571 676 447 677 1570 677 1598 648 1572 675 1572 675 39202 8955 2242 674 95722 8955 2240 602 95786 8979 2213 603 95785 8979 2213 603 95779 8980 2213 603 95774 8982 2214 602 +# +# Model: KODA LM030008-1 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9196 4413 680 483 680 457 680 457 680 458 678 460 677 461 676 462 676 462 676 1573 676 1573 676 1573 676 1573 676 463 675 1574 675 1574 675 1575 674 464 674 488 650 488 650 488 650 488 650 489 649 489 649 488 650 1599 650 1600 649 1600 649 1600 648 1601 648 1601 648 1601 648 1601 648 41005 9197 2172 649 96318 9198 2172 649 96322 9196 2146 674 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9203 4419 676 461 678 462 703 436 674 464 700 437 678 461 677 462 676 461 677 1574 676 1573 677 1574 675 1573 676 462 675 1576 675 1576 673 1577 674 1577 672 463 673 467 701 463 647 467 676 487 649 464 674 461 678 465 673 1576 672 1578 673 1575 676 1574 676 1573 676 1575 675 1573 676 41040 9166 2200 648 96372 9202 2172 649 96351 9211 2143 678 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9168 4442 654 483 654 483 680 458 679 459 678 461 676 487 650 488 650 488 649 1599 650 1599 649 1599 650 1599 650 488 649 1599 650 1599 649 1599 650 488 650 489 649 1599 650 488 649 489 649 488 649 489 649 489 649 1599 649 1599 649 489 649 1600 648 1599 649 1599 649 1600 649 1599 649 41008 9188 2172 649 96336 9189 2172 649 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9327 4434 654 482 655 483 653 483 680 458 679 459 678 463 674 488 649 488 650 1599 648 1599 648 1599 648 1599 648 489 648 1599 648 1599 648 1599 650 1599 648 489 648 1600 648 489 648 489 648 490 648 489 648 489 648 489 648 1600 647 490 647 1600 647 1600 647 1600 647 1600 648 1600 648 40983 9183 2171 648 96240 9276 2144 679 96193 9354 2145 678 96222 9298 2144 679 96215 9214 2142 675 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9223 4384 709 455 682 455 682 456 681 457 679 458 679 460 677 461 677 461 677 1572 677 1572 676 1573 676 1573 676 462 676 1572 676 1573 676 1572 676 462 676 1572 676 1572 677 462 676 461 677 462 676 462 676 462 677 1573 677 462 677 462 677 1573 677 1573 676 1573 676 1573 676 1573 676 40987 9201 2144 675 +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9350 4358 716 455 687 456 686 457 685 458 683 460 681 462 680 464 679 464 679 1575 679 1575 679 1575 679 1575 679 464 679 1575 679 1575 679 1575 679 1575 679 1575 679 1575 680 464 679 464 679 464 679 464 679 464 679 464 680 464 679 464 679 1576 679 1575 680 1576 679 1576 678 1576 679 41014 9273 2146 679 +# +# Model: Lethe RGB_LED_full +# +name: Brightness_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 19 00 00 00 +# +# Model: MonsterIlluminessence LED +# +name: Brightness_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 38 00 00 00 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9426 4531 610 549 611 549 611 520 639 548 611 522 638 549 611 549 610 549 610 1664 611 1664 611 1664 612 1664 611 1638 638 1665 612 1665 613 1665 611 549 611 521 639 1665 610 549 610 549 610 549 610 1639 635 549 610 1665 609 1637 638 549 610 1665 609 1665 610 1665 609 549 610 1665 609 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9410 4536 607 552 608 551 608 552 607 552 608 522 637 552 607 552 607 552 607 1668 607 1668 607 1639 636 1668 607 1668 607 1668 607 1668 606 1641 634 552 607 525 633 524 635 552 606 522 636 552 607 1640 634 552 606 1642 633 1668 606 1641 633 1641 633 1639 635 1668 606 552 607 1668 606 40168 9388 2251 606 +# +# Model: Monster LED_Touch_Light +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9384 4536 605 522 636 552 606 553 605 552 606 552 606 552 606 553 605 552 606 1668 606 1669 605 1668 605 1668 606 1668 606 1668 606 1668 605 1669 604 1668 606 1668 606 553 605 552 606 552 606 552 606 1668 606 552 606 524 634 552 606 1639 635 1668 606 1668 606 1668 606 552 606 1668 606 40131 9422 2222 635 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8959 4447 556 543 556 492 581 1647 494 604 557 1645 557 1619 583 1646 556 1646 556 1645 567 1635 556 543 556 1619 583 493 581 543 557 543 557 491 583 10569 8962 4426 578 542 557 492 582 1647 494 604 557 1645 557 1645 557 1618 584 1645 557 1619 522 1707 556 543 557 1646 556 517 557 543 557 543 556 518 556 10571 8961 4449 556 543 556 517 557 1648 494 604 556 1646 556 1646 556 1646 556 1646 556 1646 495 1707 555 543 556 1647 555 517 556 544 556 543 556 518 556 10572 8960 4452 554 544 555 519 555 1650 493 604 555 1648 554 1647 555 1648 554 1647 555 1622 521 1707 555 544 556 1648 554 519 554 519 581 544 555 519 555 10584 8958 4452 555 544 555 495 579 1650 493 604 555 1647 555 1647 555 1648 555 1647 555 1647 495 1707 555 520 579 1624 578 519 555 545 555 544 555 519 555 11766 8957 4453 555 518 581 545 551 1627 493 604 555 1647 555 1648 555 1647 555 1647 575 1628 494 1707 555 520 579 1647 555 544 555 519 555 545 554 519 555 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8960 4423 580 542 557 1621 581 517 574 1654 553 1623 568 1660 557 1646 556 1645 556 1647 556 494 579 1646 556 543 556 517 557 543 557 543 556 517 557 10574 8959 4426 579 520 579 1649 553 522 567 1659 547 1630 494 1734 552 1650 552 1649 552 1651 552 521 552 1624 579 547 552 522 552 547 554 546 553 521 553 10580 8955 4427 581 546 553 1649 553 502 587 1660 547 1608 516 1733 553 1650 552 1650 551 1651 552 499 574 1623 579 521 578 521 553 547 553 546 553 520 554 10579 8956 4454 553 546 553 1649 553 523 565 1661 547 1631 493 1733 553 1624 578 1649 552 1651 553 521 552 1650 553 546 553 522 552 523 578 547 552 499 575 10580 8956 4455 552 547 552 1651 551 525 566 1660 545 1631 494 1734 551 1651 551 1651 550 1652 551 522 551 1651 551 548 551 523 551 548 552 548 551 522 552 11769 8955 4456 551 548 552 1651 551 525 516 1710 546 1657 551 1651 551 1652 550 1651 551 1651 551 523 550 1652 576 523 551 523 576 523 551 549 551 522 552 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8961 4448 555 1647 555 1647 554 546 555 1647 555 1647 554 1648 554 1648 553 1649 550 549 554 519 549 1654 551 548 554 518 525 576 552 548 554 519 552 10578 8963 4449 555 1647 555 1647 555 544 557 1646 556 1646 556 1646 556 1645 557 1646 556 543 557 517 557 1645 558 543 557 494 580 542 558 543 557 490 584 10583 8961 4452 553 1650 552 1651 551 548 552 1650 552 1651 552 1650 552 1650 552 1651 551 548 552 522 551 1651 552 548 551 523 551 549 551 548 552 523 551 11769 8953 4458 551 1653 493 1708 550 549 551 1652 550 1651 551 1652 550 1630 570 1654 550 521 579 523 550 1652 576 523 551 549 551 523 551 549 551 523 551 +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8944 4460 549 1627 575 523 576 525 549 525 574 1653 549 1629 573 1652 550 1625 577 526 574 1652 549 1653 549 1652 550 525 548 551 549 551 549 524 549 10577 8947 4436 573 1624 578 551 548 528 547 525 573 1629 573 1655 547 1628 573 1654 548 553 547 1654 547 1655 547 1656 523 550 523 576 524 576 524 549 524 10603 8923 4459 549 1678 524 575 525 550 523 550 550 1677 525 1678 524 1677 525 1677 525 575 525 1676 525 1677 525 1677 525 549 524 575 525 574 526 548 525 10602 8925 4484 525 1676 526 573 527 548 524 547 554 1676 526 1676 526 1676 526 1676 526 574 526 1675 527 1676 526 1675 527 548 525 573 527 573 527 546 527 11793 8927 4482 526 1676 526 546 554 547 525 574 527 1675 527 1675 527 1649 553 1652 571 551 527 1675 527 1676 526 1675 526 572 528 546 527 547 553 522 551 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8969 4417 584 1644 556 1621 579 1644 556 516 583 1616 584 542 554 490 585 542 557 516 557 542 556 516 495 1732 551 521 557 1644 557 1616 523 1706 556 10571 8945 4418 583 1645 556 1619 582 1644 556 542 557 1645 556 542 555 518 557 543 556 517 556 543 556 493 518 1732 552 521 556 1620 581 1645 494 1706 556 10572 8948 4446 556 1644 557 1645 556 1645 556 542 557 1644 557 543 553 519 556 542 557 517 556 542 557 517 495 1732 551 522 556 1620 581 1645 494 1706 556 10573 8947 4446 557 1645 556 1645 556 1645 556 543 556 1645 556 543 553 519 557 542 557 517 557 542 556 518 494 1733 550 522 556 1645 556 1645 495 1707 556 10562 8946 4421 581 1644 557 1645 556 1624 577 517 582 1644 557 542 554 519 556 517 581 517 556 542 556 517 495 1732 551 522 556 1645 556 1645 495 1706 556 11754 8947 4446 555 1645 568 1633 555 1624 577 542 557 1645 556 543 553 519 556 543 556 517 556 515 584 543 555 1645 552 521 556 1646 572 1628 494 1707 555 +# +# Model: Neuhaus Leuchten_Direkt_sparkle_LED +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8956 4453 552 522 578 1648 553 1625 577 1623 578 1623 578 546 551 523 553 547 552 1624 577 521 553 547 553 547 547 527 553 1649 553 1649 552 1650 552 10576 8958 4454 553 547 553 1624 578 1623 579 1649 552 1650 552 547 550 523 553 548 551 1650 552 521 553 523 577 547 546 527 553 1650 552 1650 551 1650 552 10578 8959 4432 575 548 552 1650 551 1650 552 1650 552 1651 551 548 549 524 552 548 551 1651 551 522 552 548 552 548 545 528 552 1624 578 1651 551 1625 577 11762 8957 4457 551 548 552 1651 551 1651 551 1650 552 1627 575 548 549 525 551 548 552 1652 550 549 493 581 551 525 568 529 552 1651 519 1685 549 1652 550 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9266 4443 660 533 632 537 656 510 628 537 627 536 628 536 627 537 625 538 624 1665 623 1668 621 1670 620 1670 620 1671 619 1670 618 1671 617 1670 618 544 617 544 617 544 618 1671 617 1671 617 544 617 1671 617 544 617 1671 617 1671 617 1671 617 544 617 544 617 1671 617 544 617 1671 615 39236 9118 2204 588 95940 9137 2203 588 +# +# Model: Northerns Light_Chain +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9251 4440 662 532 629 535 652 509 626 535 626 535 626 535 626 536 625 537 623 1663 623 1665 621 1667 619 1668 619 1668 618 1668 618 1669 618 1668 618 1668 618 542 618 1668 618 1668 618 1668 618 542 618 1669 617 542 618 542 618 1669 617 542 617 542 617 543 616 1669 616 542 617 1668 616 39216 9090 2201 589 +# +# Model: Oremi sunset_lamp +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9243 4423 667 527 639 1584 664 528 637 529 637 531 635 533 633 535 632 536 631 1618 631 536 631 1617 632 1618 631 1618 631 1618 631 1618 631 1618 631 536 631 536 631 536 631 1618 631 536 631 536 631 536 631 536 631 1618 631 1618 631 1618 631 536 631 1618 631 1618 631 1618 631 1618 631 39535 9206 2189 631 +# +name: Power_on +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 03 FC 00 00 +# +name: Power_off +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 02 FD 00 00 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9340 4367 678 463 678 463 678 464 677 1585 678 466 675 466 674 466 675 468 673 1591 672 1615 647 1616 647 494 646 1617 645 1618 645 496 644 1618 645 496 645 496 645 496 645 496 645 496 644 496 645 496 645 496 645 1618 646 1618 645 1618 645 1618 645 1618 645 1618 644 1618 645 1618 644 41527 9284 2180 644 +# +# Model: Osram 24_Button_LED +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9255 4394 647 491 646 491 674 463 675 1583 678 461 676 462 675 464 698 463 649 1585 674 1586 673 1611 648 489 647 1613 645 1614 644 493 644 1616 643 494 643 1617 642 495 642 495 642 495 642 495 642 495 642 495 642 1617 643 495 642 1617 642 1618 641 1617 642 1618 641 1618 641 1618 642 41518 9251 2179 643 +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9539 4502 619 546 623 543 626 539 620 546 623 542 627 538 621 545 624 542 627 1648 628 1647 629 1647 629 1646 619 1656 620 1655 621 1655 621 1654 622 1654 622 1654 622 544 678 488 629 538 621 545 624 542 627 538 621 546 623 542 627 1648 628 1648 628 1647 629 1647 629 1647 629 1646 630 40018 9544 2234 621 96390 9543 2233 622 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9652 4502 631 543 627 546 624 548 632 539 630 541 629 542 628 542 628 543 626 1650 626 1652 624 1655 632 1647 682 1597 627 1652 624 1655 684 1595 681 491 626 545 625 548 632 1675 601 546 634 541 629 546 634 540 630 1651 636 1646 630 1652 635 541 628 1653 634 1648 628 1655 632 1651 636 40044 9715 2231 636 96521 9670 2238 628 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9738 4507 636 542 638 538 631 545 635 541 629 548 632 544 636 540 629 547 633 1650 637 1648 639 1646 630 1655 632 1653 634 1652 635 1650 637 1648 639 1647 640 538 631 545 635 1650 637 541 639 538 631 546 634 543 637 540 629 1655 632 1654 633 546 634 1651 636 1650 637 1650 637 1650 637 40020 9719 2230 636 96379 9708 2238 629 +# +# Model: Panasonic HK9494_LED +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9724 4507 636 542 638 540 629 547 633 544 636 541 628 548 632 544 636 540 630 1654 633 1681 606 1651 636 1648 628 1656 631 1654 633 1652 635 1649 638 539 630 1653 634 543 637 1647 629 547 633 544 636 540 629 546 634 1649 638 539 630 1653 634 543 637 1647 629 1685 602 1683 604 1652 635 40012 9716 2232 635 +# +# Model: Panasonic PBUM11070_RGB_Bulb_7w +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3481 1749 452 416 453 418 452 1290 452 1290 452 419 451 1289 453 418 452 417 452 419 451 1290 452 417 452 419 451 1290 452 418 452 1291 451 417 453 1290 452 417 452 418 452 1289 452 417 452 418 451 416 453 418 451 416 454 1289 452 417 453 1289 453 418 451 1289 453 417 452 418 452 1291 451 1289 453 417 452 418 452 418 452 1290 452 417 452 419 450 75017 3484 1751 451 417 453 418 452 1291 452 1290 452 418 452 1290 452 419 451 418 452 418 452 1291 452 418 451 419 451 1290 452 419 451 1290 452 418 452 1289 453 418 452 417 453 1289 453 418 451 418 452 417 453 418 452 418 452 1290 452 419 451 1291 451 418 452 1290 452 418 452 419 451 1291 452 1289 453 417 453 417 453 417 453 1291 451 418 452 418 451 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1400 1400 700 700 700 1400 700 2800 700 2100 1400 700 700 700 700 1400 1400 2800 1400 2800 700 +# +# Model: PixMob main +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 700 700 700 2100 1400 700 700 2800 700 1400 700 700 700 1400 1400 700 700 1400 700 700 700 700 700 700 700 2100 700 +# +name: Red +type: parsed +protocol: NECext +address: 0A F1 00 00 +command: 09 F6 00 00 +# +name: Green +type: parsed +protocol: NECext +address: 0A F1 00 00 +command: 0A F5 00 00 +# +name: Blue +type: parsed +protocol: NECext +address: 0A F1 00 00 +command: 0B F4 00 00 +# +# Model: Sooair Puck_LED +# +name: White +type: parsed +protocol: NECext +address: 0A F1 00 00 +command: 1A E5 00 00 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9229 4516 596 547 596 548 595 548 595 548 595 548 595 548 595 548 595 549 594 1654 595 1655 593 1654 594 1655 594 1655 593 1656 592 1655 594 1654 594 1655 593 1655 593 1654 594 549 593 549 593 548 594 549 593 548 594 547 595 548 594 549 593 1655 593 1656 591 1655 593 1655 593 1655 593 39847 9197 2253 591 96092 9191 2252 592 96098 9189 2252 592 96101 9186 2252 593 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9230 4519 595 548 595 548 595 548 595 548 595 548 595 546 597 547 596 547 596 1655 595 1654 596 1655 595 1654 596 1655 595 1654 596 1654 595 1655 595 548 595 1656 594 1654 595 548 594 1654 595 548 594 546 597 547 596 1655 594 548 594 548 595 1655 594 546 596 1654 595 1654 595 1655 594 39860 9213 2253 594 96136 9201 2253 619 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9266 4495 623 519 624 520 623 521 624 520 624 520 624 520 624 520 624 519 625 1628 623 1628 623 1631 620 1627 623 1627 624 1628 622 1628 622 1627 623 1629 622 522 621 522 621 1630 620 1628 622 521 622 520 623 521 622 522 621 1630 620 1630 620 521 621 522 597 1656 594 1654 596 1655 595 39873 9221 2254 619 96136 9233 2227 623 +# +# Model: RY King_Flameless_Candles +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9230 4521 570 572 571 572 572 572 572 573 571 572 572 572 572 572 572 570 574 1680 570 1681 569 1679 594 1656 594 1657 571 1679 571 1679 594 1656 571 1679 571 572 571 1680 593 1656 571 572 571 571 572 571 572 571 572 571 572 1678 589 554 571 571 572 1679 593 1657 592 1658 591 1656 571 39892 9210 2252 594 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9202 4538 569 581 593 556 594 554 595 554 594 555 593 557 592 560 589 561 589 1671 589 1671 589 1670 589 1670 589 561 588 1669 590 1669 590 1670 589 561 589 560 589 1669 588 560 588 560 588 560 588 560 589 561 588 1669 588 1669 589 560 588 1669 588 1669 588 1669 589 1669 588 1669 589 41008 9210 2244 589 +# +# Model: Sunset Lamp +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9214 4548 568 582 569 581 569 581 569 581 569 582 593 557 593 558 592 558 592 1672 590 1672 590 1673 589 1673 590 561 589 1673 589 1673 589 1673 589 1674 588 1674 588 1698 564 586 564 586 564 586 564 586 564 586 564 586 564 586 564 586 564 1698 564 1699 563 1699 563 1699 564 1699 564 41159 9218 2249 592 +# +name: Power_on +type: parsed +protocol: RC5 +address: 1D 00 00 00 +command: 39 00 00 00 +# +name: Power_off +type: parsed +protocol: RC5 +address: 1D 00 00 00 +command: 3A 00 00 00 +# +name: Brightness_up +type: parsed +protocol: RC5 +address: 1D 00 00 00 +command: 3D 00 00 00 +# +# Model: REMOTECONTROL IR6 +# +name: Brightness_dn +type: parsed +protocol: RC5 +address: 1D 00 00 00 +command: 3E 00 00 00 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 962 776 1829 1696 938 827 743 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 933 864 1850 1673 933 831 910 856 937 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 966 800 1856 1666 965 798 963 800 1213 +# +# Model: tuffenough all_models +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 992 776 1826 1697 935 827 962 800 1410 +# +# Model: Moon Lamp +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9441 4485 622 532 624 533 624 533 624 534 622 532 625 533 624 534 622 533 623 1639 623 1644 623 1646 622 1646 621 534 622 1636 624 1647 623 1649 620 534 621 532 623 1648 622 534 623 532 624 532 624 532 624 533 623 1647 623 1648 622 535 621 1644 623 1648 676 1593 677 1592 675 1593 677 72746 9403 4511 675 479 677 480 675 478 677 479 676 477 678 478 677 478 677 478 677 1593 677 1594 676 1594 675 1594 675 477 677 1593 676 1594 675 1593 675 478 676 479 675 1594 675 479 675 479 675 480 674 479 676 479 675 1592 677 1594 675 478 677 1593 677 1593 676 1593 676 1594 675 1594 675 40985 9360 2217 619 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9414 4486 676 478 623 530 623 530 621 533 623 532 622 534 621 532 676 477 624 1634 678 1589 622 1644 622 1642 676 480 674 1583 677 1589 675 1587 678 1585 676 478 677 1584 677 476 678 478 678 478 675 496 680 477 676 477 677 1582 677 479 677 1585 675 1616 676 1592 676 1590 678 1581 678 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9399 4513 673 484 673 484 673 482 674 483 674 483 674 482 675 482 675 482 675 1599 673 1598 673 1597 674 1598 673 482 674 1595 676 1596 675 1596 674 480 675 1596 674 1595 676 482 680 481 677 481 675 479 675 480 674 1593 676 479 676 479 679 1593 683 1594 678 1593 674 +# +# Model: nightlampRGB +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9371 4482 674 479 675 479 676 477 678 478 677 478 677 478 676 479 675 479 675 1594 675 1594 675 1596 673 1594 675 479 674 1594 674 1594 675 1594 674 1595 673 1594 674 1594 674 479 675 478 675 480 673 479 674 479 674 479 674 479 674 480 673 1596 672 1594 674 1594 674 1594 674 1594 674 41002 9394 2175 672 +# +name: Green +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 07 00 00 00 +# +name: Blue +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 16 00 00 00 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 6611 147 2428 4472 605 535 610 529 606 532 603 532 603 532 603 531 604 530 605 529 606 1632 596 1640 599 1639 600 1639 600 538 607 1632 607 1632 607 1632 607 1633 606 532 603 533 602 535 600 536 599 537 598 539 606 529 606 530 605 1633 606 1633 606 1633 606 1633 606 1633 606 1633 606 1632 607 40787 9187 2210 607 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9239 4483 591 558 584 536 606 540 581 563 579 541 612 535 586 559 583 537 605 1698 556 1668 586 1666 608 1668 586 1664 590 1661 614 1663 580 1670 636 512 578 542 611 535 586 1688 587 534 587 559 583 536 606 539 582 1693 582 1668 586 1664 590 558 584 1664 579 1671 614 1663 580 1670 584 39857 9228 2239 581 +# +# Model: Veezio Light_Bulb +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9205 4477 586 558 584 533 609 534 577 566 576 541 612 533 578 566 576 542 611 1663 580 1667 587 1663 611 1665 589 1662 581 1670 615 1664 590 1663 591 560 582 1668 586 564 578 1671 583 566 587 535 607 541 580 567 586 1662 581 566 587 1660 583 565 577 1671 593 1661 614 1664 642 1608 583 39864 9242 2244 586 +# \ No newline at end of file diff --git a/applications/main/infrared/resources/infrared/assets/monitor.ir b/applications/main/infrared/resources/infrared/assets/monitor.ir index 962188e86..dde652b64 100644 --- a/applications/main/infrared/resources/infrared/assets/monitor.ir +++ b/applications/main/infrared/resources/infrared/assets/monitor.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 # -# Last Updated: 2nd October 2024 +# Last Updated: 7th Sept, 2025 # name: POWER type: parsed @@ -131,8 +131,28 @@ command: 43 BC 00 00 # # Model: Monoprice 43305 # +# Model: Aorus FV43U name: POWER type: parsed protocol: NEC address: 20 00 00 00 command: 41 00 00 00 +# +name: POWER +type: parsed +protocol: RC6 +address: 00 00 00 00 +command: 0C 00 00 00 +# +name: SOURCE +type: parsed +protocol: RC6 +address: 00 00 00 00 +command: 38 00 00 00 +# +name: MENU +type: parsed +protocol: RC6 +address: 00 00 00 00 +command: 54 00 00 00 +# \ No newline at end of file diff --git a/applications/main/infrared/resources/infrared/assets/projectors.ir b/applications/main/infrared/resources/infrared/assets/projectors.ir index 6c1c6684c..862cd06d5 100644 --- a/applications/main/infrared/resources/infrared/assets/projectors.ir +++ b/applications/main/infrared/resources/infrared/assets/projectors.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 -# Last Updated 5th Oct, 2024 -# Last Checked 5th Oct, 2024 +# Last Updated 7th Sept, 2025 +# Last Checked 7th Sept, 2025 # # TEMP FIX FOR POWER: Power signals are duplicated, since projectors require second confirm to poweroff # diff --git a/applications/main/infrared/resources/infrared/assets/tv.ir b/applications/main/infrared/resources/infrared/assets/tv.ir index 8929d5f8c..125f5ce4b 100755 --- a/applications/main/infrared/resources/infrared/assets/tv.ir +++ b/applications/main/infrared/resources/infrared/assets/tv.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 -# Last Updated 21st Jan, 2025 -# Last Checked 21st Jan, 2025 +# Last Updated 7th Sept, 2025 +# Last Checked 7th Sept, 2025 # name: Power type: parsed @@ -3689,6 +3689,7 @@ address: EA C7 00 00 command: 33 CC 00 00 # # Model: Panasonic N2QAYB000752_Full +# name: Ch_prev type: parsed protocol: Kaseikyo @@ -3702,6 +3703,7 @@ address: B0 02 20 00 command: 40 03 00 00 # # Model: Soniq QSP500TV6 +# name: Power type: parsed protocol: NECext @@ -3733,6 +3735,7 @@ address: 72 DD 00 00 command: 5E A1 00 00 # # Model: AKAI ATE_22Y604W +# name: Ch_prev type: parsed protocol: NECext @@ -3740,6 +3743,7 @@ address: 72 DD 00 00 command: 56 A9 00 00 # # Model: Brandt B3228HD +# name: Power type: raw frequency: 38000 @@ -3777,6 +3781,7 @@ duty_cycle: 0.330000 data: 9344 4504 663 494 666 494 666 495 665 496 663 521 638 522 637 1634 660 522 638 1633 660 1655 638 1656 636 1656 636 1657 636 1658 635 526 633 1661 632 1662 631 1662 631 1663 631 1663 631 1662 632 530 631 530 631 530 630 530 631 530 631 530 631 530 631 530 631 1663 631 1664 630 1663 631 40893 9321 2247 631 98484 9323 2247 632 # # Model: Brandt B3230HD_TV +# name: Power type: raw frequency: 38000 @@ -3814,6 +3819,7 @@ duty_cycle: 0.330000 data: 9225 4476 586 1675 613 527 612 529 610 530 609 532 607 558 581 558 582 559 581 559 581 1681 581 1681 581 1681 581 1681 581 1681 581 1681 581 1681 581 1683 581 1681 581 1681 581 1681 581 559 581 559 581 558 582 559 580 559 581 559 581 559 581 559 581 1681 581 1681 581 1682 580 1682 581 # # Model: Gpx +# name: Power type: raw frequency: 38000 @@ -3839,6 +3845,7 @@ duty_cycle: 0.330000 data: 337 1698 336 667 336 665 338 666 337 665 392 1644 337 666 338 1696 338 666 338 1723 310 667 337 667 336 693 310 1723 311 692 311 44474 310 1723 310 692 311 692 311 693 310 693 310 694 309 1725 334 670 333 1701 332 672 332 1702 331 1702 332 1702 331 672 332 1702 331 # # Model: Grundig 2 +# name: Power type: raw frequency: 38000 @@ -3876,6 +3883,7 @@ duty_cycle: 0.330000 data: 920 772 920 772 1771 768 920 771 920 772 920 771 948 748 948 743 948 1590 946 746 1795 1613 919 85291 892 799 893 799 1767 772 916 777 914 778 914 779 913 783 913 779 913 1625 913 780 1762 1623 912 # # Model: Grundig AndroidTV +# name: Power type: raw frequency: 38000 @@ -3913,6 +3921,7 @@ duty_cycle: 0.330000 data: 846 826 847 797 1723 837 846 826 847 826 847 826 846 826 846 826 846 1641 847 827 1717 1631 867 85430 845 827 845 827 1718 813 869 805 867 806 867 808 865 809 864 809 864 1624 864 832 1689 1635 863 # # Model: GRUNDIG UNKNOWN +# name: Power type: raw frequency: 38000 @@ -3980,6 +3989,7 @@ address: 00 7F 00 00 command: 5A A5 00 00 # # Model: Hisense EN_33926A +# name: Power type: raw frequency: 38000 @@ -4005,6 +4015,7 @@ duty_cycle: 0.330000 data: 9020 4375 657 450 656 451 654 1566 656 451 655 451 653 453 626 480 653 452 655 1567 654 1568 654 454 651 1568 654 1568 654 1569 653 1570 651 1572 650 1595 626 479 626 480 625 1597 624 482 624 482 624 483 623 483 623 483 623 1599 623 1599 623 483 623 1599 623 1599 623 1599 623 1599 623 39900 8912 2136 623 # # Model: Hisense ER22601A +# name: Power type: raw frequency: 38000 @@ -4036,6 +4047,7 @@ duty_cycle: 0.330000 data: 951 749 1832 741 978 721 927 774 926 776 924 776 924 801 899 1632 1773 798 920 782 918 1613 917 85257 898 802 1773 800 919 782 918 782 919 782 919 782 918 782 918 1613 1769 802 918 783 918 1614 917 85260 898 802 1772 800 919 782 919 782 919 782 919 782 918 782 919 1613 1769 802 918 782 918 1613 918 # # Model: JVC RMT-JR01 +# name: Power type: raw frequency: 38000 @@ -4115,6 +4127,7 @@ address: 83 7A 00 00 command: 00 00 00 00 # # Model: NEC +# name: Power type: raw frequency: 38000 @@ -4242,6 +4255,7 @@ duty_cycle: 0.330000 data: 2737 825 494 832 494 390 492 392 1352 1328 461 450 405 450 435 477 297 587 296 644 295 92395 2734 829 491 836 490 394 491 419 1286 1395 296 587 378 1474 239 # # Model: RCA P46731AT_TV +# name: Power type: raw frequency: 38000 @@ -4273,6 +4287,7 @@ duty_cycle: 0.330000 data: 182 7824 176 2328 181 2323 176 2328 181 2324 175 1312 183 1323 182 2322 177 1330 175 2309 179 2325 174 1332 173 1333 183 1305 179 1327 178 1328 177 1328 177 2308 180 262 177 1885 175 295 175 861 174 2330 179 99 313 911 573 915 590 1916 180 64 349 1910 176 265 174 7896 177 3081 835 3912 182 2322 177 207 179 1942 180 2304 174 268 181 1881 179 263 176 889 177 264 175 886 583 1925 182 271 178 855 180 240 178 1907 174 2331 178 1327 178 1309 176 1329 590 916 589 919 173 306 175 833 181 317 174 1832 175 2329 180 1325 180 309 182 1833 174 1313 182 1325 180 289 181 1853 175 2329 180 8313 178 7845 176 2309 179 2325 174 2331 178 2326 173 1333 183 1304 180 2324 175 1332 173 2330 179 2325 174 1314 181 1325 180 1326 179 1327 178 1308 177 1330 175 2329 180 2324 175 1312 183 2322 177 1329 176 1329 176 2329 180 2304 174 8339 173 351 3575 3902 594 1911 588 1916 593 1911 588 1916 583 904 591 916 589 1915 594 911 584 1920 579 1907 592 915 590 915 591 916 579 908 597 909 596 909 586 1919 580 1907 179 1326 592 1912 597 908 587 901 594 1911 588 1916 593 # # Model: RCA RokuTV_RTR4061-B-CA +# name: Power type: raw frequency: 38000 @@ -4292,6 +4307,7 @@ address: 05 00 00 00 command: 10 00 00 00 # # Model: Samsung HQ24ED470AK +# name: Power type: raw frequency: 38000 @@ -4329,6 +4345,7 @@ duty_cycle: 0.330000 data: 4497 4474 564 1690 537 1691 536 1666 572 556 537 564 539 562 541 560 543 558 545 1683 544 1683 544 1684 543 532 571 556 537 564 539 562 541 560 543 1659 568 1686 541 1661 566 1662 565 562 541 560 543 558 535 565 538 563 540 561 542 558 545 556 537 1665 562 1666 572 1656 571 1684 543 557 546 555 538 564 539 562 541 42966 4502 4466 562 1692 535 1693 545 1657 570 556 537 565 538 562 541 560 543 543 1656 571 1656 571 1657 570 557 536 565 538 563 540 560 543 558 545 1683 544 1683 544 1657 570 1684 543 531 562 565 538 563 540 560 543 558 545 556 537 564 539 561 542 1660 567 1686 541 1687 540 1662 565 562 541 533 570 557 536 565 538 # # Model: Samsung LE37S71B +# name: Power type: raw frequency: 38000 @@ -4390,6 +4407,7 @@ duty_cycle: 0.330000 data: 9040 4407 659 463 659 1579 661 461 685 437 658 463 658 463 658 464 657 470 656 1585 654 491 629 1611 628 1613 626 1614 625 1615 625 1615 625 502 625 1615 625 497 625 496 625 1614 625 1615 625 497 625 497 624 503 624 496 625 1615 624 1615 624 497 625 496 625 1615 624 1615 624 1614 624 41051 9036 2175 625 # # Model: Sharp 13VT-L100 +# name: Power type: raw frequency: 38000 @@ -4427,6 +4445,7 @@ duty_cycle: 0.330000 data: 264 1848 264 792 264 792 264 792 264 792 264 1848 264 1848 264 1848 264 792 264 1848 264 792 264 792 264 792 264 1848 264 792 264 43560 264 1848 264 792 264 792 264 792 264 792 264 792 264 792 264 792 264 1848 264 792 264 1848 264 1848 264 1848 264 792 264 1848 264 43560 # # Model: Sharp Aquos_JP +# name: Power type: raw frequency: 38000 @@ -4464,6 +4483,7 @@ duty_cycle: 0.330000 data: 3361 1649 443 423 416 1237 442 398 441 1235 444 398 441 1237 442 398 441 1237 442 397 442 1236 443 397 442 1236 443 1237 442 399 440 1236 443 398 441 1263 416 1263 472 1207 471 1207 470 371 414 423 416 423 416 1263 416 423 416 1263 416 423 416 423 416 1262 417 422 417 422 417 422 417 422 417 1262 416 422 417 422 417 1262 416 423 441 398 441 398 441 1238 441 398 441 398 441 399 440 1239 440 399 440 399 440 1238 441 # # Model: Sharp LC-RC1-16 +# name: Power type: raw frequency: 38000 @@ -4501,6 +4521,7 @@ duty_cycle: 0.330000 data: 287 1812 288 760 288 761 287 761 287 761 287 760 288 1814 286 760 288 761 287 1813 287 760 288 760 288 760 288 1813 287 757 312 47498 340 1759 341 707 341 709 315 731 340 708 340 1760 340 707 341 1759 318 1783 317 730 318 1782 318 1783 317 1784 316 731 317 1782 316 43284 314 1787 314 736 312 734 314 734 314 734 314 735 313 1787 314 735 313 734 314 1787 313 735 313 734 314 733 315 1787 314 732 313 47500 285 1813 361 685 363 687 361 687 361 686 362 1739 360 687 361 1740 359 1740 360 689 358 1741 359 1743 356 1746 353 690 357 1741 356 # # Model: Sharp tv +# name: Power type: raw frequency: 38000 @@ -4514,6 +4535,7 @@ duty_cycle: 0.330000 data: 254 1721 360 681 354 738 308 706 329 711 355 1774 307 1772 361 1744 327 687 359 1772 299 742 335 705 330 736 279 1825 298 742 283 44773 384 1721 360 707 308 707 359 733 302 711 335 705 361 704 331 708 338 1766 336 704 331 1773 329 1776 306 1773 360 681 323 1782 331 44726 411 1722 328 686 360 733 302 711 335 705 361 1742 329 1803 330 1749 332 708 327 1777 335 705 330 710 325 741 274 1830 303 737 278 44778 359 1747 355 712 303 711 355 711 335 705 330 709 337 703 363 703 332 1770 332 709 337 1767 335 1771 300 1752 360 733 302 1776 326 44731 355 1751 330 711 355 737 309 705 330 710 336 1793 309 1771 331 1774 307 706 360 1771 300 740 326 714 332 735 280 1798 325 741 274 # # Model: Sharp TV2 +# name: Power type: raw frequency: 38000 @@ -4575,6 +4597,7 @@ address: 01 00 00 00 command: 11 00 00 00 # # Model: Strong STR7004 +# name: Power type: parsed protocol: NEC @@ -4612,6 +4635,7 @@ address: 01 00 00 00 command: 08 00 00 00 # # Model: Symphonic ST424FF +# name: Power type: raw frequency: 36700 @@ -4655,6 +4679,7 @@ duty_cycle: 0.330000 data: 4012 3982 514 1984 519 1979 514 1984 519 1979 514 983 519 980 512 1988 516 982 520 1979 514 1984 519 1978 515 983 519 980 511 987 515 983 519 980 511 1988 516 1982 511 987 515 1983 521 978 514 985 517 981 521 1978 515 # # Model: TC 33_VNS2000 +# name: Ch_next type: raw frequency: 38000 @@ -4680,6 +4705,7 @@ duty_cycle: 0.330000 data: 173 7448 175 7445 178 7443 170 7451 172 7449 174 7447 176 7445 168 4912 173 7447 176 7446 177 4903 172 43015 177 7443 170 7451 172 7448 175 7445 178 7444 169 7452 171 7449 174 4907 168 7452 171 7450 173 4907 168 43018 175 7447 176 7444 169 7453 170 7450 173 7448 175 7446 177 399 174 6870 169 4911 174 7447 176 7445 168 4912 173 43013 169 7451 172 7451 172 7448 175 7445 168 7453 170 7451 172 7449 174 4906 169 7452 171 7450 173 4907 168 43019 174 7447 176 7445 178 7443 170 7450 173 7448 175 7446 177 7444 169 4910 175 7447 176 7444 169 4913 172 43012 170 7452 171 7450 173 7448 175 7445 168 7453 170 7450 173 7449 174 4907 168 7452 171 7450 173 4908 177 43010 171 7448 175 7446 177 7444 169 7451 172 7448 175 # # Model: Telekom Entertain +# name: Power type: raw frequency: 38000 @@ -4717,6 +4743,7 @@ duty_cycle: 0.330000 data: 332 601 314 290 646 286 307 594 321 282 644 590 648 582 323 281 645 586 641 290 313 583 644 # # Model: Vizio D32FM-K01 +# name: Power type: raw frequency: 38000 @@ -4742,6 +4769,7 @@ duty_cycle: 0.330000 data: 9219 4485 636 495 660 469 661 1626 661 471 658 473 657 474 656 499 631 500 630 1657 630 1657 631 500 630 1657 630 1657 631 1657 631 1657 630 1657 631 1657 631 500 630 500 630 1657 631 500 630 500 630 500 630 500 631 500 630 1657 631 1657 631 500 630 1657 631 1658 630 1657 631 1658 630 39868 9106 2178 655 # # Model: Vizio XRT140R +# name: Power type: raw frequency: 38000 @@ -4749,6 +4777,7 @@ duty_cycle: 0.330000 data: 9016 4408 603 512 602 512 629 1599 686 428 630 483 630 484 629 485 628 492 627 1604 625 1605 624 491 623 1630 599 1631 598 1631 598 1631 598 1636 599 515 599 515 599 515 599 1631 599 515 599 515 599 515 599 521 599 1631 599 1631 599 1631 598 515 599 1631 598 1631 598 1631 598 1632 598 39999 9016 2166 626 95735 9012 2168 625 95735 9013 2167 626 # # Model: Xiaomi TV +# name: Vol_up type: raw frequency: 38000 @@ -4762,6 +4791,7 @@ duty_cycle: 0.330000 data: 990 603 589 1175 589 587 588 882 587 1175 588 588 588 589 586 1470 587 1471 587 588 588 881 588 12528 988 609 586 1177 587 589 586 882 587 1177 586 588 587 589 587 1470 587 1471 587 588 588 881 588 12527 989 607 588 1175 589 588 588 881 588 1175 588 588 588 587 589 1470 587 1470 588 589 587 881 588 12528 988 607 588 1175 589 588 587 882 587 1178 586 587 588 588 588 1472 586 1470 588 588 587 881 588 12529 987 608 587 1175 589 587 589 882 587 1175 589 589 587 587 589 1469 588 1470 588 587 589 881 588 12528 988 607 588 1175 589 590 585 883 586 1176 587 587 589 588 588 1471 586 1470 588 587 589 883 586 12527 989 607 588 1176 588 588 588 880 589 1176 588 588 588 589 586 1469 588 1469 589 588 587 881 588 # # Model: Zenith SC3492Z +# name: Power type: raw frequency: 38000 @@ -4799,6 +4829,7 @@ duty_cycle: 0.330000 data: 598 400 598 3999 597 4999 596 401 597 4000 597 5000 596 402 596 3999 598 4999 597 401 597 4000 597 4998 598 402 596 4000 597 402 596 4000 597 # # Model: Zenith tv +# name: Power type: raw frequency: 38000 @@ -4829,8 +4860,113 @@ frequency: 38000 duty_cycle: 0.330000 data: 591 413 591 4027 592 5030 593 411 593 4026 593 412 592 4025 618 5005 620 5004 592 439 564 4028 591 439 564 4029 590 5033 590 5035 588 440 563 122125 617 387 619 4000 592 5032 591 439 564 4028 591 439 565 4030 589 5034 589 5059 564 441 562 4057 562 442 561 4058 561 5063 560 5063 560 444 560 # +# Model: AKAI ATE_22Y604W +# name: Vol_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 590 412 592 4026 593 5030 593 412 592 4025 594 5029 594 412 616 4002 618 5003 593 439 564 4027 592 439 564 4029 590 5033 590 5035 588 440 563 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 604 2557 605 452 604 451 605 451 605 454 602 452 604 450 606 450 606 452 604 452 604 19746 603 2558 604 976 1132 974 604 452 604 452 604 451 605 451 605 451 606 451 605 116246 602 2559 603 453 603 454 602 453 603 453 604 452 604 452 604 453 603 453 603 453 603 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 602 2559 603 452 604 452 604 453 604 452 604 451 605 453 603 452 604 454 603 452 604 19747 601 2560 602 454 602 980 601 455 601 456 600 456 600 455 601 456 600 457 599 456 600 116184 604 2558 605 452 604 978 603 453 603 452 604 452 604 452 604 451 605 453 603 452 604 116254 604 2557 605 452 604 451 605 450 606 451 605 451 605 451 605 452 605 452 604 452 604 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 616 2545 617 440 616 440 616 440 616 441 615 441 615 440 616 439 617 440 616 441 615 19734 615 2547 615 964 617 440 616 441 1144 961 615 441 615 441 615 440 616 442 615 116235 615 2547 615 441 615 441 615 440 616 442 614 442 614 440 616 441 615 441 615 441 616 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 600 2562 600 455 601 454 602 455 602 454 602 455 601 453 603 455 601 455 602 456 600 19750 601 2559 603 453 603 978 603 454 1129 977 602 453 603 453 603 453 603 455 602 116250 613 2548 614 440 616 442 614 442 614 442 614 442 614 442 614 441 615 441 615 442 615 +# +name: Ch_next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 603 2559 603 452 604 452 604 452 604 451 605 452 604 451 605 452 604 453 604 452 604 19745 605 2557 606 976 1133 972 1134 973 1133 972 606 451 605 451 605 116200 616 2548 614 965 1145 961 1145 961 1144 962 614 442 614 441 615 116207 613 2547 616 967 1143 960 1145 961 1144 961 615 441 615 441 615 116266 613 2548 615 442 614 442 614 443 613 443 613 443 613 442 614 442 614 443 613 445 611 +# +# Model: GRUNDIG UNKNOWN +# +name: Ch_prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 595 2566 597 459 597 459 597 461 595 462 594 460 596 461 595 462 594 462 594 460 596 19756 588 2572 591 465 591 465 591 989 1120 986 1120 987 591 464 592 465 592 116206 592 2571 591 464 593 464 592 989 1119 986 1119 984 594 464 592 463 593 116221 592 2570 592 465 591 465 592 988 1120 984 1122 986 592 464 592 464 593 116221 598 2564 598 458 598 458 598 983 1126 980 1126 979 599 459 597 459 597 116276 599 2562 600 455 601 456 600 456 600 457 599 457 599 457 599 456 600 457 599 457 599 +# +name: Ch_next +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: 07 00 00 00 +# +name: Ch_prev +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: 0B 00 00 00 +# +name: Vol_up +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: 12 00 00 00 +# +# Model: Samsung BN59-01391A +# +name: Vol_dn +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: 10 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 04 B9 00 00 +command: 00 FF 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 04 B9 00 00 +command: 16 E9 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 04 B9 00 00 +command: 17 E8 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 04 B9 00 00 +command: 1A E5 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 04 B9 00 00 +command: 14 EB 00 00 +# +# Model: TCL 32S327 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 04 B9 00 00 +command: 15 EA 00 00 +# \ No newline at end of file From 2646c7618e974baee562ef9caa21a09e29041d8e Mon Sep 17 00:00:00 2001 From: Ashe Sterling <91851849+Ashe-Sterling@users.noreply.github.com> Date: Sat, 29 Nov 2025 16:40:21 -0800 Subject: [PATCH 186/192] Bad KB: Add Colemak keyboard layout (#466) * Add Colemak keyboard layout for badusb * Update changelog --------- Co-authored-by: WillyJL --- CHANGELOG.md | 1 + .../resources/badusb/assets/layouts/colemak.kl | Bin 0 -> 256 bytes 2 files changed, 1 insertion(+) create mode 100644 applications/main/bad_usb/resources/badusb/assets/layouts/colemak.kl diff --git a/CHANGELOG.md b/CHANGELOG.md index 97eceabd9..2b7716451 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -111,6 +111,7 @@ - Desktop: Add Keybinds support for directories (#331 by @956MB & @WillyJL) - Input Settings: Add Vibro Trigger option (#429 by @956MB) - Archive: Support opening and favoriting Picopass files (by @WillyJL) +- Bad KB: Colemak keyboard layout (#466 by @Ashe-Sterling) - OFW: GUI: Add date/time input module (by @aaronjamt) ### Updated: 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 12b89074891dd41be03eca76f6b009598bb59955 Mon Sep 17 00:00:00 2001 From: Aaron Tulino Date: Sat, 29 Nov 2025 17:41:00 -0700 Subject: [PATCH 187/192] NFC: Add MFUL counters to Info page (#472) * Add MFUL counters to Info page * Update changelog --------- Co-authored-by: WillyJL --- CHANGELOG.md | 1 + .../protocol_support/mf_ultralight/mf_ultralight_render.c | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b7716451..200282cbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,6 +72,7 @@ - UL: Returning fix for reading PWD locked MFUL (by @mishamyte) - UL: Added UL-C keys to the dictionary (by @mishamyte) - Add MIFARE Classic "Show Keys" UI (#473 by @aaronjamt) + - Add MFUL counters to Info page (#472 by @aaronjamt) - SubGHz: - UL: Roger (static 28 bit) with add manually support (by @xMasterX & @mishamyte) - UL: V2 Phoenix full support (button switch, add manually, counter decrypt/encrypt) (by @xMasterX & @RocketGod-git, original code by @Skorpionm) 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..d125d5fa0 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; @@ -44,6 +49,8 @@ void nfc_render_mf_ultralight_info( nfc_render_iso14443_3a_info(data->iso14443_3a_data, format_type, str); nfc_render_mf_ultralight_pages_count(data, str); + + nfc_render_mf_ultralight_counters(data, str); } void nfc_render_mf_ultralight_dump(const MfUltralightData* data, FuriString* str) { From 538ccd8351c4d4e110066c54d7af9cb5c895a205 Mon Sep 17 00:00:00 2001 From: Aaron Tulino Date: Sat, 29 Nov 2025 17:41:29 -0700 Subject: [PATCH 188/192] NFC: Add Saflok MFUL Parser Support (#474) * Add Saflok MFULC parser Written by @xtruan. Thanks for the contribution! Co-authored-by: xtruan * Run clang-format * Update changelog --------- Co-authored-by: xtruan Co-authored-by: WillyJL --- CHANGELOG.md | 1 + applications/main/nfc/application.fam | 13 +++- .../main/nfc/plugins/supported_cards/saflok.c | 78 +++++++++++++++---- 3 files changed, 78 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 200282cbd..cc8b11a78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,7 @@ - OFW: MFC 1k Banapass Parser (by @zinongli) - UL: Returning fix for reading PWD locked MFUL (by @mishamyte) - UL: Added UL-C keys to the dictionary (by @mishamyte) + - Add Saflok MFUL Parser Support (#474 by @aaronjamt) - Add MIFARE Classic "Show Keys" UI (#473 by @aaronjamt) - Add MFUL counters to Info page (#472 by @aaronjamt) - SubGHz: diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 7cb4b5e25..89dbcf42c 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -370,8 +370,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..5036dbffc 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 From 8754b327c3e5ff866e86a0e99566271028bb6598 Mon Sep 17 00:00:00 2001 From: tototo31 Date: Sat, 29 Nov 2025 18:47:20 -0600 Subject: [PATCH 189/192] GUI: Add Sreen Settings shortcut to Control Center brightness click (#487) * Enhance momentum app to support command line arguments for scene navigation and add RGB settings event to desktop lock menu. * Rename to ScreenSettings * Format * Update changelog * ugh * ugh * Update changelog again --------- Co-authored-by: tototo31 Co-authored-by: WillyJL --- CHANGELOG.md | 4 +++- applications/main/momentum_app/momentum_app.c | 13 +++++++++++-- .../desktop/scenes/desktop_scene_lock_menu.c | 5 +++++ .../services/desktop/views/desktop_events.h | 1 + .../services/desktop/views/desktop_view_lock_menu.c | 3 +++ 5 files changed, 23 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc8b11a78..8538680ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -114,7 +114,9 @@ - Input Settings: Add Vibro Trigger option (#429 by @956MB) - Archive: Support opening and favoriting Picopass files (by @WillyJL) - Bad KB: Colemak keyboard layout (#466 by @Ashe-Sterling) -- OFW: GUI: Add date/time input module (by @aaronjamt) +- GUI: + - GUI: Add Sreen Settings shortcut to Control Center brightness click (#487 by @tototo31) + - OFW: Add date/time input module (by @aaronjamt) ### Updated: - Apps: diff --git a/applications/main/momentum_app/momentum_app.c b/applications/main/momentum_app/momentum_app.c index 030fb9ada..a81ae0fbe 100644 --- a/applications/main/momentum_app/momentum_app.c +++ b/applications/main/momentum_app/momentum_app.c @@ -1,4 +1,5 @@ #include "momentum_app.h" +#include static bool momentum_app_custom_event_callback(void* context, uint32_t event) { furi_assert(context); @@ -483,9 +484,17 @@ void momentum_app_free(MomentumApp* app) { } extern int32_t momentum_app(void* p) { - UNUSED(p); MomentumApp* app = momentum_app_alloc(); - scene_manager_next_scene(app->scene_manager, MomentumAppSceneStart); + + // Check for command line arguments to navigate to specific scenes + uint32_t first_scene = MomentumAppSceneStart; + if(p && strlen(p)) { + if(!strcmp(p, "MiscScreen")) { + first_scene = MomentumAppSceneMiscScreen; + } + } + + scene_manager_next_scene(app->scene_manager, first_scene); view_dispatcher_run(app->view_dispatcher); momentum_app_free(app); return 0; diff --git a/applications/services/desktop/scenes/desktop_scene_lock_menu.c b/applications/services/desktop/scenes/desktop_scene_lock_menu.c index d3987b6ee..f7e199e7e 100644 --- a/applications/services/desktop/scenes/desktop_scene_lock_menu.c +++ b/applications/services/desktop/scenes/desktop_scene_lock_menu.c @@ -121,6 +121,11 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) { loader_start_detached_with_gui_error(desktop->loader, "Momentum", NULL); consumed = true; break; + case DesktopLockMenuEventScreenSettings: + desktop_scene_lock_menu_save_settings(desktop); + loader_start_detached_with_gui_error(desktop->loader, "Momentum", "MiscScreen"); + consumed = true; + break; case DesktopLockMenuEventStealthModeOn: desktop_set_stealth_mode_state(desktop, true); break; diff --git a/applications/services/desktop/views/desktop_events.h b/applications/services/desktop/views/desktop_events.h index 3f2896912..4b4bb73d1 100644 --- a/applications/services/desktop/views/desktop_events.h +++ b/applications/services/desktop/views/desktop_events.h @@ -65,4 +65,5 @@ typedef enum { DesktopLockMenuEventLockKeypad, DesktopLockMenuEventLockPinOff, DesktopLockMenuEventMomentum, + DesktopLockMenuEventScreenSettings, } DesktopEvent; diff --git a/applications/services/desktop/views/desktop_view_lock_menu.c b/applications/services/desktop/views/desktop_view_lock_menu.c index d6e5ffd34..bdff4c40c 100644 --- a/applications/services/desktop/views/desktop_view_lock_menu.c +++ b/applications/services/desktop/views/desktop_view_lock_menu.c @@ -307,6 +307,9 @@ bool desktop_lock_menu_input_callback(InputEvent* event, void* context) { case DesktopLockMenuIndexMomentum: desktop_event = DesktopLockMenuEventMomentum; break; + case DesktopLockMenuIndexBrightness: + desktop_event = DesktopLockMenuEventScreenSettings; + break; case DesktopLockMenuIndexVolume: desktop_event = stealth_mode ? DesktopLockMenuEventStealthModeOff : DesktopLockMenuEventStealthModeOn; From 0025596718abe97fb94568fca642611f0524921e Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sun, 30 Nov 2025 01:48:32 +0100 Subject: [PATCH 190/192] Apps: Add FlipMap (by jblanked) --- CHANGELOG.md | 4 +++- applications/external | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8538680ab..5be3f7cd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,9 @@ - FlipBoard Keyboard (by @jamisonderek) - FlipBoard Signal (by @jamisonderek) - FlipBoard Simon (by @jamisonderek) - - GPIO/FlipperHTTP: Free Roam (by @jblanked) + - GPIO/FlipperHTTP: + - FlipMap (by @jblanked) + - Free Roam (by @jblanked) - GPIO/GPS: [NMEA] Nearby Files (by @Stichoza) - GPIO/Sensors: [MH-Z19] CO2 Logger (by @harryob2) - iButton: iButton Converter (by @Leptopt1los) diff --git a/applications/external b/applications/external index c7a0ed190..e602f6f26 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit c7a0ed1904857a9c0b2e6c5579bb3e0821cbd7af +Subproject commit e602f6f266950f8ad69fc4d28d10a3538d0be0eb From cc9082b8fac86e534c3bb7b830274050742c40db Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sun, 30 Nov 2025 01:49:30 +0100 Subject: [PATCH 191/192] Fix typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5be3f7cd3..629ae39a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -117,7 +117,7 @@ - Archive: Support opening and favoriting Picopass files (by @WillyJL) - Bad KB: Colemak keyboard layout (#466 by @Ashe-Sterling) - GUI: - - GUI: Add Sreen Settings shortcut to Control Center brightness click (#487 by @tototo31) + - GUI: Add Screen Settings shortcut to Control Center brightness click (#487 by @tototo31) - OFW: Add date/time input module (by @aaronjamt) ### Updated: From 63081d3fc4b7d9700d9ddbe902294a5063fe5c45 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sun, 30 Nov 2025 01:54:43 +0100 Subject: [PATCH 192/192] ESP Flasher: Bump Marauder 1.8.10 (by justcallmekoko), FlipperHTTP 2.1.1 (by jblanked) --- CHANGELOG.md | 2 +- applications/external | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 629ae39a9..d5053a592 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -127,7 +127,7 @@ - UL: BT/USB Remote: PTT global zoom and google meet shortcuts for MacOS (by @hryamzik) - Asteroids: Bugfixes, title screen, Drone Buddy power-up (by @SimplyMinimal) - Combo Cracker: Allow press and hold to change values, add tutorial (by @TAxelAnderson), support alphabetic combination locks (by @henrygab) - - ESP Flasher: Bump Marauder 1.8.4 (by @justcallmekoko), add C5 support (by @Play2BReal), more reliable bootloader mode on SWCLK (by @WillyJL) + - ESP Flasher: Bump Marauder 1.8.10 (by @justcallmekoko), bump FlipperHTTP 2.1.1 (by @jblanked), add C5 support (by @Play2BReal), more reliable bootloader mode on SWCLK (by @WillyJL) - FlipDownloader: Added a new option to download GitHub repositories with dedicated keyboard, add auto updating, new keyboard, better saving system (by @jblanked) - FlipSocial: C++ rewrite, comments on feed posts, simpler logic and registration, fixed auto-updating, better saving system, bugfixes (by @jblanked) - FlipWiFi: Minor bugfixes (by @jblanked) diff --git a/applications/external b/applications/external index e602f6f26..c7b7ab75d 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit e602f6f266950f8ad69fc4d28d10a3538d0be0eb +Subproject commit c7b7ab75d3d790c5ee04213814dce21d61664234