From a54b0ab4a0e0407b7f009e01d3bb5221bf3ffbb0 Mon Sep 17 00:00:00 2001 From: 0mega <171701900+zero-mega@users.noreply.github.com> Date: Wed, 4 Mar 2026 13:17:05 +0100 Subject: [PATCH] PSA faster BF and GUI --- helpers/protopirate_types.h | 2 + images/DolphinDone_80x58.png | Bin 0 -> 448 bytes images/DolphinWait_59x54.png | Bin 0 -> 385 bytes protocols/psa.c | 524 ++++++++++++----------- protocols/psa.h | 31 ++ protopirate_app.c | 13 + protopirate_app_i.h | 3 + protopirate_history.h | 5 + scenes/protopirate_scene_receiver_info.c | 410 +++++++++++++----- 9 files changed, 634 insertions(+), 354 deletions(-) create mode 100644 images/DolphinDone_80x58.png create mode 100644 images/DolphinWait_59x54.png diff --git a/helpers/protopirate_types.h b/helpers/protopirate_types.h index 72f2c39..7490e00 100644 --- a/helpers/protopirate_types.h +++ b/helpers/protopirate_types.h @@ -26,6 +26,8 @@ typedef enum { // File management ProtoPirateCustomEventReceiverInfoSave, ProtoPirateCustomEventReceiverInfoEmulate, + ProtoPirateCustomEventReceiverInfoBruteforceStart, + ProtoPirateCustomEventReceiverInfoBruteforceCancel, ProtoPirateCustomEventSavedInfoDelete, // Emulator ProtoPirateCustomEventSavedInfoEmulate, diff --git a/images/DolphinDone_80x58.png b/images/DolphinDone_80x58.png new file mode 100644 index 0000000000000000000000000000000000000000..881aaa8d2aa6bbe8d563d04c9a0f445cbbbe8be8 GIT binary patch literal 448 zcmV;x0YCnUP)Ly)qzZ!K4#8r(UBSXa znAOIRW>$#V-OSGYE$(XJseTS0hyOcBl4wiH02<8!1DMkGfFup2QZj%Ebe2E?8{hM=Dav;RfV4N%e7sc)sXx_pc2Fw=j81b1#%aC+R)BGZ^ANyboj05S znF4fD_c*OpN2!+mz~Ka73Km-T0Br1Ck(><}EsYD90W2`k-QPcC+Vg`HFK_vDlK}TE zzYj#*$7lY@+Tu08SFI$h1t11{gy$E~DyS4yy_fF#REjDaX?|eKuE-m}*acS3E#m{Y z939dqs;{4lugsnGfCup0+~*fSJ9j-}w?_^GzGm!hu?`#T7!NPVAmfYCejB;~N5<+) qEC8EEYaa^W+CWKv(*WEJaQz2JY`H0gasz1q00009-B7=*unSm;$OBqT^SA;FTMs!1={^90H=u$91fsGva7s`$AXjX{ z0R^OiEhOB#+k4L-wgS{l=9@_d+i7TL>{6es&C+GZS@44xfnXo*f*`{c2s@zYvoH`K z3J_C72LM{j7}5Thj1Y@{3<><{*U-L}MP5C?dkBu7{!>nfGI}^TK{a3! zQvvh|1UD`;>v=5ZyeCS0yZM#d*9D%@A42iZ#=lDzEGp)p0x$tu+nxKY6{eve0dqiq zi0On5eTPW2a8%p&hpeOhv#VDtIP9OH*M^+|3}%5-`iMrP(=TkI`&>G{$Sst(yvf#i zcs0[i] = key[sum0 & 3] + sum0; + out->s1[i] = key[(sum1 >> 11) & 3] + sum1; + } +} + +static inline void psa_tea_encrypt_with_schedule(uint32_t* v0, uint32_t* v1, const PsaTeaSchedule* sched) { + for(int i = 0; i < TEA_ROUNDS; i++) { + *v0 += (sched->s0[i] ^ (((*v1 >> 5) ^ (*v1 << 4)) + *v1)); + *v1 += (sched->s1[i] ^ (((*v0 >> 5) ^ (*v0 << 4)) + *v0)); + } +} + #define PSA_BF1_CONST_U4 0x0E0F5C41U #define PSA_BF1_CONST_U5 0x0F5C4123U @@ -154,6 +177,7 @@ static void psa_calculate_checksum(uint8_t* buffer); static uint8_t psa_calculate_tea_crc(uint32_t v0, uint32_t v1); static void psa_tea_encrypt(uint32_t* v0, uint32_t* v1, const uint32_t* key); static void psa_unpack_tea_result_to_buffer(uint8_t* buffer, uint32_t v0, uint32_t v1); +static bool parse_hex_key_str(const char* str, uint64_t* out); #ifdef ENABLE_EMULATE_FEATURE static void psa_second_stage_xor_encrypt(uint8_t* buffer) { @@ -196,7 +220,7 @@ static void psa_build_buffer_mode23( (unsigned int)instance->crc, (unsigned int)instance->button); - memset(buffer, 0, 48); + memset(buffer, 0, PSA_BUFFER_SIZE); buffer[2] = (uint8_t)((instance->serial >> 16) & 0xFF); buffer[3] = (uint8_t)((instance->serial >> 8) & 0xFF); @@ -342,7 +366,7 @@ static void psa_build_buffer_mode36( (unsigned int)instance->crc, (unsigned int)instance->button); - memset(buffer, 0, 48); + memset(buffer, 0, PSA_BUFFER_SIZE); uint32_t v0 = ((instance->serial & 0xFFFFFF) << 8) | ((instance->counter >> 24) & 0xFF); uint32_t v1 = ((instance->counter & 0xFFFFFF) << 8) | ((instance->button & 0xF) << 4) | @@ -427,13 +451,13 @@ static void psa_encoder_build_upload(SubGhzProtocolEncoderPSA* instance) { (unsigned int)instance->crc, (unsigned int)instance->button); - uint8_t buffer[48] = {0}; + uint8_t buffer[PSA_BUFFER_SIZE] = {0}; uint8_t preserve_buffer01[2] = {0}; uint8_t* preserve_ptr = NULL; if(instance->key1_low != 0 || instance->key1_high != 0) { - uint8_t orig_buffer[48] = {0}; + uint8_t orig_buffer[PSA_BUFFER_SIZE] = {0}; psa_setup_byte_buffer( orig_buffer, instance->key1_low, instance->key1_high, instance->key2_low); preserve_buffer01[0] = orig_buffer[0]; @@ -792,6 +816,13 @@ static void psa_setup_byte_buffer( buffer[8] = (uint8_t)((key2_low >> 8) & 0xFF); } +static bool psa_direct_xor_allowed_by_key2(uint8_t key2_high_byte) { + uint8_t lo = key2_high_byte & 0xf; + if(lo < 3) return true; + if(lo < 7 && (key2_high_byte & 0xc) != 0) return true; + return false; +} + static void psa_calculate_checksum(uint8_t* buffer) { uint32_t checksum = 0; for(int i = 2; i < 8; i++) { @@ -835,7 +866,7 @@ static void psa_tea_encrypt(uint32_t* v0, uint32_t* v1, const uint32_t* key) { } } -static void psa_tea_decrypt(uint32_t* v0, uint32_t* v1, const uint32_t* key) { +static inline void psa_tea_decrypt(uint32_t* v0, uint32_t* v1, const uint32_t* key) { uint32_t sum = TEA_DELTA * TEA_ROUNDS; for(int i = 0; i < TEA_ROUNDS; i++) { uint32_t k_idx2 = (sum >> 11) & 3; @@ -914,14 +945,18 @@ static bool psa_brute_force_decrypt_bf1( uint8_t* buffer, uint32_t w0, uint32_t w1) { + + PsaTeaSchedule bf1_sched; + psa_tea_build_schedule(PSA_BF1_KEY_SCHEDULE, &bf1_sched); + for(uint32_t counter = PSA_BF1_START; counter < PSA_BF1_END; counter++) { uint32_t wk2 = PSA_BF1_CONST_U4; uint32_t wk3 = counter; - psa_tea_encrypt(&wk2, &wk3, PSA_BF1_KEY_SCHEDULE); + psa_tea_encrypt_with_schedule(&wk2, &wk3, &bf1_sched); uint32_t wk0 = (counter << 8) | 0x0E; uint32_t wk1 = PSA_BF1_CONST_U5; - psa_tea_encrypt(&wk0, &wk1, PSA_BF1_KEY_SCHEDULE); + psa_tea_encrypt_with_schedule(&wk0, &wk1, &bf1_sched); uint32_t working_key[4] = {wk0, wk1, wk2, wk3}; @@ -970,7 +1005,8 @@ static bool psa_brute_force_decrypt_bf2( (uint8_t)((dec_v1 >> 16) & 0xFF), }; uint16_t crc16 = psa_calculate_crc16_bf2(crc_buffer, 6); - uint16_t expected_crc = (((dec_v1 >> 16) & 0xFF) << 8) | (dec_v1 & 0xFF); + + uint16_t expected_crc = (uint16_t)(dec_v1 & 0xFFFF); if(crc16 == expected_crc) { psa_extract_fields_mode36(buffer, instance); @@ -981,6 +1017,163 @@ static bool psa_brute_force_decrypt_bf2( return false; } +static void psa_fill_bf_state_from_buffer(PsaBfState* state, uint8_t* buffer) { + state->decrypted_button = (buffer[5] >> 4) & 0xF; + state->decrypted_serial = ((uint32_t)buffer[3] << 8) | ((uint32_t)buffer[2] << 16) | + (uint32_t)buffer[4]; + state->decrypted_counter = ((uint32_t)buffer[7] << 8) | ((uint32_t)buffer[6] << 16) | + (uint32_t)buffer[8] | (((uint32_t)buffer[5] & 0xF) << 24); + state->decrypted_crc = (uint16_t)buffer[9]; + state->decrypted_seed = state->decrypted_serial; + state->decrypted_type = 0x36; +} + +#define PSA_BF_PROGRESS_INTERVAL 65536U + +void psa_brute_force_run(PsaBfState* state) { + uint8_t buffer[48] = {0}; + psa_setup_byte_buffer(buffer, state->key1_low, state->key1_high, state->key2_low); + uint32_t w0, w1; + psa_prepare_tea_data(buffer, &w0, &w1); + + state->progress_current = 0; + state->progress_total = (PSA_BF1_END - PSA_BF1_START) + (PSA_BF2_END - PSA_BF2_START); + state->status = PSA_BF_STATUS_RUNNING; + + PsaTeaSchedule bf1_sched; + psa_tea_build_schedule(PSA_BF1_KEY_SCHEDULE, &bf1_sched); + + for(uint32_t counter = PSA_BF1_START; counter < PSA_BF1_END; counter++) { + if(state->cancel) { + state->status = PSA_BF_STATUS_CANCELLED; + return; + } + if((counter & (PSA_BF_PROGRESS_INTERVAL - 1)) == 0) { + state->progress_current = counter - PSA_BF1_START; + } + + uint32_t wk2 = PSA_BF1_CONST_U4; + uint32_t wk3 = counter; + psa_tea_encrypt_with_schedule(&wk2, &wk3, &bf1_sched); + uint32_t wk0 = (counter << 8) | 0x0E; + uint32_t wk1 = PSA_BF1_CONST_U5; + psa_tea_encrypt_with_schedule(&wk0, &wk1, &bf1_sched); + uint32_t working_key[4] = {wk0, wk1, wk2, wk3}; + + uint32_t dec_v0 = w0; + uint32_t dec_v1 = w1; + psa_tea_decrypt(&dec_v0, &dec_v1, working_key); + + if((counter & 0xFFFFFF) == (dec_v0 >> 8)) { + uint8_t crc = psa_calculate_tea_crc(dec_v0, dec_v1); + if(crc == (dec_v1 & 0xFF)) { + psa_unpack_tea_result_to_buffer(buffer, dec_v0, dec_v1); + psa_fill_bf_state_from_buffer(state, buffer); + state->progress_current = counter - PSA_BF1_START; + state->status = PSA_BF_STATUS_FOUND; + return; + } + } + } + + state->progress_current = PSA_BF1_END - PSA_BF1_START; + + for(uint32_t counter = PSA_BF2_START; counter < PSA_BF2_END; counter++) { + if(state->cancel) { + state->status = PSA_BF_STATUS_CANCELLED; + return; + } + if((counter & (PSA_BF_PROGRESS_INTERVAL - 1)) == 0) { + state->progress_current = + (PSA_BF1_END - PSA_BF1_START) + (counter - PSA_BF2_START); + } + + uint32_t working_key[4] = { + PSA_BF2_KEY_SCHEDULE[0] ^ counter, + PSA_BF2_KEY_SCHEDULE[1] ^ counter, + PSA_BF2_KEY_SCHEDULE[2] ^ counter, + PSA_BF2_KEY_SCHEDULE[3] ^ counter, + }; + uint32_t dec_v0 = w0; + uint32_t dec_v1 = w1; + psa_tea_decrypt(&dec_v0, &dec_v1, working_key); + + if((counter & 0xFFFFFF) == (dec_v0 >> 8)) { + psa_unpack_tea_result_to_buffer(buffer, dec_v0, dec_v1); + uint8_t crc_buffer[6] = { + (uint8_t)((dec_v0 >> 24) & 0xFF), + (uint8_t)((dec_v0 >> 8) & 0xFF), + (uint8_t)((dec_v0 >> 16) & 0xFF), + (uint8_t)(dec_v0 & 0xFF), + (uint8_t)((dec_v1 >> 24) & 0xFF), + (uint8_t)((dec_v1 >> 16) & 0xFF), + }; + uint16_t crc16 = psa_calculate_crc16_bf2(crc_buffer, 6); + uint16_t expected_crc = (uint16_t)(dec_v1 & 0xFFFF); + if(crc16 == expected_crc) { + psa_fill_bf_state_from_buffer(state, buffer); + state->progress_current = + (PSA_BF1_END - PSA_BF1_START) + (counter - PSA_BF2_START); + state->status = PSA_BF_STATUS_FOUND; + return; + } + } + } + + state->status = PSA_BF_STATUS_NOT_FOUND; +} + +int32_t psa_brute_force_thread_entry(void* arg) { + PsaBfState* state = arg; + psa_brute_force_run(state); + return 0; +} + +static bool parse_hex_key_str(const char* str, uint64_t* out) { + *out = 0; + size_t len = str ? strlen(str) : 0; + if(len > 16) len = 16; + for(size_t i = 0; i < len; i++) { + char c = str[i]; + if(c == ' ') continue; + uint8_t nibble; + if(c >= '0' && c <= '9') nibble = c - '0'; + else if(c >= 'A' && c <= 'F') nibble = c - 'A' + 10; + else if(c >= 'a' && c <= 'f') nibble = c - 'a' + 10; + else return false; + *out = (*out << 4) | nibble; + } + return true; +} + +bool psa_bf_state_from_flipper_format(PsaBfState* state, FlipperFormat* ff) { + furi_check(state); + furi_check(ff); + FuriString* temp = furi_string_alloc(); + bool ok = false; + do { + flipper_format_rewind(ff); + if(!flipper_format_read_string(ff, "Key", temp)) break; + uint64_t key1 = 0; + if(!parse_hex_key_str(furi_string_get_cstr(temp), &key1)) break; + state->key1_low = (uint32_t)(key1 & 0xFFFFFFFF); + state->key1_high = (uint32_t)((key1 >> 32) & 0xFFFFFFFF); + + if(!flipper_format_read_string(ff, "Key_2", temp)) break; + uint64_t key2 = 0; + if(!parse_hex_key_str(furi_string_get_cstr(temp), &key2)) break; + state->key2_low = (uint16_t)(key2 & 0xFFFF); + + state->cancel = 0; + state->progress_current = 0; + state->progress_total = 0; + state->status = PSA_BF_STATUS_IDLE; + ok = true; + } while(false); + furi_string_free(temp); + return ok; +} + static bool psa_direct_xor_decrypt(SubGhzProtocolDecoderPSA* instance, uint8_t* buffer) { psa_calculate_checksum(buffer); uint8_t checksum = buffer[11]; @@ -1008,6 +1201,56 @@ static bool psa_direct_xor_decrypt(SubGhzProtocolDecoderPSA* instance, uint8_t* return false; } +static void psa_handle_decoded_frame(SubGhzProtocolDecoderPSA* instance, uint8_t direct_xor_mode) { + instance->key2_low = instance->decode_data_low; + instance->key2_high = instance->decode_data_high; + instance->validation_field = (uint16_t)(instance->decode_data_low & 0xFFFF); + instance->decrypted_type = 0; + instance->decrypted_button = 0; + instance->decrypted_serial = 0; + instance->decrypted_counter = 0; + instance->decrypted_crc = 0; + instance->decrypted_seed = 0; + instance->decrypted = 0x00; + instance->mode_serialize = + psa_direct_xor_allowed_by_key2((uint8_t)(instance->key2_low >> 8)) ? direct_xor_mode : + 0x36; + instance->status_flag = 0x80; + + FURI_LOG_I( + TAG, + "Signal decoded - Key1:%08lX%08lX Key2:%04X ValField:%04X", + (unsigned long)instance->key1_high, + (unsigned long)instance->key1_low, + (unsigned int)(instance->key2_low & 0xFFFF), + (unsigned int)instance->validation_field); + + uint8_t buffer[PSA_BUFFER_SIZE] = {0}; + psa_setup_byte_buffer(buffer, instance->key1_low, instance->key1_high, instance->key2_low); + if(instance->mode_serialize != 0x36 && psa_direct_xor_decrypt(instance, buffer)) { + instance->mode_serialize = 0x23; + instance->decrypted = 0x50; + FURI_LOG_I( + TAG, + "Direct XOR decryption SUCCESS - Type:0x23 Btn:%02X Ser:%06lX Cnt:%08lX", + (unsigned int)instance->decrypted_button, + (unsigned long)instance->decrypted_serial, + (unsigned long)instance->decrypted_counter); + } else { + instance->decrypted = 0x00; + instance->mode_serialize = 0x36; + FURI_LOG_I( + TAG, + "Direct XOR decryption FAILED - marked for brute force (Type:0x36) - deferred to get_string"); + } + if(instance->base.callback) { + instance->base.callback(&instance->base, instance->base.context); + } + instance->decode_data_low = 0; + instance->decode_data_high = 0; + instance->decode_count_bit = 0; +} + static void psa_decrypt_router(SubGhzProtocolDecoderPSA* instance) { FURI_LOG_I(TAG, "=== DECRYPTION ROUTER CALLED ==="); FURI_LOG_I( @@ -1019,14 +1262,21 @@ static void psa_decrypt_router(SubGhzProtocolDecoderPSA* instance) { (unsigned int)instance->mode_serialize, (unsigned int)instance->decrypted); - uint8_t buffer[48] = {0}; + uint8_t buffer[PSA_BUFFER_SIZE] = {0}; psa_setup_byte_buffer(buffer, instance->key1_low, instance->key1_high, instance->key2_low); + uint8_t key2_high_byte = buffer[8]; uint8_t mode = instance->mode_serialize; if(mode == 1 || mode == 2) { - if(psa_direct_xor_decrypt(instance, buffer)) { + if(!psa_direct_xor_allowed_by_key2(key2_high_byte)) { + mode = 0x36; + FURI_LOG_I( + TAG, + "Key2 high byte 0x%02X fails OG direct-XOR filter -> 0x36 (TEA BF)", + key2_high_byte); + } else if(psa_direct_xor_decrypt(instance, buffer)) { mode = 0x23; FURI_LOG_I(TAG, "Converted mode %d -> 0x23 (Direct XOR)", instance->mode_serialize); } else { @@ -1046,48 +1296,19 @@ static void psa_decrypt_router(SubGhzProtocolDecoderPSA* instance) { } FURI_LOG_W(TAG, "Direct XOR decryption FAILED"); } else if(mode == 0x36) { - FURI_LOG_I(TAG, "Attempting TEA brute force decryption (Type 0x36)"); - uint32_t w0, w1; - psa_prepare_tea_data(buffer, &w0, &w1); - FURI_LOG_D( - TAG, "Prepared TEA data: w0=0x%08lX w1=0x%08lX", (unsigned long)w0, (unsigned long)w1); - - FURI_LOG_I(TAG, "Starting BF1 brute force..."); - if(psa_brute_force_decrypt_bf1(instance, buffer, w0, w1)) { - instance->mode_serialize = 0x36; - instance->decrypted = 0x50; - FURI_LOG_I( - TAG, - "BF1 brute force SUCCESS - Btn:%02X Ser:%06lX Cnt:%08lX", - (unsigned int)instance->decrypted_button, - (unsigned long)instance->decrypted_serial, - (unsigned long)instance->decrypted_counter); - return; - } - FURI_LOG_W(TAG, "BF1 brute force FAILED - trying BF2..."); - if(psa_brute_force_decrypt_bf2(instance, buffer, w0, w1)) { - instance->mode_serialize = 0x36; - instance->decrypted = 0x50; - FURI_LOG_I( - TAG, - "BF2 brute force SUCCESS - Btn:%02X Ser:%06lX Cnt:%08lX", - (unsigned int)instance->decrypted_button, - (unsigned long)instance->decrypted_serial, - (unsigned long)instance->decrypted_counter); - return; - } - FURI_LOG_E(TAG, "Both BF1 and BF2 brute force FAILED"); + FURI_LOG_I(TAG, "TEA BF deferred - use Brute force in receiver info"); + return; } else { - FURI_LOG_I(TAG, "Mode uninitialized (0x00) - trying direct XOR first"); - if(psa_direct_xor_decrypt(instance, buffer)) { + FURI_LOG_I(TAG, "Mode uninitialized (0x00) - trying direct XOR or BF"); + if(psa_direct_xor_allowed_by_key2(key2_high_byte) && psa_direct_xor_decrypt(instance, buffer)) { instance->mode_serialize = 0x23; instance->decrypted = 0x50; FURI_LOG_I(TAG, "Direct XOR decryption SUCCESS"); return; } - FURI_LOG_I(TAG, "Direct XOR failed - trying brute force..."); + FURI_LOG_I(TAG, "Direct XOR skipped or failed - trying brute force..."); uint32_t w0, w1; psa_prepare_tea_data(buffer, &w0, &w1); @@ -1290,58 +1511,7 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati instance->state = new_state; return; } - - uint32_t new_key2_low = instance->decode_data_low; - instance->validation_field = (uint16_t)(instance->decode_data_low & 0xFFFF); - - instance->decrypted_type = 0; - instance->decrypted_button = 0; - instance->decrypted_serial = 0; - instance->decrypted_counter = 0; - instance->decrypted_crc = 0; - instance->decrypted_seed = 0; - instance->decrypted = 0x00; - - instance->key2_low = new_key2_low; - instance->key2_high = instance->decode_data_high; - instance->mode_serialize = 1; - instance->status_flag = 0x80; - - FURI_LOG_I( - TAG, - "Signal decoded - Key1:%08lX%08lX Key2:%04X ValField:%04X", - (unsigned long)instance->key1_high, - (unsigned long)instance->key1_low, - (unsigned int)(instance->key2_low & 0xFFFF), - (unsigned int)instance->validation_field); - - uint8_t buffer[48] = {0}; - psa_setup_byte_buffer( - buffer, instance->key1_low, instance->key1_high, instance->key2_low); - if(psa_direct_xor_decrypt(instance, buffer)) { - instance->mode_serialize = 0x23; - instance->decrypted = 0x50; - FURI_LOG_I( - TAG, - "Direct XOR decryption SUCCESS - Type:0x23 Btn:%02X Ser:%06lX Cnt:%08lX", - (unsigned int)instance->decrypted_button, - (unsigned long)instance->decrypted_serial, - (unsigned long)instance->decrypted_counter); - } else { - instance->decrypted = 0x00; - instance->mode_serialize = 0x36; - FURI_LOG_I( - TAG, - "Direct XOR decryption FAILED - marked for brute force (Type:0x36) - deferred to get_string"); - } - - if(instance->base.callback) { - instance->base.callback(&instance->base, instance->base.context); - } - - instance->decode_data_low = 0; - instance->decode_data_high = 0; - instance->decode_count_bit = 0; + psa_handle_decoded_frame(instance, 1); new_state = PSADecoderState0; instance->state = new_state; return; @@ -1458,57 +1628,10 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati } if(((instance->key1_high >> 16) & 0xF) == 0xA) { - uint32_t new_key2_low = instance->decode_data_low; - instance->validation_field = (uint16_t)(instance->decode_data_low & 0xFFFF); - - instance->decrypted_type = 0; - instance->decrypted_button = 0; - instance->decrypted_serial = 0; - instance->decrypted_counter = 0; - instance->decrypted_crc = 0; - instance->decrypted_seed = 0; - instance->decrypted = 0x00; - - instance->key2_low = new_key2_low; - instance->key2_high = instance->decode_data_high; - instance->status_flag = 0x80; - - FURI_LOG_I( - TAG, - "Signal decoded (alt path) - Key1:%08lX%08lX Key2:%04X ValField:%04X", - (unsigned long)instance->key1_high, - (unsigned long)instance->key1_low, - (unsigned int)(instance->key2_low & 0xFFFF), - (unsigned int)instance->validation_field); - - uint8_t buffer[48] = {0}; - psa_setup_byte_buffer( - buffer, instance->key1_low, instance->key1_high, instance->key2_low); - if(psa_direct_xor_decrypt(instance, buffer)) { - instance->mode_serialize = 0x23; - instance->decrypted = 0x50; - FURI_LOG_I( - TAG, - "Direct XOR decryption SUCCESS - Type:0x23 Btn:%02X Ser:%06lX Cnt:%08lX", - (unsigned int)instance->decrypted_button, - (unsigned long)instance->decrypted_serial, - (unsigned long)instance->decrypted_counter); - } else { - instance->decrypted = 0x00; - FURI_LOG_I( - TAG, - "Direct XOR decryption FAILED - marked for brute force (Type:0x36) - deferred to get_string"); - instance->mode_serialize = 0x36; - } - - if(instance->base.callback) { - instance->base.callback(&instance->base, instance->base.context); - } - - instance->decode_data_low = 0; - instance->decode_data_high = 0; - instance->decode_count_bit = 0; + psa_handle_decoded_frame(instance, 1); new_state = PSADecoderState0; + instance->state = new_state; + return; } else { return; } @@ -1520,8 +1643,8 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati case PSADecoderState3: if(duration >= 250) { - if(duration >= PSA_TE_LONG_250 && duration < 0x12c) { - if(instance->pattern_counter > PSA_PATTERN_THRESHOLD_2) { + if(duration >= PSA_TE_LONG_250 && duration < PSA_TE_LONG_300) { + if(instance->pattern_counter > PSA_PATTERN_THRESHOLD_2) { FURI_LOG_I( TAG, "[State3->State4] Transition detected with pattern_cnt=%lu", @@ -1587,7 +1710,7 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati tolerance = duration - PSA_TE_SHORT_125; if(tolerance < PSA_TOLERANCE_50) { manchester_input = ((level ^ 1) & 0x7f) << 1; - } else if(duration >= PSA_TE_LONG_250 && duration < 0x12c) { + } else if(duration >= PSA_TE_LONG_250 && duration < PSA_TE_LONG_300) { if(level == 0) { manchester_input = 6; } else { @@ -1636,58 +1759,7 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati instance->state = new_state; return; } - - uint32_t new_key2_low = instance->decode_data_low; - instance->validation_field = (uint16_t)(instance->decode_data_low & 0xFFFF); - - instance->decrypted_type = 0; - instance->decrypted_button = 0; - instance->decrypted_serial = 0; - instance->decrypted_counter = 0; - instance->decrypted_crc = 0; - instance->decrypted_seed = 0; - instance->decrypted = 0x00; - - instance->key2_low = new_key2_low; - instance->key2_high = instance->decode_data_high; - instance->mode_serialize = 2; - instance->status_flag = 0x80; - - FURI_LOG_I( - TAG, - "Signal decoded (state4) - Key1:%08lX%08lX Key2:%04X ValField:%04X", - (unsigned long)instance->key1_high, - (unsigned long)instance->key1_low, - (unsigned int)(instance->key2_low & 0xFFFF), - (unsigned int)instance->validation_field); - - uint8_t buffer[48] = {0}; - psa_setup_byte_buffer( - buffer, instance->key1_low, instance->key1_high, instance->key2_low); - if(psa_direct_xor_decrypt(instance, buffer)) { - instance->mode_serialize = 0x23; - instance->decrypted = 0x50; - FURI_LOG_I( - TAG, - "Direct XOR decryption SUCCESS - Type:0x23 Btn:%02X Ser:%06lX Cnt:%08lX", - (unsigned int)instance->decrypted_button, - (unsigned long)instance->decrypted_serial, - (unsigned long)instance->decrypted_counter); - } else { - instance->decrypted = 0x00; - instance->mode_serialize = 0x36; - FURI_LOG_I( - TAG, - "Direct XOR decryption FAILED - marked for brute force (Type:0x36) - deferred to get_string"); - } - - if(instance->base.callback) { - instance->base.callback(&instance->base, instance->base.context); - } - - instance->decode_data_low = 0; - instance->decode_data_high = 0; - instance->decode_count_bit = 0; + psa_handle_decoded_frame(instance, 2); new_state = PSADecoderState0; instance->state = new_state; return; @@ -1777,55 +1849,15 @@ SubGhzProtocolStatus FuriString* temp_str = furi_string_alloc(); do { - if(!flipper_format_read_string(flipper_format, "Key", temp_str)) { - break; - } - - const char* key1_str = furi_string_get_cstr(temp_str); + if(!flipper_format_read_string(flipper_format, "Key", temp_str)) break; uint64_t key1 = 0; - size_t str_len = strlen(key1_str); - for(size_t i = 0; i < str_len && i < 16; i++) { - char c = key1_str[i]; - if(c == ' ') continue; - - uint8_t nibble; - if(c >= '0' && c <= '9') { - nibble = c - '0'; - } else if(c >= 'A' && c <= 'F') { - nibble = c - 'A' + 10; - } else if(c >= 'a' && c <= 'f') { - nibble = c - 'a' + 10; - } else { - break; - } - key1 = (key1 << 4) | nibble; - } + if(!parse_hex_key_str(furi_string_get_cstr(temp_str), &key1)) break; instance->key1_low = (uint32_t)(key1 & 0xFFFFFFFF); instance->key1_high = (uint32_t)((key1 >> 32) & 0xFFFFFFFF); - if(!flipper_format_read_string(flipper_format, "Key_2", temp_str)) { - break; - } - - const char* key2_str = furi_string_get_cstr(temp_str); + if(!flipper_format_read_string(flipper_format, "Key_2", temp_str)) break; uint64_t key2 = 0; - str_len = strlen(key2_str); - for(size_t i = 0; i < str_len && i < 16; i++) { - char c = key2_str[i]; - if(c == ' ') continue; - - uint8_t nibble; - if(c >= '0' && c <= '9') { - nibble = c - '0'; - } else if(c >= 'A' && c <= 'F') { - nibble = c - 'A' + 10; - } else if(c >= 'a' && c <= 'f') { - nibble = c - 'a' + 10; - } else { - break; - } - key2 = (key2 << 4) | nibble; - } + if(!parse_hex_key_str(furi_string_get_cstr(temp_str), &key2)) break; instance->key2_low = (uint32_t)(key2 & 0xFFFFFFFF); instance->key2_high = (uint32_t)((key2 >> 32) & 0xFFFFFFFF); @@ -1890,7 +1922,7 @@ void subghz_protocol_decoder_psa_get_string(void* context, FuriString* output) { "Key2:%04X\r\n" "Btn:%02X\r\n" "Ser:%06lX\r\n" - "Cnt:%08lX\r\n" + "Cnt:%lX\r\n" "CRC:%02X\r\n" "Type:%02X\r\n" "Sd:%06lX", diff --git a/protocols/psa.h b/protocols/psa.h index 29ef96d..f702d6e 100644 --- a/protocols/psa.h +++ b/protocols/psa.h @@ -14,6 +14,37 @@ #define PSA_PROTOCOL_NAME "PSA" +#define PSA_BF_STATUS_IDLE 0 +#define PSA_BF_STATUS_RUNNING 1 +#define PSA_BF_STATUS_FOUND 2 +#define PSA_BF_STATUS_NOT_FOUND 3 +#define PSA_BF_STATUS_CANCELLED 4 + +typedef struct PsaBfState PsaBfState; +struct PsaBfState { + volatile uint8_t cancel; + volatile uint32_t progress_current; + volatile uint32_t progress_total; + volatile uint8_t status; + + uint32_t key1_low; + uint32_t key1_high; + uint16_t key2_low; + + uint8_t decrypted_button; + uint32_t decrypted_serial; + uint32_t decrypted_counter; + uint16_t decrypted_crc; + uint32_t decrypted_seed; + uint8_t decrypted_type; +}; + +void psa_brute_force_run(PsaBfState* state); + +int32_t psa_brute_force_thread_entry(void* arg); + +bool psa_bf_state_from_flipper_format(PsaBfState* state, FlipperFormat* ff); + typedef struct SubGhzProtocolDecoderPSA SubGhzProtocolDecoderPSA; typedef struct SubGhzProtocolEncoderPSA SubGhzProtocolEncoderPSA; diff --git a/protopirate_app.c b/protopirate_app.c index 9c97945..6f9cb80 100644 --- a/protopirate_app.c +++ b/protopirate_app.c @@ -123,6 +123,8 @@ ProtoPirateApp* protopirate_app_alloc() { app->setting = subghz_setting_alloc(); app->loaded_file_path = NULL; app->start_tx_time = 0; + app->psa_bf_state = NULL; + app->psa_bf_thread = NULL; subghz_setting_load(app->setting, EXT_PATH("subghz/assets/setting_user")); LOG_HEAP("After subghz_setting"); @@ -445,6 +447,17 @@ void protopirate_app_free(ProtoPirateApp* app) { view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewReceiver); protopirate_view_receiver_free(app->protopirate_receiver); + if(app->psa_bf_thread) { + if(app->psa_bf_state) app->psa_bf_state->cancel = 1; + furi_thread_join(app->psa_bf_thread); + furi_thread_free(app->psa_bf_thread); + app->psa_bf_thread = NULL; + } + if(app->psa_bf_state) { + free(app->psa_bf_state); + app->psa_bf_state = NULL; + } + // Receiver Info FURI_LOG_D(TAG, "Removing receiver_info view"); view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewReceiverInfo); diff --git a/protopirate_app_i.h b/protopirate_app_i.h index 6853905..7c35df4 100644 --- a/protopirate_app_i.h +++ b/protopirate_app_i.h @@ -24,6 +24,7 @@ #include #include #include "defines.h" +#include "protocols/psa.h" #define PROTOPIRATE_KEYSTORE_DIR_NAME APP_ASSETS_PATH("encrypted") @@ -66,6 +67,8 @@ struct ProtoPirateApp { ProtoPirateSettings settings; uint32_t start_tx_time; uint8_t tx_power; + PsaBfState* psa_bf_state; + FuriThread* psa_bf_thread; }; typedef enum { diff --git a/protopirate_history.h b/protopirate_history.h index ff9caf8..fcaa72d 100644 --- a/protopirate_history.h +++ b/protopirate_history.h @@ -28,3 +28,8 @@ void protopirate_history_get_text_item( SubGhzProtocolDecoderBase* protopirate_history_get_decoder_base(ProtoPirateHistory* instance, uint16_t idx); FlipperFormat* protopirate_history_get_raw_data(ProtoPirateHistory* instance, uint16_t idx); + +void protopirate_history_set_item_str( + ProtoPirateHistory* instance, + uint16_t idx, + const char* str); diff --git a/scenes/protopirate_scene_receiver_info.c b/scenes/protopirate_scene_receiver_info.c index 7fecabe..7350fd1 100644 --- a/scenes/protopirate_scene_receiver_info.c +++ b/scenes/protopirate_scene_receiver_info.c @@ -1,6 +1,7 @@ // scenes/protopirate_scene_receiver_info.c #include "../protopirate_app_i.h" #include "../helpers/protopirate_storage.h" +#include "proto_pirate_icons.h" #define TAG "ProtoPirateReceiverInfo" @@ -9,36 +10,73 @@ static bool is_emu_off = false; static void protopirate_scene_receiver_info_widget_callback( GuiButtonType result, InputType type, - void* context) { - ProtoPirateApp* app = context; - if(type == InputTypeShort || type == InputTypeLong) { - if(result == GuiButtonTypeRight) { - view_dispatcher_send_custom_event( - app->view_dispatcher, ProtoPirateCustomEventReceiverInfoSave); - } -#ifdef ENABLE_EMULATE_FEATURE - else if(result == GuiButtonTypeLeft && !is_emu_off) { - view_dispatcher_send_custom_event( - app->view_dispatcher, ProtoPirateCustomEventReceiverInfoEmulate); - } -#endif + void* context); + +static bool psa_item_needs_bruteforce(ProtoPirateApp* app) { + FlipperFormat* ff = + protopirate_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen); + if(!ff) return false; + FuriString* s = furi_string_alloc(); + flipper_format_rewind(ff); + bool has_key = flipper_format_read_string(ff, "Key", s); + if(!has_key) { + furi_string_free(s); + return false; + } + flipper_format_rewind(ff); + bool has_serial = flipper_format_read_string(ff, "Serial", s); + furi_string_free(s); + return !has_serial; +} + +#define PSA_BF_PROGRESS_BAR_X 62 +#define PSA_BF_PROGRESS_BAR_W 64 +#define PSA_BF_PROGRESS_BAR_Y 24 +#define PSA_BF_PROGRESS_BAR_H 8 + +static void protopirate_receiver_info_show_bf_progress(ProtoPirateApp* app) { + widget_reset(app->widget); + widget_add_icon_element(app->widget, 0, 5, &I_DolphinWait_59x54); + widget_add_string_element( + app->widget, 62, 0, AlignLeft, AlignTop, FontPrimary, "Bruteforcing..."); + PsaBfState* s = app->psa_bf_state; + uint32_t cur = s->progress_current; + uint32_t total = s->progress_total; + uint8_t pct = total ? (uint8_t)((uint32_t)((uint64_t)cur * 100 / total)) : 0; + if(pct > 100) pct = 100; + + FuriString* pct_str = furi_string_alloc_printf("%u%%", pct); + widget_add_string_element( + app->widget, 62, 12, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(pct_str)); + furi_string_free(pct_str); + + widget_add_rect_element( + app->widget, + PSA_BF_PROGRESS_BAR_X, + PSA_BF_PROGRESS_BAR_Y, + PSA_BF_PROGRESS_BAR_W, + PSA_BF_PROGRESS_BAR_H, + 2, + false); + if(pct > 0) { + uint8_t fill_w = (PSA_BF_PROGRESS_BAR_W * (uint32_t)pct) / 100; + if(fill_w < 2) fill_w = 2; + widget_add_rect_element( + app->widget, + PSA_BF_PROGRESS_BAR_X + 1, + PSA_BF_PROGRESS_BAR_Y + 1, + fill_w - 1, + PSA_BF_PROGRESS_BAR_H - 2, + 1, + true); } } -void protopirate_scene_receiver_info_on_enter(void* context) { - furi_check(context); - ProtoPirateApp* app = context; - - // Always reset per-enter to avoid stale static state - is_emu_off = false; - +static void protopirate_receiver_info_build_normal_widget(ProtoPirateApp* app) { widget_reset(app->widget); - FuriString* text; - text = furi_string_alloc(); - + FuriString* text = furi_string_alloc(); protopirate_history_get_text_item_menu(app->txrx->history, text, app->txrx->idx_menu_chosen); - widget_add_string_element( app->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, furi_string_get_cstr(text)); @@ -52,81 +90,51 @@ void protopirate_scene_receiver_info_on_enter(void* context) { FuriString* protocol = furi_string_alloc(); flipper_format_rewind(ff); if(flipper_format_read_string(ff, "Protocol", protocol)) { - if(furi_string_cmp_str(protocol, "PSA") == 0) { - is_psa = true; - } - if(furi_string_cmp_str(protocol, "Scher-Khan") == 0) { - is_emu_off = true; - } else if(furi_string_cmp_str(protocol, "Kia V5") == 0) { - is_emu_off = true; - } else if(furi_string_cmp_str(protocol, "Kia V6") == 0) { - is_emu_off = true; - } else { - is_emu_off = false; - } + if(furi_string_cmp_str(protocol, "PSA") == 0) is_psa = true; + if(furi_string_cmp_str(protocol, "Scher-Khan") == 0) is_emu_off = true; + else if(furi_string_cmp_str(protocol, "Kia V5") == 0) is_emu_off = true; + else if(furi_string_cmp_str(protocol, "Kia V6") == 0) is_emu_off = true; + else is_emu_off = false; } furi_string_free(protocol); } - // Skip the first line (protocol name + Xbits) since it's already shown as the title const char* text_str = furi_string_get_cstr(text); const char* first_newline = strchr(text_str, '\r'); if(first_newline) { - // Skip \r\n text_str = first_newline + 1; - if(*text_str == '\n') { - text_str++; - } + if(*text_str == '\n') text_str++; } else { - // Try \n only first_newline = strchr(text_str, '\n'); - if(first_newline) { - text_str = first_newline + 1; - } + if(first_newline) text_str = first_newline + 1; } if(is_psa) { FuriString* reformatted = furi_string_alloc(); const char* current = text_str; - while(*current) { const char* line_end = strchr(current, '\r'); - if(!line_end) { - line_end = strchr(current, '\n'); - } - if(!line_end) { - line_end = current + strlen(current); - } + if(!line_end) line_end = strchr(current, '\n'); + if(!line_end) line_end = current + strlen(current); if(strncmp(current, "Ser:", 4) == 0) { size_t ser_len = line_end - current; furi_string_cat_printf(reformatted, "%.*s", (int)ser_len, current); - const char* next_line = line_end; if(*next_line == '\r') next_line++; if(*next_line == '\n') next_line++; - if(strncmp(next_line, "Cnt:", 4) == 0) { const char* cnt_end = strchr(next_line, '\r'); - if(!cnt_end) { - cnt_end = strchr(next_line, '\n'); - } - if(!cnt_end) { - cnt_end = next_line + strlen(next_line); - } - - size_t cnt_len = cnt_end - next_line; - furi_string_cat_printf(reformatted, " %.*s\r\n", (int)cnt_len, next_line); - + if(!cnt_end) cnt_end = strchr(next_line, '\n'); + if(!cnt_end) cnt_end = next_line + strlen(next_line); + furi_string_cat_printf(reformatted, " %.*s\r\n", (int)(cnt_end - next_line), next_line); current = cnt_end; - if(*current == '\r') current++; - if(*current == '\n') current++; } else { furi_string_cat_printf(reformatted, "\r\n"); current = line_end; - if(*current == '\r') current++; - if(*current == '\n') current++; } + if(*current == '\r') current++; + if(*current == '\n') current++; } else { size_t line_len = line_end - current; furi_string_cat_printf(reformatted, "%.*s\r\n", (int)line_len, current); @@ -134,78 +142,274 @@ void protopirate_scene_receiver_info_on_enter(void* context) { if(*current == '\r') current++; if(*current == '\n') current++; } - if(*current == '\0') break; } - widget_add_string_multiline_element( - app->widget, - 0, - 11, - AlignLeft, - AlignTop, - FontSecondary, - furi_string_get_cstr(reformatted)); + app->widget, 0, 11, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(reformatted)); furi_string_free(reformatted); } else { widget_add_string_multiline_element( app->widget, 0, 11, AlignLeft, AlignTop, FontSecondary, text_str); } -#ifdef ENABLE_EMULATE_FEATURE - // Add emulate button on the left - if(!is_emu_off) { + bool psa_needs_bf = is_psa && psa_item_needs_bruteforce(app); + if(psa_needs_bf) { widget_add_button_element( - app->widget, - GuiButtonTypeLeft, - "Emulate", - protopirate_scene_receiver_info_widget_callback, - app); + app->widget, GuiButtonTypeLeft, "Brute force", + protopirate_scene_receiver_info_widget_callback, app); + } +#ifdef ENABLE_EMULATE_FEATURE + else if(!is_emu_off) { + widget_add_button_element( + app->widget, GuiButtonTypeLeft, "Emulate", + protopirate_scene_receiver_info_widget_callback, app); } #endif - // Add save button on the right widget_add_button_element( - app->widget, - GuiButtonTypeRight, - "Save", - protopirate_scene_receiver_info_widget_callback, - app); + app->widget, GuiButtonTypeRight, "Save", + protopirate_scene_receiver_info_widget_callback, app); furi_string_free(text); +} +static void protopirate_receiver_info_show_bf_result( + ProtoPirateApp* app, + uint8_t status, + PsaBfState* s) { + UNUSED(s); + widget_reset(app->widget); + const char* title = + (status == PSA_BF_STATUS_FOUND) ? "Found!" : + (status == PSA_BF_STATUS_CANCELLED) ? "Cancelled" : "Not found"; + if(status == PSA_BF_STATUS_FOUND) { + widget_add_icon_element(app->widget, 0, 3, &I_DolphinDone_80x58); + widget_add_string_element( + app->widget, 82, 32, AlignLeft, AlignCenter, FontPrimary, title); + widget_add_button_element( + app->widget, + GuiButtonTypeCenter, + "OK", + protopirate_scene_receiver_info_widget_callback, + app); + } else if(status == PSA_BF_STATUS_CANCELLED) { + widget_add_string_element(app->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, title); + widget_add_icon_element(app->widget, (128 - 45) / 2, 14, &I_WarningDolphin_45x42); + } else { + widget_add_string_element(app->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, title); + } +} + +static void protopirate_scene_receiver_info_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + ProtoPirateApp* app = context; + if(type == InputTypeShort || type == InputTypeLong) { + if(result == GuiButtonTypeRight) { + view_dispatcher_send_custom_event( + app->view_dispatcher, ProtoPirateCustomEventReceiverInfoSave); + } + else if(result == GuiButtonTypeLeft) { + + if(!app->psa_bf_thread && psa_item_needs_bruteforce(app)) { + view_dispatcher_send_custom_event( + app->view_dispatcher, ProtoPirateCustomEventReceiverInfoBruteforceStart); + } +#ifdef ENABLE_EMULATE_FEATURE + else if(!is_emu_off) { + view_dispatcher_send_custom_event( + app->view_dispatcher, ProtoPirateCustomEventReceiverInfoEmulate); + } +#endif + } + else if(result == GuiButtonTypeCenter) { + + view_dispatcher_send_custom_event( + app->view_dispatcher, ProtoPirateCustomEventReceiverInfoBruteforceCancel); + } + } +} + +void protopirate_scene_receiver_info_on_enter(void* context) { + furi_check(context); + ProtoPirateApp* app = context; + + is_emu_off = false; + + if(app->psa_bf_thread && app->psa_bf_state) { + if(app->psa_bf_state->status == PSA_BF_STATUS_RUNNING) { + protopirate_receiver_info_show_bf_progress(app); + } else { + protopirate_receiver_info_show_bf_result( + app, app->psa_bf_state->status, app->psa_bf_state); + } + view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewWidget); + return; + } + + protopirate_receiver_info_build_normal_widget(app); view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewWidget); } +static void psa_bf_finish_and_show_result(ProtoPirateApp* app) { + PsaBfState* s = app->psa_bf_state; + uint8_t status = s->status; + if(app->psa_bf_thread) { + furi_thread_join(app->psa_bf_thread); + furi_thread_free(app->psa_bf_thread); + app->psa_bf_thread = NULL; + } + if(status == PSA_BF_STATUS_FOUND) { + FlipperFormat* ff = + protopirate_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen); + if(ff) { + flipper_format_rewind(ff); + flipper_format_insert_or_update_uint32(ff, "Serial", &s->decrypted_serial, 1); + uint32_t btn = s->decrypted_button; + flipper_format_insert_or_update_uint32(ff, "Btn", &btn, 1); + flipper_format_insert_or_update_uint32(ff, "Cnt", &s->decrypted_counter, 1); + uint32_t type = s->decrypted_type; + flipper_format_insert_or_update_uint32(ff, "Type", &type, 1); + uint32_t crc_val = s->decrypted_crc; + flipper_format_insert_or_update_uint32(ff, "CRC", &crc_val, 1); + flipper_format_insert_or_update_uint32(ff, "Seed", &s->decrypted_seed, 1); + } + FuriString* new_str = furi_string_alloc_printf( + "PSA 128bit\r\n" + "Key1:%08lX%08lX\r\n" + "Key2:%04X\r\n" + "Btn:%02X\r\n" + "Ser:%06lX\r\n" + "Cnt:%lX\r\n" + "Type:%02X\r\n" + "Sd:%06lX", + (unsigned long)s->key1_high, (unsigned long)s->key1_low, + (unsigned int)(s->key2_low & 0xFFFF), + (unsigned int)s->decrypted_button, + (unsigned long)s->decrypted_serial, + (unsigned long)s->decrypted_counter, + (unsigned int)s->decrypted_type, + (unsigned long)s->decrypted_seed); + protopirate_history_set_item_str( + app->txrx->history, app->txrx->idx_menu_chosen, furi_string_get_cstr(new_str)); + furi_string_free(new_str); + } + if(status == PSA_BF_STATUS_FOUND) { + + protopirate_receiver_info_show_bf_result(app, status, s); + + } else { + free(app->psa_bf_state); + app->psa_bf_state = NULL; + protopirate_receiver_info_show_bf_result(app, status, NULL); + } +} + bool protopirate_scene_receiver_info_on_event(void* context, SceneManagerEvent event) { ProtoPirateApp* app = context; bool consumed = false; + if(event.type == SceneManagerEventTypeBack) { + if(app->psa_bf_state && app->psa_bf_state->status == PSA_BF_STATUS_FOUND) { + + protopirate_receiver_info_build_normal_widget(app); + free(app->psa_bf_state); + app->psa_bf_state = NULL; + consumed = true; + } else if(app->psa_bf_thread && app->psa_bf_state && + app->psa_bf_state->status == PSA_BF_STATUS_RUNNING) { + app->psa_bf_state->cancel = 1; + consumed = true; + } + return consumed; + } + + if(event.type == SceneManagerEventTypeTick) { + if(app->psa_bf_thread && app->psa_bf_state) { + if(app->psa_bf_state->status == PSA_BF_STATUS_RUNNING) { + protopirate_receiver_info_show_bf_progress(app); + consumed = true; + } else { + psa_bf_finish_and_show_result(app); + consumed = true; + } + } + return consumed; + } + if(event.type == SceneManagerEventTypeCustom) { - if(event.event == ProtoPirateCustomEventReceiverInfoSave) { - // Get the flipper format from history + if(event.event == ProtoPirateCustomEventReceiverInfoBruteforceStart) { FlipperFormat* ff = protopirate_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen); + if(!ff || !psa_item_needs_bruteforce(app)) { + consumed = true; + return consumed; + } + PsaBfState* state = malloc(sizeof(PsaBfState)); + if(!state) { + notification_message(app->notifications, &sequence_error); + consumed = true; + return consumed; + } + if(!psa_bf_state_from_flipper_format(state, ff)) { + free(state); + notification_message(app->notifications, &sequence_error); + consumed = true; + return consumed; + } + app->psa_bf_state = state; + app->psa_bf_thread = furi_thread_alloc_ex( + "PsaBf", 2048, psa_brute_force_thread_entry, state); + if(!app->psa_bf_thread) { + free(state); + app->psa_bf_state = NULL; + notification_message(app->notifications, &sequence_error); + consumed = true; + return consumed; + } + furi_thread_start(app->psa_bf_thread); + protopirate_receiver_info_show_bf_progress(app); + consumed = true; + return consumed; + } + if(event.event == ProtoPirateCustomEventReceiverInfoBruteforceCancel) { + if(app->psa_bf_state && app->psa_bf_state->status == PSA_BF_STATUS_FOUND) { + + protopirate_receiver_info_build_normal_widget(app); + free(app->psa_bf_state); + app->psa_bf_state = NULL; + } else if(app->psa_bf_state && app->psa_bf_state->status == PSA_BF_STATUS_RUNNING) { + app->psa_bf_state->cancel = 1; + } else { + if(app->psa_bf_state) { + psa_bf_finish_and_show_result(app); + } + scene_manager_previous_scene(app->scene_manager); + } + consumed = true; + return consumed; + } + + if(event.event == ProtoPirateCustomEventReceiverInfoSave) { + FlipperFormat* ff = + protopirate_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen); if(ff) { - // Extract protocol name FuriString* protocol = furi_string_alloc(); flipper_format_rewind(ff); if(!flipper_format_read_string(ff, "Protocol", protocol)) { furi_string_set_str(protocol, "Unknown"); } - FuriString* saved_path = furi_string_alloc(); if(protopirate_storage_save_capture( ff, furi_string_get_cstr(protocol), saved_path)) { - // Show success notification notification_message(app->notifications, &sequence_success); FURI_LOG_I(TAG, "Saved to: %s", furi_string_get_cstr(saved_path)); } else { notification_message(app->notifications, &sequence_error); FURI_LOG_E(TAG, "Save failed"); } - furi_string_free(protocol); furi_string_free(saved_path); } @@ -213,26 +417,16 @@ bool protopirate_scene_receiver_info_on_event(void* context, SceneManagerEvent e } #ifdef ENABLE_EMULATE_FEATURE else if(event.event == ProtoPirateCustomEventReceiverInfoEmulate && !is_emu_off) { - // Get the flipper format from history FlipperFormat* ff = protopirate_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen); - if(ff) { - // Save to temp file (will be deleted on emulate exit) if(protopirate_storage_save_temp(ff)) { FURI_LOG_I(TAG, "Saved temp for emulate"); - - // Set the temp file path for emulate scene - if(app->loaded_file_path) { - furi_string_free(app->loaded_file_path); - } + if(app->loaded_file_path) furi_string_free(app->loaded_file_path); app->loaded_file_path = furi_string_alloc_set_str(PROTOPIRATE_TEMP_FILE); - - // Go to emulate scene scene_manager_next_scene(app->scene_manager, ProtoPirateSceneEmulate); } else { notification_message(app->notifications, &sequence_error); - FURI_LOG_E(TAG, "Failed to save temp for emulate"); } } else { FURI_LOG_E(TAG, "No flipper format data for index %d", app->txrx->idx_menu_chosen);