PSA faster BF and GUI

This commit is contained in:
0mega committed 2026-03-04 13:17:05 +01:00
1 parent 36c8dce72d
commit a54b0ab4a0
9 files changed
+634 -354

No files matched your search

+2
View File
@@ -26,6 +26,8 @@ typedef enum {
// File management
ProtoPirateCustomEventReceiverInfoSave,
ProtoPirateCustomEventReceiverInfoEmulate,
ProtoPirateCustomEventReceiverInfoBruteforceStart,
ProtoPirateCustomEventReceiverInfoBruteforceCancel,
ProtoPirateCustomEventSavedInfoDelete,
// Emulator
ProtoPirateCustomEventSavedInfoEmulate,
Binary file not shown.

After

Width:  |  Height:  |  Size: 448 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 B

+278 -246
View File
@@ -22,10 +22,33 @@ static const SubGhzBlockConst subghz_protocol_psa_const = {
#define PSA_MAX_BITS 0x79
#define PSA_KEY1_BITS 0x40
#define PSA_KEY2_BITS 0x50
#define PSA_BUFFER_SIZE 48
#define PSA_TE_LONG_300 0x12c
#define TEA_DELTA 0x9E3779B9U
#define TEA_ROUNDS 32
typedef struct {
uint32_t s0[TEA_ROUNDS];
uint32_t s1[TEA_ROUNDS];
} PsaTeaSchedule;
static void psa_tea_build_schedule(const uint32_t* key, PsaTeaSchedule* out) {
for(int i = 0; i < TEA_ROUNDS; i++) {
uint32_t sum0 = (uint32_t)((uint64_t)i * TEA_DELTA);
uint32_t sum1 = (uint32_t)((uint64_t)(i + 1) * TEA_DELTA);
out->s0[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",
+31
View File
@@ -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;
+13
View File
@@ -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);
+3
View File
@@ -24,6 +24,7 @@
#include <lib/subghz/subghz_file_encoder_worker.h>
#include <dialogs/dialogs.h>
#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 {
+5
View File
@@ -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);
+302 -108
View File
@@ -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);