mirror of
https://protopirate.net/ProtoPirate/ProtoPirate.git
synced 2026-03-30 14:55:48 +00:00
@@ -147,6 +147,29 @@ static bool protopirate_storage_write_capture_data(
|
||||
if(flipper_format_read_uint32(flipper_format, "Type", &uint32_value, 1))
|
||||
flipper_format_write_uint32(save_file, "Type", &uint32_value, 1);
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, "Seed", &uint32_value, 1))
|
||||
flipper_format_write_uint32(save_file, "Seed", &uint32_value, 1);
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint8_t val_field[2];
|
||||
if(flipper_format_read_hex(flipper_format, "ValidationField", val_field, 2)) {
|
||||
flipper_format_write_hex(save_file, "ValidationField", val_field, 2);
|
||||
} else {
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, "ValidationField", &uint32_value, 1))
|
||||
flipper_format_write_uint32(save_file, "ValidationField", &uint32_value, 1);
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_string(flipper_format, "Key_2", string_value))
|
||||
flipper_format_write_string(save_file, "Key_2", string_value);
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint8_t key1_buf[8];
|
||||
if(flipper_format_read_hex(flipper_format, "Key1", key1_buf, 8))
|
||||
flipper_format_write_hex(save_file, "Key1", key1_buf, 8);
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, "Check", &uint32_value, 1))
|
||||
flipper_format_write_uint32(save_file, "Check", &uint32_value, 1);
|
||||
|
||||
662
protocols/psa.c
662
protocols/psa.c
@@ -93,6 +93,19 @@ struct SubGhzProtocolEncoderPSA {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint32_t key1_low;
|
||||
uint32_t key1_high;
|
||||
uint16_t validation_field;
|
||||
uint32_t key2_low;
|
||||
uint32_t counter;
|
||||
uint8_t button;
|
||||
uint8_t type;
|
||||
uint8_t seed;
|
||||
uint8_t mode;
|
||||
uint32_t serial;
|
||||
uint16_t crc;
|
||||
bool is_running;
|
||||
};
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_psa_decoder = {
|
||||
@@ -107,11 +120,11 @@ const SubGhzProtocolDecoder subghz_protocol_psa_decoder = {
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_psa_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
.alloc = subghz_protocol_encoder_psa_alloc,
|
||||
.free = subghz_protocol_encoder_psa_free,
|
||||
.deserialize = subghz_protocol_encoder_psa_deserialize,
|
||||
.stop = subghz_protocol_encoder_psa_stop,
|
||||
.yield = subghz_protocol_encoder_psa_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol psa_protocol = {
|
||||
@@ -123,29 +136,529 @@ const SubGhzProtocol psa_protocol = {
|
||||
.encoder = &subghz_protocol_psa_encoder,
|
||||
};
|
||||
|
||||
static void psa_setup_byte_buffer(uint8_t* buffer, uint32_t key1_low, uint32_t key1_high, uint32_t key2_low);
|
||||
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 void psa_second_stage_xor_encrypt(uint8_t* buffer) {
|
||||
|
||||
uint8_t E6 = buffer[8];
|
||||
uint8_t E7 = buffer[9];
|
||||
|
||||
uint8_t P[6];
|
||||
P[0] = buffer[2];
|
||||
P[1] = buffer[3];
|
||||
P[2] = buffer[4];
|
||||
P[3] = buffer[5];
|
||||
P[4] = buffer[6];
|
||||
P[5] = buffer[7];
|
||||
|
||||
uint8_t E5 = P[5] ^ E7 ^ E6;
|
||||
uint8_t E0 = P[2] ^ E5;
|
||||
uint8_t E2 = P[4] ^ E0;
|
||||
uint8_t E4 = P[3] ^ E2;
|
||||
uint8_t E3 = P[0] ^ E5;
|
||||
uint8_t E1 = P[1] ^ E3;
|
||||
|
||||
buffer[2] = E0;
|
||||
buffer[3] = E1;
|
||||
buffer[4] = E2;
|
||||
buffer[5] = E3;
|
||||
buffer[6] = E4;
|
||||
buffer[7] = E5;
|
||||
}
|
||||
|
||||
static void psa_build_buffer_mode23(SubGhzProtocolEncoderPSA* instance, uint8_t* buffer, uint8_t* preserve_buffer01) {
|
||||
|
||||
FURI_LOG_I(TAG, "=== MODE 0x23 ENCRYPTION ===");
|
||||
FURI_LOG_I(TAG, "Input: Ser:%06lX Cnt:%08lX CRC:%02X Btn:%01X",
|
||||
(unsigned long)instance->serial, (unsigned long)instance->counter,
|
||||
(unsigned int)instance->crc, (unsigned int)instance->button);
|
||||
|
||||
memset(buffer, 0, 48);
|
||||
|
||||
buffer[2] = (uint8_t)((instance->serial >> 16) & 0xFF);
|
||||
buffer[3] = (uint8_t)((instance->serial >> 8) & 0xFF);
|
||||
buffer[4] = (uint8_t)(instance->serial & 0xFF);
|
||||
buffer[5] = (uint8_t)((instance->counter >> 8) & 0xFF);
|
||||
buffer[6] = (uint8_t)(instance->counter & 0xFF);
|
||||
buffer[7] = (uint8_t)(instance->crc & 0xFF);
|
||||
buffer[8] = (uint8_t)(instance->button & 0xF);
|
||||
|
||||
uint8_t original_buffer9 = 0;
|
||||
uint8_t original_buffer8 = 0;
|
||||
bool has_original_key2 = (instance->key2_low != 0);
|
||||
if(has_original_key2) {
|
||||
original_buffer9 = (uint8_t)(instance->key2_low & 0xFF);
|
||||
original_buffer8 = (uint8_t)((instance->key2_low >> 8) & 0xFF);
|
||||
buffer[9] = original_buffer9;
|
||||
FURI_LOG_D(TAG, "Original Key2: 0x%04X, buffer[8]=0x%02X buffer[9]=0x%02X, preserving buffer[9]",
|
||||
(unsigned int)instance->key2_low, original_buffer8, original_buffer9);
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "No original Key2, will find valid buffer[9]");
|
||||
}
|
||||
|
||||
FURI_LOG_D(TAG, "Plaintext buffer[2-9]: %02X %02X %02X %02X %02X %02X %02X %02X",
|
||||
buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7], buffer[8], buffer[9]);
|
||||
|
||||
uint8_t initial_plaintext[6];
|
||||
initial_plaintext[0] = buffer[2];
|
||||
initial_plaintext[1] = buffer[3];
|
||||
initial_plaintext[2] = buffer[4];
|
||||
initial_plaintext[3] = buffer[5];
|
||||
initial_plaintext[4] = buffer[6];
|
||||
initial_plaintext[5] = buffer[7];
|
||||
uint8_t initial_button = buffer[8] & 0xF;
|
||||
|
||||
bool found = false;
|
||||
uint8_t buffer9_to_use = has_original_key2 ? original_buffer9 : 0;
|
||||
uint8_t buffer9_end = has_original_key2 ? original_buffer9 + 1 : 255;
|
||||
|
||||
for(uint8_t buffer9_try = buffer9_to_use; buffer9_try < buffer9_end && !found; buffer9_try++) {
|
||||
for(uint8_t buffer8_high_try = 0; buffer8_high_try < 16 && !found; buffer8_high_try++) {
|
||||
|
||||
buffer[2] = initial_plaintext[0];
|
||||
buffer[3] = initial_plaintext[1];
|
||||
buffer[4] = initial_plaintext[2];
|
||||
buffer[5] = initial_plaintext[3];
|
||||
buffer[6] = initial_plaintext[4];
|
||||
buffer[7] = initial_plaintext[5];
|
||||
buffer[8] = initial_button | (buffer8_high_try << 4);
|
||||
buffer[9] = buffer9_try;
|
||||
|
||||
psa_second_stage_xor_encrypt(buffer);
|
||||
|
||||
psa_calculate_checksum(buffer);
|
||||
uint8_t checksum_after = buffer[11];
|
||||
uint8_t key2_high_after = checksum_after & 0xF0;
|
||||
|
||||
uint8_t validation = (checksum_after ^ buffer[8]) & 0xF0;
|
||||
if(validation == 0) {
|
||||
buffer[8] = (buffer[8] & 0x0F) | key2_high_after;
|
||||
buffer[13] = buffer[9] ^ buffer[8];
|
||||
found = true;
|
||||
FURI_LOG_D(TAG, "Found valid Key2: buffer[8]=0x%02X buffer[9]=0x%02X", buffer[8], buffer[9]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!found) {
|
||||
FURI_LOG_W(TAG, "Brute force failed, using default approach");
|
||||
buffer[2] = initial_plaintext[0];
|
||||
buffer[3] = initial_plaintext[1];
|
||||
buffer[4] = initial_plaintext[2];
|
||||
buffer[5] = initial_plaintext[3];
|
||||
buffer[6] = initial_plaintext[4];
|
||||
buffer[7] = initial_plaintext[5];
|
||||
buffer[8] = initial_button;
|
||||
buffer[9] = has_original_key2 ? original_buffer9 : 0x23;
|
||||
|
||||
psa_second_stage_xor_encrypt(buffer);
|
||||
psa_calculate_checksum(buffer);
|
||||
uint8_t checksum_after = buffer[11];
|
||||
uint8_t key2_high_after = checksum_after & 0xF0;
|
||||
buffer[8] = (buffer[8] & 0x0F) | key2_high_after;
|
||||
buffer[13] = buffer[9] ^ buffer[8];
|
||||
}
|
||||
|
||||
if(preserve_buffer01 != NULL) {
|
||||
buffer[0] = preserve_buffer01[0];
|
||||
buffer[1] = preserve_buffer01[1];
|
||||
FURI_LOG_D(TAG, "Preserved buffer[0-1] from original Key1: %02X %02X", buffer[0], buffer[1]);
|
||||
} else {
|
||||
buffer[0] = buffer[2] ^ buffer[6];
|
||||
buffer[1] = buffer[3] ^ buffer[7];
|
||||
FURI_LOG_D(TAG, "Derived buffer[0-1]: %02X %02X", buffer[0], buffer[1]);
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "Encrypted buffer[0-9]: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X",
|
||||
buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7], buffer[8], buffer[9]);
|
||||
}
|
||||
|
||||
static void psa_build_buffer_mode36(SubGhzProtocolEncoderPSA* instance, uint8_t* buffer, uint8_t* preserve_buffer01) {
|
||||
FURI_LOG_I(TAG, "=== MODE 0x36 ENCRYPTION ===");
|
||||
FURI_LOG_I(TAG, "Input: Ser:%06lX Cnt:%08lX CRC:%02X Btn:%01X",
|
||||
(unsigned long)instance->serial, (unsigned long)instance->counter,
|
||||
(unsigned int)instance->crc, (unsigned int)instance->button);
|
||||
|
||||
memset(buffer, 0, 48);
|
||||
|
||||
uint32_t v0 = ((instance->serial & 0xFFFFFF) << 8) |
|
||||
((instance->counter >> 24) & 0xFF);
|
||||
uint32_t v1 = ((instance->counter & 0xFFFFFF) << 8) |
|
||||
((instance->button & 0xF) << 4) |
|
||||
(instance->crc & 0xFF);
|
||||
|
||||
FURI_LOG_D(TAG, "Packed v0:0x%08lX v1:0x%08lX (before CRC)", (unsigned long)v0, (unsigned long)v1);
|
||||
|
||||
uint8_t crc = psa_calculate_tea_crc(v0, v1);
|
||||
v1 = (v1 & 0xFFFFFF00) | crc;
|
||||
|
||||
FURI_LOG_D(TAG, "Calculated CRC: 0x%02X, v1 after CRC: 0x%08lX", (unsigned int)crc, (unsigned long)v1);
|
||||
|
||||
uint32_t bf_counter = PSA_BF1_START | (instance->serial & 0xFFFFFF);
|
||||
FURI_LOG_D(TAG, "BF counter: 0x%08lX (BF1_START | serial)", (unsigned long)bf_counter);
|
||||
|
||||
uint32_t working_key[4];
|
||||
|
||||
uint32_t wk2 = PSA_BF1_CONST_U4;
|
||||
uint32_t wk3 = bf_counter;
|
||||
psa_tea_encrypt(&wk2, &wk3, PSA_BF1_KEY_SCHEDULE);
|
||||
|
||||
uint32_t wk0 = (bf_counter << 8) | 0x0E;
|
||||
uint32_t wk1 = PSA_BF1_CONST_U5;
|
||||
psa_tea_encrypt(&wk0, &wk1, PSA_BF1_KEY_SCHEDULE);
|
||||
|
||||
working_key[0] = wk0;
|
||||
working_key[1] = wk1;
|
||||
working_key[2] = wk2;
|
||||
working_key[3] = wk3;
|
||||
|
||||
psa_tea_encrypt(&v0, &v1, working_key);
|
||||
|
||||
FURI_LOG_D(TAG, "TEA encrypted v0:0x%08lX v1:0x%08lX", (unsigned long)v0, (unsigned long)v1);
|
||||
FURI_LOG_D(TAG, "Working key: %08lX %08lX %08lX %08lX",
|
||||
(unsigned long)working_key[0], (unsigned long)working_key[1],
|
||||
(unsigned long)working_key[2], (unsigned long)working_key[3]);
|
||||
|
||||
psa_unpack_tea_result_to_buffer(buffer, v0, v1);
|
||||
|
||||
if(preserve_buffer01 != NULL) {
|
||||
buffer[0] = preserve_buffer01[0];
|
||||
buffer[1] = preserve_buffer01[1];
|
||||
FURI_LOG_D(TAG, "Preserved buffer[0-1] from original Key1: %02X %02X", buffer[0], buffer[1]);
|
||||
} else {
|
||||
buffer[0] = buffer[2] ^ buffer[6];
|
||||
buffer[1] = buffer[3] ^ buffer[7];
|
||||
FURI_LOG_D(TAG, "Derived buffer[0-1]: %02X %02X", buffer[0], buffer[1]);
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "Encrypted buffer[0-9]: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X",
|
||||
buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7], buffer[8], buffer[9]);
|
||||
}
|
||||
|
||||
static void psa_encoder_build_upload(SubGhzProtocolEncoderPSA* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
FURI_LOG_I(TAG, "=== ENCODER BUILD UPLOAD ===");
|
||||
FURI_LOG_I(TAG, "Mode: 0x%02X, Serial:%06lX Counter:%08lX CRC:%02X Button:%01X",
|
||||
(unsigned int)instance->mode, (unsigned long)instance->serial,
|
||||
(unsigned long)instance->counter, (unsigned int)instance->crc,
|
||||
(unsigned int)instance->button);
|
||||
|
||||
uint8_t buffer[48] = {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};
|
||||
psa_setup_byte_buffer(orig_buffer, instance->key1_low, instance->key1_high, instance->key2_low);
|
||||
preserve_buffer01[0] = orig_buffer[0];
|
||||
preserve_buffer01[1] = orig_buffer[1];
|
||||
preserve_ptr = preserve_buffer01;
|
||||
FURI_LOG_D(TAG, "Original Key1: %08lX%08lX, preserving buffer[0-1]: %02X %02X",
|
||||
(unsigned long)instance->key1_high, (unsigned long)instance->key1_low,
|
||||
preserve_buffer01[0], preserve_buffer01[1]);
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "No original Key1, will derive buffer[0-1]");
|
||||
}
|
||||
|
||||
if(instance->mode == 0x23) {
|
||||
psa_build_buffer_mode23(instance, buffer, preserve_ptr);
|
||||
} else if(instance->mode == 0x36) {
|
||||
psa_build_buffer_mode36(instance, buffer, preserve_ptr);
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Unknown mode: 0x%02X", instance->mode);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t key1_high = ((uint32_t)buffer[0] << 24) | ((uint32_t)buffer[1] << 16) |
|
||||
((uint32_t)buffer[2] << 8) | (uint32_t)buffer[3];
|
||||
uint32_t key1_low = ((uint32_t)buffer[4] << 24) | ((uint32_t)buffer[5] << 16) |
|
||||
((uint32_t)buffer[6] << 8) | (uint32_t)buffer[7];
|
||||
uint16_t validation_field = ((uint16_t)buffer[8] << 8) | (uint16_t)buffer[9];
|
||||
|
||||
FURI_LOG_I(TAG, "=== ENCRYPTED KEYS ===");
|
||||
FURI_LOG_I(TAG, "Key1: %08lX%08lX", (unsigned long)key1_high, (unsigned long)key1_low);
|
||||
FURI_LOG_I(TAG, "Key2: %04X", (unsigned int)validation_field);
|
||||
|
||||
size_t index = 0;
|
||||
uint32_t te = PSA_TE_LONG_250;
|
||||
|
||||
for(int i = 0; i < 80; i++) {
|
||||
if(index >= instance->encoder.size_upload - 2) break;
|
||||
instance->encoder.upload[index++] = level_duration_make(true, te);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, te);
|
||||
}
|
||||
|
||||
uint32_t te_long_transition = subghz_protocol_psa_const.te_long;
|
||||
if(index < instance->encoder.size_upload - 3) {
|
||||
instance->encoder.upload[index++] = level_duration_make(false, te);
|
||||
instance->encoder.upload[index++] = level_duration_make(true, te_long_transition);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, te);
|
||||
}
|
||||
|
||||
uint64_t key1_data = ((uint64_t)key1_high << 32) | key1_low;
|
||||
for(int bit = 63; bit >= 0; bit--) {
|
||||
if(index >= instance->encoder.size_upload - 2) break;
|
||||
bool bit_value = (key1_data >> bit) & 1;
|
||||
if(bit_value) {
|
||||
instance->encoder.upload[index++] = level_duration_make(true, te);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, te);
|
||||
} else {
|
||||
instance->encoder.upload[index++] = level_duration_make(false, te);
|
||||
instance->encoder.upload[index++] = level_duration_make(true, te);
|
||||
}
|
||||
}
|
||||
|
||||
for(int bit = 15; bit >= 0; bit--) {
|
||||
if(index >= instance->encoder.size_upload - 2) break;
|
||||
bool bit_value = (validation_field >> bit) & 1;
|
||||
if(bit_value) {
|
||||
instance->encoder.upload[index++] = level_duration_make(true, te);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, te);
|
||||
} else {
|
||||
instance->encoder.upload[index++] = level_duration_make(false, te);
|
||||
instance->encoder.upload[index++] = level_duration_make(true, te);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t end_duration = PSA_TE_END_1000;
|
||||
if(index < instance->encoder.size_upload - 1) {
|
||||
instance->encoder.upload[index++] = level_duration_make(true, end_duration);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, end_duration);
|
||||
}
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.repeat = 10;
|
||||
|
||||
FURI_LOG_I(TAG, "=== TRANSMISSION PARAMETERS ===");
|
||||
FURI_LOG_I(TAG, "TE: %lu, TE_LONG_TRANS: %lu, TE_END: %lu",
|
||||
(unsigned long)te, (unsigned long)te_long_transition, (unsigned long)end_duration);
|
||||
FURI_LOG_I(TAG, "Upload size: %zu levels, Repeat: %d",
|
||||
instance->encoder.size_upload, instance->encoder.repeat);
|
||||
FURI_LOG_I(TAG, "Key1 data (64-bit): %016llX", (unsigned long long)key1_data);
|
||||
FURI_LOG_I(TAG, "Key2 data (16-bit): %04X", (unsigned int)validation_field);
|
||||
FURI_LOG_I(TAG, "=== END ENCODER BUILD ===");
|
||||
}
|
||||
|
||||
void* subghz_protocol_encoder_psa_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
return NULL;
|
||||
SubGhzProtocolEncoderPSA* instance = malloc(sizeof(SubGhzProtocolEncoderPSA));
|
||||
|
||||
if(instance) {
|
||||
memset(instance, 0, sizeof(SubGhzProtocolEncoderPSA));
|
||||
instance->base.protocol = &psa_protocol;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->encoder.size_upload = 600;
|
||||
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.is_running = false;
|
||||
instance->is_running = false;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_psa_free(void* context) {
|
||||
UNUSED(context);
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderPSA* instance = context;
|
||||
|
||||
if(instance->encoder.upload) {
|
||||
free(instance->encoder.upload);
|
||||
}
|
||||
free(instance);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_psa_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
UNUSED(context);
|
||||
UNUSED(flipper_format);
|
||||
return SubGhzProtocolStatusError;
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderPSA* instance = context;
|
||||
|
||||
FURI_LOG_I(TAG, "=== ENCODER DESERIALIZE ===");
|
||||
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
uint8_t hex_buffer[8];
|
||||
|
||||
do {
|
||||
if(!flipper_format_read_string(flipper_format, "Key", temp_str)) {
|
||||
if(!flipper_format_read_hex(flipper_format, "Key1", hex_buffer, 8)) {
|
||||
FURI_LOG_E(TAG, "Failed to read Key1");
|
||||
break;
|
||||
}
|
||||
instance->key1_low = ((uint32_t)hex_buffer[0] << 24) | ((uint32_t)hex_buffer[1] << 16) |
|
||||
((uint32_t)hex_buffer[2] << 8) | (uint32_t)hex_buffer[3];
|
||||
instance->key1_high = ((uint32_t)hex_buffer[4] << 24) | ((uint32_t)hex_buffer[5] << 16) |
|
||||
((uint32_t)hex_buffer[6] << 8) | (uint32_t)hex_buffer[7];
|
||||
} else {
|
||||
const char* key_str = furi_string_get_cstr(temp_str);
|
||||
uint64_t key1 = 0;
|
||||
size_t str_len = strlen(key_str);
|
||||
for(size_t i = 0; i < str_len && i < 16; i++) {
|
||||
char c = key_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;
|
||||
}
|
||||
instance->key1_low = (uint32_t)(key1 & 0xFFFFFFFF);
|
||||
instance->key1_high = (uint32_t)((key1 >> 32) & 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_string(flipper_format, "Key_2", temp_str)) {
|
||||
const char* key2_str = furi_string_get_cstr(temp_str);
|
||||
uint64_t key2 = 0;
|
||||
size_t 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;
|
||||
}
|
||||
instance->key2_low = (uint32_t)(key2 & 0xFFFFFFFF);
|
||||
instance->validation_field = (uint16_t)(key2 & 0xFFFF);
|
||||
} else {
|
||||
uint32_t val_field_val;
|
||||
if(flipper_format_read_uint32(flipper_format, "ValidationField", &val_field_val, 1)) {
|
||||
instance->validation_field = (uint16_t)(val_field_val & 0xFFFF);
|
||||
instance->key2_low = instance->validation_field;
|
||||
} else {
|
||||
uint8_t val_field[2];
|
||||
if(flipper_format_read_hex(flipper_format, "ValidationField", val_field, 2)) {
|
||||
instance->validation_field = ((uint16_t)val_field[0] << 8) | val_field[1];
|
||||
instance->key2_low = instance->validation_field;
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "ValidationField not found");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_uint32(flipper_format, "Serial", &instance->serial, 1)) {
|
||||
FURI_LOG_E(TAG, "Serial not found");
|
||||
break;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_uint32(flipper_format, "Cnt", &instance->counter, 1)) {
|
||||
FURI_LOG_E(TAG, "Counter not found");
|
||||
break;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t btn_val = 0;
|
||||
if(!flipper_format_read_uint32(flipper_format, "Btn", &btn_val, 1)) {
|
||||
FURI_LOG_E(TAG, "Button not found");
|
||||
break;
|
||||
}
|
||||
instance->button = (uint8_t)(btn_val & 0xFF);
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t type_val = 0;
|
||||
if(!flipper_format_read_uint32(flipper_format, "Type", &type_val, 1)) {
|
||||
FURI_LOG_E(TAG, "Type not found");
|
||||
break;
|
||||
}
|
||||
instance->type = (uint8_t)(type_val & 0xFF);
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t crc_val = 0;
|
||||
if(!flipper_format_read_uint32(flipper_format, "CRC", &crc_val, 1)) {
|
||||
FURI_LOG_E(TAG, "CRC not found");
|
||||
break;
|
||||
}
|
||||
instance->crc = (uint16_t)(crc_val & 0xFFFF);
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t seed_val = 0;
|
||||
if(!flipper_format_read_uint32(flipper_format, "Seed", &seed_val, 1)) {
|
||||
FURI_LOG_E(TAG, "Seed not found");
|
||||
break;
|
||||
}
|
||||
instance->seed = (uint8_t)(seed_val & 0xFF);
|
||||
|
||||
instance->mode = instance->type;
|
||||
if(instance->mode == 0x23 || instance->mode == 0) {
|
||||
instance->mode = 0x23;
|
||||
} else if(instance->mode == 0x36) {
|
||||
instance->mode = 0x36;
|
||||
} else {
|
||||
instance->mode = 0x23;
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "=== LOADED VALUES ===");
|
||||
FURI_LOG_I(TAG, "Key1: %08lX%08lX", (unsigned long)instance->key1_high, (unsigned long)instance->key1_low);
|
||||
FURI_LOG_I(TAG, "Key2: %04X (validation_field: %04X)",
|
||||
(unsigned int)instance->key2_low, (unsigned int)instance->validation_field);
|
||||
FURI_LOG_I(TAG, "Serial: %06lX, Counter: %08lX, CRC: %02X, Button: %01X, Type/Mode: 0x%02X",
|
||||
(unsigned long)instance->serial, (unsigned long)instance->counter,
|
||||
(unsigned int)instance->crc, (unsigned int)instance->button, (unsigned int)instance->mode);
|
||||
|
||||
psa_encoder_build_upload(instance);
|
||||
|
||||
instance->is_running = true;
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
furi_string_free(temp_str);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_psa_stop(void* context) {
|
||||
UNUSED(context);
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderPSA* instance = context;
|
||||
instance->is_running = false;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_psa_yield(void* context) {
|
||||
UNUSED(context);
|
||||
return level_duration_reset();
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderPSA* instance = context;
|
||||
|
||||
if(!instance->is_running || instance->encoder.size_upload == 0) {
|
||||
instance->is_running = false;
|
||||
return level_duration_reset();
|
||||
}
|
||||
|
||||
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
|
||||
instance->encoder.front++;
|
||||
|
||||
if(instance->encoder.front >= instance->encoder.size_upload) {
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.repeat--;
|
||||
if(instance->encoder.repeat <= 0) {
|
||||
instance->is_running = false;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uint32_t psa_abs_diff(uint32_t a, uint32_t b) {
|
||||
@@ -529,6 +1042,7 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati
|
||||
return;
|
||||
}
|
||||
new_state = PSADecoderState3;
|
||||
FURI_LOG_I(TAG, "[State0->State3] Detected te=125 pattern (Type 0x23)");
|
||||
} else {
|
||||
new_state = PSADecoderState1;
|
||||
}
|
||||
@@ -590,6 +1104,8 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati
|
||||
}
|
||||
if(long_diff < 100) {
|
||||
if(instance->pattern_counter > PSA_PATTERN_THRESHOLD_1) {
|
||||
FURI_LOG_I(TAG, "[State1->State2] Transition detected with pattern_cnt=%lu > %d",
|
||||
(unsigned long)instance->pattern_counter, PSA_PATTERN_THRESHOLD_1);
|
||||
new_state = PSADecoderState2;
|
||||
instance->decode_data_low = 0;
|
||||
instance->decode_data_high = 0;
|
||||
@@ -597,6 +1113,11 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati
|
||||
manchester_advance(instance->manchester_state, ManchesterEventReset,
|
||||
&instance->manchester_state, NULL);
|
||||
instance->state = new_state;
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "[State1] LONG(%lu) seen but pattern_cnt=%lu <= %d",
|
||||
(unsigned long)duration,
|
||||
(unsigned long)instance->pattern_counter,
|
||||
PSA_PATTERN_THRESHOLD_1);
|
||||
}
|
||||
instance->pattern_counter = 0;
|
||||
instance->prev_duration = duration;
|
||||
@@ -829,50 +1350,75 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati
|
||||
break;
|
||||
|
||||
case PSADecoderState3:
|
||||
if(level) {
|
||||
if(duration >= PSA_TE_LONG_250 && duration < 0x12c) {
|
||||
if(instance->pattern_counter > PSA_PATTERN_THRESHOLD_2) {
|
||||
FURI_LOG_I(TAG, "[State3->State4] Transition detected with pattern_cnt=%lu > %d",
|
||||
(unsigned long)instance->pattern_counter, PSA_PATTERN_THRESHOLD_2);
|
||||
new_state = PSADecoderState4;
|
||||
instance->decode_data_low = 0;
|
||||
instance->decode_data_high = 0;
|
||||
instance->decode_count_bit = 0;
|
||||
manchester_advance(instance->manchester_state, ManchesterEventReset,
|
||||
&instance->manchester_state, NULL);
|
||||
instance->state = new_state;
|
||||
} else {
|
||||
FURI_LOG_W(TAG, "[State3] %s(%lu) seen but pattern_cnt=%lu <= %d",
|
||||
level ? "HIGH" : "LOW",
|
||||
(unsigned long)duration,
|
||||
(unsigned long)instance->pattern_counter,
|
||||
PSA_PATTERN_THRESHOLD_2);
|
||||
}
|
||||
instance->pattern_counter = 0;
|
||||
instance->prev_duration = duration;
|
||||
return;
|
||||
}
|
||||
|
||||
if(duration < PSA_TE_SHORT_125) {
|
||||
tolerance = PSA_TE_SHORT_125 - duration;
|
||||
if(tolerance < PSA_TOLERANCE_50) {
|
||||
uint32_t prev_diff = psa_abs_diff(prev_dur, PSA_TE_SHORT_125);
|
||||
if(prev_diff <= PSA_TOLERANCE_49) {
|
||||
instance->pattern_counter++;
|
||||
} else {
|
||||
instance->pattern_counter = 0;
|
||||
}
|
||||
instance->prev_duration = duration;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
tolerance = duration - PSA_TE_SHORT_125;
|
||||
if(tolerance < PSA_TOLERANCE_50) {
|
||||
uint32_t prev_diff = psa_abs_diff(prev_dur, PSA_TE_SHORT_125);
|
||||
if(prev_diff <= PSA_TOLERANCE_49) {
|
||||
instance->pattern_counter++;
|
||||
} else {
|
||||
instance->pattern_counter = 0;
|
||||
}
|
||||
instance->prev_duration = duration;
|
||||
return;
|
||||
} else if(duration >= PSA_TE_LONG_250 && duration < 0x12c) {
|
||||
if(instance->pattern_counter > PSA_PATTERN_THRESHOLD_2) {
|
||||
new_state = PSADecoderState4;
|
||||
instance->decode_data_low = 0;
|
||||
instance->decode_data_high = 0;
|
||||
instance->decode_count_bit = 0;
|
||||
manchester_advance(instance->manchester_state, ManchesterEventReset,
|
||||
&instance->manchester_state, NULL);
|
||||
instance->state = new_state;
|
||||
}
|
||||
instance->pattern_counter = 0;
|
||||
instance->prev_duration = duration;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
new_state = PSADecoderState0;
|
||||
if(tolerance < PSA_TOLERANCE_50) {
|
||||
uint32_t prev_diff = psa_abs_diff(prev_dur, PSA_TE_SHORT_125);
|
||||
if(prev_diff <= PSA_TOLERANCE_49) {
|
||||
instance->pattern_counter++;
|
||||
FURI_LOG_D(TAG, "[State3] %s(%lu) pattern_cnt=%lu (prev_diff=%lu)",
|
||||
level ? "HIGH" : "LOW",
|
||||
(unsigned long)duration,
|
||||
(unsigned long)instance->pattern_counter,
|
||||
(unsigned long)prev_diff);
|
||||
} else {
|
||||
if(instance->pattern_counter < 10) {
|
||||
FURI_LOG_D(TAG, "[State3] %s(%lu) reset pattern_cnt (prev_diff=%lu > %d, cnt=%lu)",
|
||||
level ? "HIGH" : "LOW",
|
||||
(unsigned long)duration,
|
||||
(unsigned long)prev_diff,
|
||||
PSA_TOLERANCE_49,
|
||||
(unsigned long)instance->pattern_counter);
|
||||
instance->pattern_counter = 0;
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "[State3] %s(%lu) pattern_cnt kept at %lu (prev_diff=%lu, slight variation)",
|
||||
level ? "HIGH" : "LOW",
|
||||
(unsigned long)duration,
|
||||
(unsigned long)instance->pattern_counter,
|
||||
(unsigned long)prev_diff);
|
||||
}
|
||||
}
|
||||
instance->prev_duration = duration;
|
||||
return;
|
||||
}
|
||||
|
||||
if(duration > 500) {
|
||||
new_state = PSADecoderState0;
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "[State3] %s(%lu) ignored (tolerance=%lu >= %d), staying in State3",
|
||||
level ? "HIGH" : "LOW",
|
||||
(unsigned long)duration,
|
||||
(unsigned long)tolerance,
|
||||
PSA_TOLERANCE_50);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case PSADecoderState4:
|
||||
@@ -1031,6 +1577,26 @@ SubGhzProtocolStatus subghz_protocol_decoder_psa_serialize(
|
||||
snprintf(key2_str, sizeof(key2_str), "%016llX", key2);
|
||||
if(!flipper_format_write_string_cstr(flipper_format, "Key_2", key2_str)) break;
|
||||
|
||||
uint32_t val_field = instance->validation_field;
|
||||
flipper_format_write_uint32(flipper_format, "ValidationField", &val_field, 1);
|
||||
|
||||
if(instance->decrypted == 0x50 && instance->decrypted_type != 0) {
|
||||
flipper_format_write_uint32(flipper_format, "Serial", &instance->decrypted_serial, 1);
|
||||
|
||||
uint32_t btn_temp = instance->decrypted_button;
|
||||
flipper_format_write_uint32(flipper_format, "Btn", &btn_temp, 1);
|
||||
|
||||
flipper_format_write_uint32(flipper_format, "Cnt", &instance->decrypted_counter, 1);
|
||||
|
||||
uint32_t crc_temp = instance->decrypted_crc;
|
||||
flipper_format_write_uint32(flipper_format, "CRC", &crc_temp, 1);
|
||||
|
||||
uint32_t type_temp = instance->decrypted_type;
|
||||
flipper_format_write_uint32(flipper_format, "Type", &type_temp, 1);
|
||||
|
||||
flipper_format_write_uint32(flipper_format, "Seed", &instance->decrypted_seed, 1);
|
||||
}
|
||||
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
|
||||
@@ -40,6 +40,19 @@ void protopirate_scene_receiver_info_on_enter(void* context) {
|
||||
furi_string_reset(text);
|
||||
protopirate_history_get_text_item(app->txrx->history, text, app->txrx->idx_menu_chosen);
|
||||
|
||||
bool is_psa = false;
|
||||
FlipperFormat* ff = protopirate_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen);
|
||||
if(ff) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
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');
|
||||
@@ -57,8 +70,71 @@ void protopirate_scene_receiver_info_on_enter(void* context) {
|
||||
}
|
||||
}
|
||||
|
||||
widget_add_string_multiline_element(
|
||||
app->widget, 0, 11, AlignLeft, AlignTop, FontSecondary, text_str);
|
||||
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(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);
|
||||
|
||||
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++;
|
||||
}
|
||||
} else {
|
||||
|
||||
size_t line_len = line_end - current;
|
||||
furi_string_cat_printf(reformatted, "%.*s\r\n", (int)line_len, current);
|
||||
current = line_end;
|
||||
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));
|
||||
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
|
||||
|
||||
@@ -148,6 +148,26 @@ void protopirate_scene_saved_info_on_enter(void* context) {
|
||||
furi_string_cat_printf(info_str, "Type: %02X\n", (uint8_t)temp_data);
|
||||
}
|
||||
|
||||
flipper_format_rewind(ff);
|
||||
if(flipper_format_read_string(ff, "Key", temp_str)) {
|
||||
furi_string_cat_printf(info_str, "Key1: %s\n", furi_string_get_cstr(temp_str));
|
||||
}
|
||||
|
||||
flipper_format_rewind(ff);
|
||||
if(flipper_format_read_string(ff, "Key_2", temp_str)) {
|
||||
furi_string_cat_printf(info_str, "Key2: %s\n", furi_string_get_cstr(temp_str));
|
||||
}
|
||||
|
||||
flipper_format_rewind(ff);
|
||||
if(flipper_format_read_uint32(ff, "ValidationField", &temp_data, 1)) {
|
||||
furi_string_cat_printf(info_str, "ValField: %04X\n", (uint16_t)temp_data);
|
||||
}
|
||||
|
||||
flipper_format_rewind(ff);
|
||||
if(flipper_format_read_uint32(ff, "Seed", &temp_data, 1)) {
|
||||
furi_string_cat_printf(info_str, "Seed: %02X\n", (uint8_t)temp_data);
|
||||
}
|
||||
|
||||
flipper_format_rewind(ff);
|
||||
if(flipper_format_read_string(ff, "Manufacture", temp_str)) {
|
||||
furi_string_cat_printf(info_str, "Manufacture: %s\n", furi_string_get_cstr(temp_str));
|
||||
|
||||
Reference in New Issue
Block a user