Compare commits

...

11 Commits

Author SHA1 Message Date
grugnoymeme
b041177398 fixing problem of exit confirm not working when entering subghz app with right arrow ALREADY WIP
All checks were successful
Build Dev Firmware / build (push) Successful in 6m35s
2026-03-17 22:18:26 +01:00
grugnoymeme
f347d5a976 better display of datas after decrypt for PSA
All checks were successful
Build Dev Firmware / build (push) Successful in 6m54s
2026-03-17 21:08:18 +01:00
grugnoymeme
3a6da87288 hide Emulate choice in subghz saved menu for psa encrypted sub files bc useless, and moved PSA decrypt first
All checks were successful
Build Dev Firmware / build (push) Successful in 6m41s
2026-03-17 20:17:21 +01:00
grugnoymeme
5d94639d81 Merge remote-tracking branch 'refs/remotes/origin/main' 2026-03-17 20:12:54 +01:00
grugnoymeme
5dcfc48e10 restored holtekS protocols bc super commons, fixed fiat spa, scheduled tea iterations for faster bf1 in psa, fixed progress bar's issue on 1 percent in psa decrypt, fmt protocol items 2026-03-17 20:07:07 +01:00
Andrea Santaniello
20a95b2fec Apprently using the bt thread to save the keys causes a crash due to the low memory
All checks were successful
Build Dev Firmware / build (push) Successful in 6m40s
2026-03-17 17:24:03 +01:00
Andrea Santaniello
3605669cc5 Fixing crash on finding first good candidate
All checks were successful
Build Dev Firmware / build (push) Successful in 6m38s
2026-03-17 15:28:16 +01:00
Andrea Santaniello
fb1c28a0dd Update subghz_scene_kl_bf_cleanup.c
All checks were successful
Build Dev Firmware / build (push) Successful in 6m42s
2026-03-17 14:23:56 +01:00
Andrea Santaniello
64a971e806 Keeloq Bruteforcer updates
Some checks failed
Build Dev Firmware / build (push) Failing after 2m37s
2026-03-17 14:01:27 +01:00
d4rks1d33
12db96a8ab Fix counter brute force, open window to 500ms per transmission making more stable
All checks were successful
Build Dev Firmware / build (push) Successful in 6m34s
2026-03-16 21:41:33 -03:00
d4rks1d33
4b50b8b70c Fix warning UI
All checks were successful
Build Dev Firmware / build (push) Successful in 6m35s
2026-03-16 20:09:04 -03:00
19 changed files with 1056 additions and 887 deletions

View File

@@ -32,4 +32,5 @@ ADD_SCENE(subghz, keeloq_key_edit, KeeloqKeyEdit)
ADD_SCENE(subghz, psa_decrypt, PsaDecrypt)
ADD_SCENE(subghz, keeloq_decrypt, KeeloqDecrypt)
ADD_SCENE(subghz, keeloq_bf2, KeeloqBf2)
ADD_SCENE(subghz, kl_bf_cleanup, KlBfCleanup)
ADD_SCENE(subghz, counter_bf, CounterBf)

View File

@@ -5,9 +5,10 @@
#define TAG "SubGhzCounterBf"
// How many ticks to wait between transmissions (1 tick ~100ms)
#define COUNTER_BF_TX_INTERVAL_TICKS 3
#define COUNTER_BF_TX_INTERVAL_TICKS 5
typedef enum {
CounterBfStateWarning,
CounterBfStateIdle,
CounterBfStateRunning,
CounterBfStateStopped,
@@ -22,8 +23,16 @@ typedef struct {
uint32_t tick_wait;
} CounterBfContext;
#define CounterBfEventStart (0xC0)
#define CounterBfEventStop (0xC1)
#define CounterBfEventStart (0xC0)
#define CounterBfEventStop (0xC1)
#define CounterBfEventWarningOk (0xC2)
static void counter_bf_warning_callback(GuiButtonType result, InputType type, void* context) {
SubGhz* subghz = context;
if(result == GuiButtonTypeCenter && type == InputTypeShort) {
view_dispatcher_send_custom_event(subghz->view_dispatcher, CounterBfEventWarningOk);
}
}
static void counter_bf_widget_callback(GuiButtonType result, InputType type, void* context) {
SubGhz* subghz = context;
@@ -32,18 +41,35 @@ static void counter_bf_widget_callback(GuiButtonType result, InputType type, voi
}
}
static void counter_bf_draw_warning(SubGhz* subghz) {
widget_reset(subghz->widget);
widget_add_string_multiline_element(
subghz->widget,
64,
20,
AlignCenter,
AlignCenter,
FontSecondary,
"WARNING:\nThis may desync\nyour fob!");
widget_add_button_element(
subghz->widget,
GuiButtonTypeCenter,
"OK",
counter_bf_warning_callback,
subghz);
}
static void counter_bf_draw(SubGhz* subghz, CounterBfContext* ctx) {
widget_reset(subghz->widget);
FuriString* str = furi_string_alloc();
furi_string_printf(
str,
"WARNING: THIS MAY BE DESYNC YOUR FOB\n"
"Counter BruteForce\n"
"Cnt: 0x%08lX\n"
"Start: 0x%08lX\n"
"Cnt: 0x%06lX\n"
"Start: 0x%06lX\n"
"Sent: %lu",
ctx->current_cnt,
ctx->start_cnt,
ctx->current_cnt & 0xFFFFFF,
ctx->start_cnt & 0xFFFFFF,
ctx->packets_sent);
widget_add_string_multiline_element(
subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(str));
@@ -58,14 +84,12 @@ static void counter_bf_draw(SubGhz* subghz, CounterBfContext* ctx) {
}
static void counter_bf_save(SubGhz* subghz, CounterBfContext* ctx) {
// Escribir el Cnt final directamente en el archivo .sub en disco.
// No usar subghz_save_protocol_to_file() porque ese serializa el estado
// actual del encoder (que puede tener el Cnt ya incrementado internamente).
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* file_fff = flipper_format_buffered_file_alloc(storage);
if(flipper_format_buffered_file_open_existing(
file_fff, furi_string_get_cstr(subghz->file_path))) {
if(!flipper_format_update_uint32(file_fff, "Cnt", &ctx->current_cnt, 1)) {
uint32_t cnt = ctx->current_cnt & 0xFFFFFF;
if(!flipper_format_update_uint32(file_fff, "Cnt", &cnt, 1)) {
FURI_LOG_E(TAG, "Failed to update Cnt in .sub file");
}
} else {
@@ -78,16 +102,15 @@ static void counter_bf_save(SubGhz* subghz, CounterBfContext* ctx) {
static void counter_bf_send(SubGhz* subghz, CounterBfContext* ctx) {
subghz_txrx_stop(subghz->txrx);
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
uint32_t delta = (ctx->current_cnt - ctx->start_cnt) & 0xFFFFFF;
furi_hal_subghz_set_rolling_counter_mult((int32_t)delta);
subghz_block_generic_global_counter_override_set(ctx->current_cnt & 0xFFFFFF);
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
uint32_t repeat = 20;
flipper_format_rewind(fff);
flipper_format_update_uint32(fff, "Repeat", &repeat, 1);
// Actualizar Cnt DESPUES de Repeat (update es secuencial en el buffer)
flipper_format_rewind(fff);
flipper_format_update_uint32(fff, "Cnt", &ctx->current_cnt, 1);
subghz_tx_start(subghz, fff);
ctx->packets_sent++;
@@ -99,42 +122,38 @@ void subghz_scene_counter_bf_on_enter(void* context) {
CounterBfContext* ctx = malloc(sizeof(CounterBfContext));
memset(ctx, 0, sizeof(CounterBfContext));
ctx->state = CounterBfStateIdle;
ctx->state = CounterBfStateWarning;
ctx->step = 1;
furi_hal_subghz_set_rolling_counter_mult(0);
subghz_key_load(subghz, furi_string_get_cstr(subghz->file_path), false);
// FIX: Leer el Cnt DIRECTAMENTE del archivo en disco con un FlipperFormat
// propio, completamente separado del fff en memoria (que puede tener el Cnt
// modificado por TXs previas y no refleja el estado real del .sub).
{
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* file_fff = flipper_format_buffered_file_alloc(storage);
if(flipper_format_buffered_file_open_existing(
file_fff, furi_string_get_cstr(subghz->file_path))) {
uint32_t cnt = 0;
if(flipper_format_read_uint32(file_fff, "Cnt", &cnt, 1)) {
ctx->current_cnt = cnt;
ctx->start_cnt = cnt;
} else {
FURI_LOG_W(TAG, "Cnt field not found in file");
}
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
flipper_format_rewind(fff);
uint32_t cnt = 0;
if(flipper_format_read_uint32(fff, "Cnt", &cnt, 1)) {
ctx->current_cnt = cnt & 0xFFFFFF;
ctx->start_cnt = cnt & 0xFFFFFF;
} else {
FURI_LOG_E(TAG, "Failed to open .sub file for Cnt read");
FURI_LOG_W(TAG, "Cnt not in fff after key_load, reading from disk");
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* file_fff = flipper_format_buffered_file_alloc(storage);
if(flipper_format_buffered_file_open_existing(
file_fff, furi_string_get_cstr(subghz->file_path))) {
if(flipper_format_read_uint32(file_fff, "Cnt", &cnt, 1)) {
ctx->current_cnt = cnt & 0xFFFFFF;
ctx->start_cnt = cnt & 0xFFFFFF;
}
}
flipper_format_free(file_fff);
furi_record_close(RECORD_STORAGE);
}
flipper_format_free(file_fff);
furi_record_close(RECORD_STORAGE);
}
scene_manager_set_scene_state(
subghz->scene_manager, SubGhzSceneCounterBf, (uint32_t)(uintptr_t)ctx);
// Deshabilitar auto-increment del protocolo para controlar el Cnt manualmente
furi_hal_subghz_set_rolling_counter_mult(0);
// Recargar el protocolo DESPUES de haber leído el Cnt del disco,
// para preparar el fff para TX sin que pise nuestro valor leído.
subghz_key_load(subghz, furi_string_get_cstr(subghz->file_path), false);
counter_bf_draw(subghz, ctx);
counter_bf_draw_warning(subghz);
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget);
}
@@ -145,15 +164,21 @@ bool subghz_scene_counter_bf_on_event(void* context, SceneManagerEvent event) {
if(!ctx) return false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == CounterBfEventWarningOk) {
ctx->state = CounterBfStateIdle;
counter_bf_draw(subghz, ctx);
return true;
}
if(event.event == CounterBfEventStart) {
if(ctx->state == CounterBfStateWarning) return true;
if(ctx->state != CounterBfStateRunning) {
ctx->state = CounterBfStateRunning;
ctx->tick_wait = 0;
subghz->state_notifications = SubGhzNotificationStateTx;
counter_bf_send(subghz, ctx);
} else {
// FIX 2: Al detener, guardar el contador actual en el .sub
// para que al volver a emular manualmente continúe desde acá.
ctx->state = CounterBfStateStopped;
subghz_txrx_stop(subghz->txrx);
subghz->state_notifications = SubGhzNotificationStateIDLE;
@@ -168,19 +193,24 @@ bool subghz_scene_counter_bf_on_event(void* context, SceneManagerEvent event) {
if(ctx->tick_wait > 0) {
ctx->tick_wait--;
} else {
ctx->current_cnt += ctx->step;
ctx->current_cnt = (ctx->current_cnt + ctx->step) & 0xFFFFFF;
counter_bf_send(subghz, ctx);
counter_bf_save(subghz, ctx);
counter_bf_draw(subghz, ctx);
}
}
return true;
} else if(event.type == SceneManagerEventTypeBack) {
if(ctx->state == CounterBfStateWarning) {
furi_hal_subghz_set_rolling_counter_mult(1);
free(ctx);
scene_manager_previous_scene(subghz->scene_manager);
return true;
}
subghz_txrx_stop(subghz->txrx);
subghz->state_notifications = SubGhzNotificationStateIDLE;
// FIX 2 (también en Back): guardar siempre al salir
counter_bf_save(subghz, ctx);
furi_hal_subghz_set_rolling_counter_mult(1);
free(ctx);
scene_manager_previous_scene(subghz->scene_manager);

View File

@@ -5,9 +5,18 @@
enum {
KlBf2IndexLoadSig1,
KlBf2IndexLoadSig2,
KlBf2IndexType,
KlBf2IndexStartBf,
};
static const char* kl_bf2_type_labels[] = {
"Type: Auto (6>7>8)",
"Type: 6 (Serial 1)",
"Type: 7 (Serial 2)",
"Type: 8 (Serial 3)",
};
static const uint8_t kl_bf2_type_values[] = {0, 6, 7, 8};
static bool kl_bf2_extract_key(SubGhz* subghz, uint32_t* out_fix, uint32_t* out_hop) {
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
flipper_format_rewind(fff);
@@ -88,6 +97,17 @@ static void kl_bf2_rebuild_menu(SubGhz* subghz) {
subghz->submenu, label2, KlBf2IndexLoadSig2,
kl_bf2_submenu_callback, subghz);
int type_idx = 0;
for(int i = 0; i < 4; i++) {
if(kl_bf2_type_values[i] == subghz->keeloq_bf2.learn_type) {
type_idx = i;
break;
}
}
submenu_add_item(
subghz->submenu, kl_bf2_type_labels[type_idx], KlBf2IndexType,
kl_bf2_submenu_callback, subghz);
if(subghz->keeloq_bf2.sig1_loaded && subghz->keeloq_bf2.sig2_loaded) {
submenu_add_item(
subghz->submenu, "Start BF", KlBf2IndexStartBf,
@@ -102,6 +122,7 @@ void subghz_scene_keeloq_bf2_on_enter(void* context) {
subghz->keeloq_bf2.sig1_loaded = false;
subghz->keeloq_bf2.sig2_loaded = false;
subghz->keeloq_bf2.learn_type = 0;
kl_bf2_rebuild_menu(subghz);
}
@@ -194,6 +215,16 @@ bool subghz_scene_keeloq_bf2_on_event(void* context, SceneManagerEvent event) {
kl_bf2_rebuild_menu(subghz);
return true;
} else if(event.event == KlBf2IndexType) {
uint8_t cur = subghz->keeloq_bf2.learn_type;
if(cur == 0) cur = 6;
else if(cur == 6) cur = 7;
else if(cur == 7) cur = 8;
else cur = 0;
subghz->keeloq_bf2.learn_type = cur;
kl_bf2_rebuild_menu(subghz);
return true;
} else if(event.event == KlBf2IndexStartBf) {
if(!subghz->keeloq_bf2.sig1_loaded || !subghz->keeloq_bf2.sig2_loaded) {
return true;

View File

@@ -1,11 +1,15 @@
#include "../subghz_i.h"
#include "../helpers/subghz_txrx_i.h"
#include <lib/subghz/protocols/keeloq.h>
#include <lib/subghz/protocols/keeloq_common.h>
#include <lib/subghz/environment.h>
#include <lib/subghz/subghz_keystore.h>
#include <furi.h>
#include <bt/bt_service/bt.h>
#define KL_DECRYPT_EVENT_DONE (0xD2)
#define KL_TOTAL_KEYS 0x100000000ULL
#define KL_DECRYPT_EVENT_DONE (0xD2)
#define KL_DECRYPT_EVENT_CANDIDATE (0xD3)
#define KL_TOTAL_KEYS 0x100000000ULL
#define KL_MSG_BF_REQUEST 0x10
#define KL_MSG_BF_PROGRESS 0x11
@@ -27,8 +31,10 @@ typedef struct {
uint32_t hop2;
uint32_t candidate_count;
uint64_t recovered_mfkey;
uint16_t recovered_type;
uint32_t recovered_cnt;
bool ble_offload;
} KlDecryptCtx;
@@ -52,46 +58,30 @@ static void kl_ble_data_received(uint8_t* data, uint16_t size, void* context) {
} else if(data[0] == KL_MSG_BF_RESULT && size >= 26) {
uint8_t found = data[1];
uint64_t mfkey = 0;
uint64_t devkey = 0;
uint32_t cnt = 0;
uint32_t elapsed_ms = 0;
memcpy(&mfkey, data + 2, 8);
memcpy(&devkey, data + 10, 8);
memcpy(&cnt, data + 18, 4);
memcpy(&elapsed_ms, data + 22, 4);
if(found) {
if(found == 1) {
uint64_t mfkey = 0;
uint32_t cnt = 0;
memcpy(&mfkey, data + 2, 8);
memcpy(&cnt, data + 18, 4);
uint16_t learn_type = (size >= 27) ? data[26] : 6;
furi_string_printf(
ctx->result,
"Key FOUND!\n"
"MfKey:%08lX%08lX\n"
"DevKey:%08lX%08lX\n"
"Cnt:%04lX Sn:%07lX\n"
"Saved to user keys",
(uint32_t)(mfkey >> 32), (uint32_t)(mfkey & 0xFFFFFFFF),
(uint32_t)(devkey >> 32), (uint32_t)(devkey & 0xFFFFFFFF),
cnt, ctx->serial);
FlipperFormat* fff = subghz_txrx_get_fff_data(ctx->subghz->txrx);
flipper_format_rewind(fff);
char mf_str[20];
snprintf(mf_str, sizeof(mf_str), "BF_%07lX", ctx->serial);
flipper_format_insert_or_update_string_cstr(fff, "Manufacture", mf_str);
uint32_t cnt_val = cnt;
flipper_format_rewind(fff);
flipper_format_insert_or_update_uint32(fff, "Cnt", &cnt_val, 1);
ctx->candidate_count++;
ctx->recovered_mfkey = mfkey;
ctx->recovered_type = learn_type;
ctx->success = true;
}
ctx->recovered_cnt = cnt;
view_dispatcher_send_custom_event(ctx->subghz->view_dispatcher, KL_DECRYPT_EVENT_DONE);
subghz_view_keeloq_decrypt_update_candidates(
ctx->subghz->subghz_keeloq_decrypt, ctx->candidate_count);
view_dispatcher_send_custom_event(
ctx->subghz->view_dispatcher, KL_DECRYPT_EVENT_CANDIDATE);
} else if(found == 2) {
ctx->success = (ctx->candidate_count > 0);
view_dispatcher_send_custom_event(
ctx->subghz->view_dispatcher, KL_DECRYPT_EVENT_DONE);
}
}
}
@@ -114,7 +104,7 @@ static bool kl_ble_start_offload(KlDecryptCtx* ctx) {
uint8_t req[18];
req[0] = KL_MSG_BF_REQUEST;
req[1] = 0;
req[1] = ctx->subghz->keeloq_bf2.learn_type;
memcpy(req + 2, &ctx->fix, 4);
memcpy(req + 6, &ctx->hop, 4);
memcpy(req + 10, &ctx->hop2, 4);
@@ -189,28 +179,68 @@ bool subghz_scene_keeloq_decrypt_on_event(void* context, SceneManagerEvent event
if(!ctx) return false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == KL_DECRYPT_EVENT_DONE) {
if(event.event == KL_DECRYPT_EVENT_CANDIDATE) {
if(!subghz->keeloq_keys_manager) {
subghz->keeloq_keys_manager = subghz_keeloq_keys_alloc();
}
char key_name[24];
snprintf(key_name, sizeof(key_name), "BF_%07lX", ctx->serial);
subghz_keeloq_keys_add(
subghz->keeloq_keys_manager,
ctx->recovered_mfkey,
ctx->recovered_type,
key_name);
subghz_keeloq_keys_save(subghz->keeloq_keys_manager);
SubGhzKeystore* env_ks = subghz_environment_get_keystore(
subghz->txrx->environment);
SubGhzKeyArray_t* env_arr = subghz_keystore_get_data(env_ks);
SubGhzKey* entry = SubGhzKeyArray_push_raw(*env_arr);
entry->name = furi_string_alloc_set(key_name);
entry->key = ctx->recovered_mfkey;
entry->type = ctx->recovered_type;
return true;
} else if(event.event == KL_DECRYPT_EVENT_DONE) {
kl_ble_cleanup(ctx);
subghz->keeloq_bf2.sig1_loaded = false;
subghz->keeloq_bf2.sig2_loaded = false;
if(ctx->success) {
subghz_save_protocol_to_file(
subghz,
subghz_txrx_get_fff_data(subghz->txrx),
furi_string_get_cstr(subghz->file_path));
if(!subghz->keeloq_keys_manager) {
subghz->keeloq_keys_manager = subghz_keeloq_keys_alloc();
}
char key_name[24];
snprintf(key_name, sizeof(key_name), "BF_%07lX", ctx->serial);
subghz_keeloq_keys_add(
subghz->keeloq_keys_manager,
ctx->recovered_mfkey,
furi_string_printf(
ctx->result,
"Found %lu candidate(s)\n"
"Last: %08lX%08lX\n"
"Type:%u Cnt:%04lX\n"
"Saved to user keys",
ctx->candidate_count,
(uint32_t)(ctx->recovered_mfkey >> 32),
(uint32_t)(ctx->recovered_mfkey & 0xFFFFFFFF),
ctx->recovered_type,
key_name);
subghz_keeloq_keys_save(subghz->keeloq_keys_manager);
ctx->recovered_cnt);
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
flipper_format_rewind(fff);
char mf_str[20];
snprintf(mf_str, sizeof(mf_str), "BF_%07lX", ctx->serial);
flipper_format_insert_or_update_string_cstr(fff, "Manufacture", mf_str);
uint32_t cnt_val = ctx->recovered_cnt;
flipper_format_rewind(fff);
flipper_format_insert_or_update_uint32(fff, "Cnt", &cnt_val, 1);
if(ctx->hop2 != 0) {
flipper_format_rewind(fff);
flipper_format_insert_or_update_uint32(fff, "Hop2", &ctx->hop2, 1);
}
if(subghz_path_is_file(subghz->file_path)) {
subghz_save_protocol_to_file(
subghz,
subghz_txrx_get_fff_data(subghz->txrx),
furi_string_get_cstr(subghz->file_path));
}
subghz_view_keeloq_decrypt_set_result(
subghz->subghz_keeloq_decrypt, true, furi_string_get_cstr(ctx->result));
@@ -230,13 +260,8 @@ bool subghz_scene_keeloq_decrypt_on_event(void* context, SceneManagerEvent event
uint8_t cancel_msg = KL_MSG_BF_CANCEL;
bt_custom_data_tx(bt, &cancel_msg, 1);
furi_record_close(RECORD_BT);
kl_ble_cleanup(ctx);
}
ctx->cancel = true;
furi_string_free(ctx->result);
free(ctx);
scene_manager_set_scene_state(
subghz->scene_manager, SubGhzSceneKeeloqDecrypt, 0);
scene_manager_previous_scene(subghz->scene_manager);
return true;
}

View File

@@ -0,0 +1,141 @@
#include "../subghz_i.h"
#include <lib/subghz/protocols/keeloq_common.h>
typedef struct {
uint32_t serial;
uint32_t fix;
uint32_t hop;
uint32_t hop2;
uint8_t btn;
uint16_t disc;
size_t bf_indices[32];
size_t bf_count;
size_t valid_indices[32];
size_t valid_count;
} KlCleanupCtx;
static bool kl_cleanup_validate_hop(uint64_t key, uint32_t hop, uint8_t btn, uint16_t disc) {
uint32_t dec = subghz_protocol_keeloq_common_decrypt(hop, key);
if((dec >> 28) != btn) return false;
uint16_t dec_disc = (dec >> 16) & 0x3FF;
if(dec_disc == disc) return true;
if((dec_disc & 0xFF) == (disc & 0xFF)) return true;
return false;
}
static bool kl_cleanup_validate_key(uint64_t key, uint32_t hop1, uint32_t hop2, uint8_t btn, uint16_t disc) {
if(!kl_cleanup_validate_hop(key, hop1, btn, disc)) return false;
if(hop2 == 0) return true;
if(!kl_cleanup_validate_hop(key, hop2, btn, disc)) return false;
uint32_t dec1 = subghz_protocol_keeloq_common_decrypt(hop1, key);
uint32_t dec2 = subghz_protocol_keeloq_common_decrypt(hop2, key);
uint16_t cnt1 = dec1 & 0xFFFF;
uint16_t cnt2 = dec2 & 0xFFFF;
int diff = (int)cnt2 - (int)cnt1;
return (diff >= 1 && diff <= 256);
}
void subghz_scene_kl_bf_cleanup_on_enter(void* context) {
SubGhz* subghz = context;
KlCleanupCtx* ctx = malloc(sizeof(KlCleanupCtx));
memset(ctx, 0, sizeof(KlCleanupCtx));
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
flipper_format_rewind(fff);
uint8_t key_data[8] = {0};
if(flipper_format_read_hex(fff, "Key", key_data, 8)) {
ctx->fix = ((uint32_t)key_data[0] << 24) | ((uint32_t)key_data[1] << 16) |
((uint32_t)key_data[2] << 8) | key_data[3];
ctx->hop = ((uint32_t)key_data[4] << 24) | ((uint32_t)key_data[5] << 16) |
((uint32_t)key_data[6] << 8) | key_data[7];
ctx->serial = ctx->fix & 0x0FFFFFFF;
ctx->btn = ctx->fix >> 28;
ctx->disc = ctx->serial & 0x3FF;
}
ctx->hop2 = 0;
flipper_format_rewind(fff);
flipper_format_read_uint32(fff, "Hop2", &ctx->hop2, 1);
scene_manager_set_scene_state(
subghz->scene_manager, SubGhzSceneKlBfCleanup, (uint32_t)(uintptr_t)ctx);
if(!subghz->keeloq_keys_manager) {
subghz->keeloq_keys_manager = subghz_keeloq_keys_alloc();
}
char bf_name[24];
snprintf(bf_name, sizeof(bf_name), "BF_%07lX", ctx->serial);
size_t user_count = subghz_keeloq_keys_user_count(subghz->keeloq_keys_manager);
ctx->bf_count = 0;
ctx->valid_count = 0;
for(size_t i = 0; i < user_count && ctx->bf_count < 32; i++) {
SubGhzKey* k = subghz_keeloq_keys_get(subghz->keeloq_keys_manager, i);
if(!k || !k->name) continue;
const char* name = furi_string_get_cstr(k->name);
if(strcmp(name, bf_name) == 0) {
ctx->bf_indices[ctx->bf_count] = i;
if(kl_cleanup_validate_key(k->key, ctx->hop, ctx->hop2, ctx->btn, ctx->disc)) {
ctx->valid_indices[ctx->valid_count++] = i;
}
ctx->bf_count++;
}
}
FuriString* msg = furi_string_alloc();
if(ctx->bf_count == 0) {
furi_string_set_str(msg, "No BF candidate keys\nfound for this serial.");
} else if(ctx->bf_count == 1) {
furi_string_set_str(msg, "Only 1 BF key exists.\nNothing to clean up.");
} else if(ctx->valid_count == 1) {
size_t deleted = 0;
for(int i = (int)ctx->bf_count - 1; i >= 0; i--) {
if(ctx->bf_indices[i] != ctx->valid_indices[0]) {
subghz_keeloq_keys_delete(subghz->keeloq_keys_manager, ctx->bf_indices[i]);
deleted++;
}
}
subghz_keeloq_keys_save(subghz->keeloq_keys_manager);
furi_string_printf(msg,
"Cleaned %u keys.\nKept valid key:\n%s",
deleted, bf_name);
} else if(ctx->valid_count == 0) {
furi_string_printf(msg,
"%u BF keys found\nbut none validates\nhop. Kept all.",
ctx->bf_count);
} else {
furi_string_printf(msg,
"%u BF keys, %u valid.\nCannot auto-select.\nKept all.",
ctx->bf_count, ctx->valid_count);
}
widget_add_text_scroll_element(subghz->widget, 0, 0, 128, 64, furi_string_get_cstr(msg));
furi_string_free(msg);
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget);
}
bool subghz_scene_kl_bf_cleanup_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
return false;
}
void subghz_scene_kl_bf_cleanup_on_exit(void* context) {
SubGhz* subghz = context;
KlCleanupCtx* ctx = (KlCleanupCtx*)(uintptr_t)scene_manager_get_scene_state(
subghz->scene_manager, SubGhzSceneKlBfCleanup);
if(ctx) {
free(ctx);
scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneKlBfCleanup, 0);
}
widget_reset(subghz->widget);
}

View File

@@ -45,25 +45,24 @@ bool subghz_scene_need_saving_on_event(void* context, SceneManagerEvent event) {
subghz_rx_key_state_set(subghz, SubGhzRxKeyStateBack);
scene_manager_previous_scene(subghz->scene_manager);
return true;
} else if(event.event == SubGhzCustomEventSceneExit) {
} else if(event.event == SubGhzCustomEventSceneExit) {
SubGhzRxKeyState state = subghz_rx_key_state_get(subghz);
subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE);
if(state == SubGhzRxKeyStateExit) {
if(scene_manager_has_previous_scene(subghz->scene_manager, SubGhzSceneReadRAW)) {
if(!furi_string_empty(subghz->file_path_tmp)) {
subghz_delete_file(subghz);
}
}
subghz_txrx_set_preset(
subghz->txrx, "AM650", subghz->last_settings->frequency, NULL, 0);
scene_manager_search_and_switch_to_previous_scene(
subghz->scene_manager, SubGhzSceneStart);
if(!scene_manager_search_and_switch_to_previous_scene(
subghz->scene_manager, SubGhzSceneStart)) {
scene_manager_previous_scene(subghz->scene_manager);
}
} else {
scene_manager_previous_scene(subghz->scene_manager);
}
return true;
}
}

View File

@@ -2,10 +2,10 @@
enum SubmenuIndex {
SubmenuIndexEmulate,
SubmenuIndexPsaDecrypt,
SubmenuIndexEdit,
SubmenuIndexDelete,
SubmenuIndexSignalSettings,
SubmenuIndexPsaDecrypt,
SubmenuIndexCounterBf
};
@@ -45,12 +45,23 @@ void subghz_scene_saved_menu_on_enter(void* context) {
}
}
submenu_add_item(
subghz->submenu,
"Emulate",
SubmenuIndexEmulate,
subghz_scene_saved_menu_submenu_callback,
subghz);
if(!is_psa_encrypted) {
submenu_add_item(
subghz->submenu,
"Emulate",
SubmenuIndexEmulate,
subghz_scene_saved_menu_submenu_callback,
subghz);
}
if(is_psa_encrypted) {
submenu_add_item(
subghz->submenu,
"PSA Decrypt",
SubmenuIndexPsaDecrypt,
subghz_scene_saved_menu_submenu_callback,
subghz);
}
submenu_add_item(
subghz->submenu,
@@ -73,15 +84,8 @@ void subghz_scene_saved_menu_on_enter(void* context) {
SubmenuIndexSignalSettings,
subghz_scene_saved_menu_submenu_callback,
subghz);
};
if(is_psa_encrypted) {
submenu_add_item(
subghz->submenu,
"PSA Decrypt",
SubmenuIndexPsaDecrypt,
subghz_scene_saved_menu_submenu_callback,
subghz);
}
if(has_counter) {
submenu_add_item(
subghz->submenu,
@@ -107,6 +111,11 @@ bool subghz_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexEmulate);
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTransmitter);
return true;
} else if(event.event == SubmenuIndexPsaDecrypt) {
scene_manager_set_scene_state(
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexPsaDecrypt);
scene_manager_next_scene(subghz->scene_manager, SubGhzScenePsaDecrypt);
return true;
} else if(event.event == SubmenuIndexDelete) {
scene_manager_set_scene_state(
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexDelete);
@@ -122,11 +131,6 @@ bool subghz_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexSignalSettings);
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSignalSettings);
return true;
} else if(event.event == SubmenuIndexPsaDecrypt) {
scene_manager_set_scene_state(
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexPsaDecrypt);
scene_manager_next_scene(subghz->scene_manager, SubGhzScenePsaDecrypt);
return true;
} else if(event.event == SubmenuIndexCounterBf) {
scene_manager_set_scene_state(
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexCounterBf);

View File

@@ -403,6 +403,7 @@ int32_t subghz_app(void* p) {
subghz->view_dispatcher, subghz->gui, ViewDispatcherTypeFullscreen);
furi_string_set(subghz->file_path, SUBGHZ_APP_FOLDER);
if(subghz_txrx_is_database_loaded(subghz->txrx)) {
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneStart);
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiver);
} else {
scene_manager_set_scene_state(

View File

@@ -121,6 +121,7 @@ struct SubGhz {
bool sig2_loaded;
FuriString* sig1_path;
FuriString* sig2_path;
uint8_t learn_type;
} keeloq_bf2;
};

View File

@@ -19,6 +19,7 @@ typedef struct {
uint32_t eta_sec;
bool done;
bool success;
uint32_t candidates;
FuriString* result_str;
char status_line[40];
} SubGhzKeeloqDecryptModel;
@@ -72,15 +73,21 @@ static void subghz_view_keeloq_decrypt_draw(Canvas* canvas, void* _model) {
}
canvas_draw_str(canvas, 2, 48, speed_str);
char elapsed_str[24];
uint32_t el_m = model->elapsed_sec / 60;
uint32_t el_s = model->elapsed_sec % 60;
if(el_m > 0) {
snprintf(elapsed_str, sizeof(elapsed_str), "Elapsed: %lum %lus", el_m, el_s);
if(model->candidates > 0) {
char cand_str[32];
snprintf(cand_str, sizeof(cand_str), "Candidates: %lu", model->candidates);
canvas_draw_str(canvas, 2, 58, cand_str);
} else {
snprintf(elapsed_str, sizeof(elapsed_str), "Elapsed: %lus", el_s);
char elapsed_str[24];
uint32_t el_m = model->elapsed_sec / 60;
uint32_t el_s = model->elapsed_sec % 60;
if(el_m > 0) {
snprintf(elapsed_str, sizeof(elapsed_str), "Elapsed: %lum %lus", el_m, el_s);
} else {
snprintf(elapsed_str, sizeof(elapsed_str), "Elapsed: %lus", el_s);
}
canvas_draw_str(canvas, 2, 58, elapsed_str);
}
canvas_draw_str(canvas, 2, 58, elapsed_str);
canvas_draw_str_aligned(canvas, 126, 64, AlignRight, AlignBottom, "Hold BACK");
} else {
@@ -124,6 +131,7 @@ SubGhzViewKeeloqDecrypt* subghz_view_keeloq_decrypt_alloc(void) {
model->eta_sec = 0;
model->done = false;
model->success = false;
model->candidates = 0;
},
false);
@@ -205,6 +213,7 @@ void subghz_view_keeloq_decrypt_reset(SubGhzViewKeeloqDecrypt* instance) {
model->eta_sec = 0;
model->done = false;
model->success = false;
model->candidates = 0;
furi_string_reset(model->result_str);
model->status_line[0] = '\0';
},
@@ -225,3 +234,13 @@ void subghz_view_keeloq_decrypt_set_status(SubGhzViewKeeloqDecrypt* instance, co
},
true);
}
void subghz_view_keeloq_decrypt_update_candidates(
SubGhzViewKeeloqDecrypt* instance, uint32_t count) {
furi_check(instance);
with_view_model(
instance->view,
SubGhzKeeloqDecryptModel * model,
{ model->candidates = count; },
true);
}

View File

@@ -32,3 +32,6 @@ void subghz_view_keeloq_decrypt_set_result(
void subghz_view_keeloq_decrypt_reset(SubGhzViewKeeloqDecrypt* instance);
void subghz_view_keeloq_decrypt_set_status(SubGhzViewKeeloqDecrypt* instance, const char* status);
void subghz_view_keeloq_decrypt_update_candidates(
SubGhzViewKeeloqDecrypt* instance, uint32_t count);

View File

@@ -50,8 +50,10 @@ static void subghz_view_psa_decrypt_draw(Canvas* canvas, void* _model) {
// Progress bar outline + fill
canvas_draw_rframe(canvas, 3, 15, 122, 12, 2);
uint8_t fill = (uint8_t)((uint16_t)model->progress * 116 / 100);
if(fill > 0) {
if(fill > 2) {
canvas_draw_rbox(canvas, 5, 17, fill, 8, 1);
} else if(fill > 0) {
canvas_draw_box(canvas, 5, 17, fill, 8);
}
canvas_set_font(canvas, FontSecondary);
@@ -90,19 +92,23 @@ static void subghz_view_psa_decrypt_draw(Canvas* canvas, void* _model) {
// Cancel hint - bottom right
canvas_draw_str_aligned(canvas, 126, 64, AlignRight, AlignBottom, "Hold BACK");
} else {
// Result screen
canvas_set_font(canvas, FontSecondary);
if(model->result_str) {
elements_multiline_text_aligned(
canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(model->result_str));
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 64, 4, AlignCenter, AlignTop, "Decrypted!");
if(model->result_str) {
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(canvas, 64, 20, AlignCenter, AlignTop,
furi_string_get_cstr(model->result_str));
}
elements_button_center(canvas, "Ok");
}
}
static bool subghz_view_psa_decrypt_input(InputEvent* event, void* context) {
SubGhzViewPsaDecrypt* instance = (SubGhzViewPsaDecrypt*)context;
if(event->key == InputKeyBack) {
if(event->key == InputKeyBack || event->key == InputKeyOk) {
if(instance->callback) {
instance->callback(SubGhzCustomEventViewTransmitterBack, instance->context);
}

View File

@@ -0,0 +1,517 @@
#include "fiat_spa.h"
#include "../blocks/const.h"
#include "../blocks/decoder.h"
#include "../blocks/encoder.h"
#include "../blocks/generic.h"
#include "../blocks/math.h"
#include <lib/toolbox/manchester_decoder.h>
#define TAG "SubGhzProtocolFiatSpa"
static const SubGhzBlockConst subghz_protocol_fiat_spa_const = {
.te_short = 200,
.te_long = 400,
.te_delta = 100,
.min_count_bit_for_found = 64,
};
#define FIAT_SPA_PREAMBLE_PAIRS 150
#define FIAT_SPA_GAP_US 800
#define FIAT_SPA_TOTAL_BURSTS 3
#define FIAT_SPA_INTER_BURST_GAP 25000
#define FIAT_SPA_UPLOAD_MAX 1328
struct SubGhzProtocolDecoderFiatSpa {
SubGhzProtocolDecoderBase base;
SubGhzBlockGeneric generic;
SubGhzBlockDecoder decoder;
ManchesterState manchester_state;
uint16_t preamble_count;
uint32_t data_low;
uint32_t data_high;
uint8_t bit_count;
uint32_t hop;
uint32_t fix;
uint8_t endbyte;
};
struct SubGhzProtocolEncoderFiatSpa {
SubGhzProtocolEncoderBase base;
void* decoder_callback;
void* decoder_context;
SubGhzBlockGeneric generic;
SubGhzProtocolBlockEncoder encoder;
uint32_t hop;
uint32_t fix;
uint8_t endbyte;
};
typedef struct {
SubGhzProtocolDecoderBase base;
SubGhzBlockGeneric generic;
} SubGhzProtocolCommonFiatSpa;
typedef enum {
FiatSpaDecoderStepReset = 0,
FiatSpaDecoderStepPreamble,
FiatSpaDecoderStepData,
} FiatSpaDecoderStep;
const SubGhzProtocolDecoder subghz_protocol_fiat_spa_decoder = {
.alloc = subghz_protocol_decoder_fiat_spa_alloc,
.free = subghz_protocol_decoder_fiat_spa_free,
.feed = subghz_protocol_decoder_fiat_spa_feed,
.reset = subghz_protocol_decoder_fiat_spa_reset,
.get_hash_data = subghz_protocol_decoder_fiat_spa_get_hash_data,
.serialize = subghz_protocol_decoder_fiat_spa_serialize,
.deserialize = subghz_protocol_decoder_fiat_spa_deserialize,
.get_string = subghz_protocol_decoder_fiat_spa_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_fiat_spa_encoder = {
.alloc = subghz_protocol_encoder_fiat_spa_alloc,
.free = subghz_protocol_encoder_fiat_spa_free,
.deserialize = subghz_protocol_encoder_fiat_spa_deserialize,
.stop = subghz_protocol_encoder_fiat_spa_stop,
.yield = subghz_protocol_encoder_fiat_spa_yield,
};
const SubGhzProtocol subghz_protocol_fiat_spa = {
.name = SUBGHZ_PROTOCOL_FIAT_SPA_NAME,
.type = SubGhzProtocolTypeStatic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_fiat_spa_decoder,
.encoder = &subghz_protocol_fiat_spa_encoder,
};
void* subghz_protocol_decoder_fiat_spa_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderFiatSpa* instance = malloc(sizeof(SubGhzProtocolDecoderFiatSpa));
instance->base.protocol = &subghz_protocol_fiat_spa;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_fiat_spa_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderFiatSpa* instance = context;
free(instance);
}
void subghz_protocol_decoder_fiat_spa_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderFiatSpa* instance = context;
instance->decoder.parser_step = FiatSpaDecoderStepReset;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->preamble_count = 0;
instance->data_low = 0;
instance->data_high = 0;
instance->bit_count = 0;
instance->hop = 0;
instance->fix = 0;
instance->endbyte = 0;
instance->manchester_state = ManchesterStateMid1;
}
void subghz_protocol_decoder_fiat_spa_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderFiatSpa* instance = context;
uint32_t te_short = (uint32_t)subghz_protocol_fiat_spa_const.te_short;
uint32_t te_long = (uint32_t)subghz_protocol_fiat_spa_const.te_long;
uint32_t te_delta = (uint32_t)subghz_protocol_fiat_spa_const.te_delta;
uint32_t gap_threshold = FIAT_SPA_GAP_US;
uint32_t diff;
switch(instance->decoder.parser_step) {
case FiatSpaDecoderStepReset:
if(!level) return;
if(duration < te_short) {
diff = te_short - duration;
} else {
diff = duration - te_short;
}
if(diff < te_delta) {
instance->data_low = 0;
instance->data_high = 0;
instance->decoder.parser_step = FiatSpaDecoderStepPreamble;
instance->preamble_count = 0;
instance->bit_count = 0;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
manchester_advance(
instance->manchester_state,
ManchesterEventReset,
&instance->manchester_state,
NULL);
}
break;
case FiatSpaDecoderStepPreamble:
if(level) {
if(duration < te_short) {
diff = te_short - duration;
} else {
diff = duration - te_short;
}
if(diff < te_delta) {
instance->preamble_count++;
} else {
instance->decoder.parser_step = FiatSpaDecoderStepReset;
}
return;
}
if(duration < te_short) {
diff = te_short - duration;
} else {
diff = duration - te_short;
}
if(diff < te_delta) {
instance->preamble_count++;
} else {
if(instance->preamble_count >= FIAT_SPA_PREAMBLE_PAIRS) {
if(duration < gap_threshold) {
diff = gap_threshold - duration;
} else {
diff = duration - gap_threshold;
}
if(diff < te_delta) {
instance->decoder.parser_step = FiatSpaDecoderStepData;
instance->preamble_count = 0;
instance->data_low = 0;
instance->data_high = 0;
instance->bit_count = 0;
manchester_advance(instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
return;
}
}
instance->decoder.parser_step = FiatSpaDecoderStepReset;
}
if(instance->preamble_count >= FIAT_SPA_PREAMBLE_PAIRS &&
instance->decoder.parser_step == FiatSpaDecoderStepPreamble) {
if(duration < gap_threshold) {
diff = gap_threshold - duration;
} else {
diff = duration - gap_threshold;
}
if(diff < te_delta) {
instance->decoder.parser_step = FiatSpaDecoderStepData;
instance->preamble_count = 0;
instance->data_low = 0;
instance->data_high = 0;
instance->bit_count = 0;
manchester_advance(instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
return;
}
}
break;
case FiatSpaDecoderStepData: {
ManchesterEvent event = ManchesterEventReset;
if(duration < te_short) {
diff = te_short - duration;
if(diff < te_delta) {
event = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
}
} else {
diff = duration - te_short;
if(diff < te_delta) {
event = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
} else {
if(duration < te_long) {
diff = te_long - duration;
} else {
diff = duration - te_long;
}
if(diff < te_delta) {
event = level ? ManchesterEventLongLow : ManchesterEventLongHigh;
}
}
}
if(event != ManchesterEventReset) {
bool data_bit_bool;
if(manchester_advance(
instance->manchester_state,
event,
&instance->manchester_state,
&data_bit_bool)) {
uint32_t new_bit = data_bit_bool ? 1 : 0;
uint32_t carry = (instance->data_low >> 31) & 1;
instance->data_low = (instance->data_low << 1) | new_bit;
instance->data_high = (instance->data_high << 1) | carry;
instance->bit_count++;
if(instance->bit_count == 64) {
instance->fix = instance->data_low;
instance->hop = instance->data_high;
instance->data_low = 0;
instance->data_high = 0;
}
if(instance->bit_count == 0x47) {
instance->endbyte = (uint8_t)(instance->data_low & 0x3F);
instance->generic.data = ((uint64_t)instance->hop << 32) | instance->fix;
instance->generic.data_count_bit = 71;
instance->generic.serial = instance->fix;
instance->generic.btn = instance->endbyte;
instance->generic.cnt = instance->hop;
instance->decoder.decode_data = instance->generic.data;
instance->decoder.decode_count_bit = instance->generic.data_count_bit;
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
instance->data_low = 0;
instance->data_high = 0;
instance->bit_count = 0;
instance->decoder.parser_step = FiatSpaDecoderStepReset;
}
}
} else {
if(instance->bit_count == 0x47) {
instance->endbyte = (uint8_t)(instance->data_low & 0x3F);
instance->generic.data = ((uint64_t)instance->hop << 32) | instance->fix;
instance->generic.data_count_bit = 71;
instance->generic.serial = instance->fix;
instance->generic.btn = instance->endbyte;
instance->generic.cnt = instance->hop;
instance->decoder.decode_data = instance->generic.data;
instance->decoder.decode_count_bit = instance->generic.data_count_bit;
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
instance->data_low = 0;
instance->data_high = 0;
instance->bit_count = 0;
instance->decoder.parser_step = FiatSpaDecoderStepReset;
} else if(instance->bit_count < 64) {
instance->decoder.parser_step = FiatSpaDecoderStepReset;
}
}
break;
}
}
}
uint8_t subghz_protocol_decoder_fiat_spa_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderFiatSpa* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_fiat_spa_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderFiatSpa* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
if(subghz_block_generic_serialize(&instance->generic, flipper_format, preset) !=
SubGhzProtocolStatusOk) {
break;
}
if(!flipper_format_write_uint32(
flipper_format, "EndByte", (uint32_t*)&instance->endbyte, 1)) {
break;
}
ret = SubGhzProtocolStatusOk;
} while(false);
return ret;
}
SubGhzProtocolStatus subghz_protocol_decoder_fiat_spa_deserialize(
void* context,
FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderFiatSpa* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
if(ret != SubGhzProtocolStatusOk) break;
uint32_t endbyte_temp = 0;
if(!flipper_format_read_uint32(flipper_format, "EndByte", &endbyte_temp, 1)) {
instance->endbyte = 0;
} else {
instance->endbyte = (uint8_t)endbyte_temp;
}
instance->hop = (uint32_t)(instance->generic.data >> 32);
instance->fix = (uint32_t)(instance->generic.data & 0xFFFFFFFF);
instance->generic.cnt = instance->hop;
instance->generic.serial = instance->fix;
instance->generic.btn = instance->endbyte;
ret = SubGhzProtocolStatusOk;
} while(false);
return ret;
}
void subghz_protocol_decoder_fiat_spa_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolCommonFiatSpa* instance = context;
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%08lX%08lX\r\n"
"Fix:%08lX\r\n"
"Hop:%08lX\r\n"
"EndByte:%02X",
instance->generic.protocol_name,
instance->generic.data_count_bit,
(uint32_t)(instance->generic.data >> 32),
(uint32_t)(instance->generic.data & 0xFFFFFFFF),
instance->generic.serial,
instance->generic.cnt,
instance->generic.btn);
}
void* subghz_protocol_encoder_fiat_spa_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderFiatSpa* instance = malloc(sizeof(SubGhzProtocolEncoderFiatSpa));
instance->base.protocol = &subghz_protocol_fiat_spa;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 3;
instance->encoder.size_upload = FIAT_SPA_UPLOAD_MAX;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
instance->encoder.front = 0;
instance->hop = 0;
instance->fix = 0;
instance->endbyte = 0;
return instance;
}
void subghz_protocol_encoder_fiat_spa_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderFiatSpa* instance = context;
if(instance->encoder.upload) {
free(instance->encoder.upload);
}
free(instance);
}
void subghz_protocol_encoder_fiat_spa_stop(void* context) {
furi_assert(context);
SubGhzProtocolEncoderFiatSpa* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_fiat_spa_yield(void* context) {
furi_assert(context);
SubGhzProtocolEncoderFiatSpa* instance = context;
if(!instance->encoder.is_running || instance->encoder.repeat == 0) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
static void subghz_protocol_encoder_fiat_spa_get_upload(SubGhzProtocolEncoderFiatSpa* instance) {
furi_assert(instance);
size_t index = 0;
uint32_t te_short = subghz_protocol_fiat_spa_const.te_short;
uint32_t te_long = subghz_protocol_fiat_spa_const.te_long;
uint64_t data = ((uint64_t)instance->hop << 32) | instance->fix;
uint8_t endbyte_to_send = instance->endbyte >> 1;
for(uint8_t burst = 0; burst < FIAT_SPA_TOTAL_BURSTS; burst++) {
if(burst > 0) {
instance->encoder.upload[index++] =
level_duration_make(false, FIAT_SPA_INTER_BURST_GAP);
}
for(int i = 0; i < FIAT_SPA_PREAMBLE_PAIRS; i++) {
instance->encoder.upload[index++] = level_duration_make(true, te_short);
instance->encoder.upload[index++] = level_duration_make(false, te_short);
}
instance->encoder.upload[index - 1] = level_duration_make(false, FIAT_SPA_GAP_US);
bool first_bit = (data >> 63) & 1;
if(first_bit) {
instance->encoder.upload[index++] = level_duration_make(true, te_long);
} else {
instance->encoder.upload[index++] = level_duration_make(true, te_short);
instance->encoder.upload[index++] = level_duration_make(false, te_long);
}
bool prev_bit = first_bit;
for(int bit = 62; bit >= 0; bit--) {
bool curr_bit = (data >> bit) & 1;
if(!prev_bit && !curr_bit) {
instance->encoder.upload[index++] = level_duration_make(true, te_short);
instance->encoder.upload[index++] = level_duration_make(false, te_short);
} else if(!prev_bit && curr_bit) {
instance->encoder.upload[index++] = level_duration_make(true, te_long);
} else if(prev_bit && !curr_bit) {
instance->encoder.upload[index++] = level_duration_make(false, te_long);
} else {
instance->encoder.upload[index++] = level_duration_make(false, te_short);
instance->encoder.upload[index++] = level_duration_make(true, te_short);
}
prev_bit = curr_bit;
}
for(int bit = 5; bit >= 0; bit--) {
bool curr_bit = (endbyte_to_send >> bit) & 1;
if(!prev_bit && !curr_bit) {
instance->encoder.upload[index++] = level_duration_make(true, te_short);
instance->encoder.upload[index++] = level_duration_make(false, te_short);
} else if(!prev_bit && curr_bit) {
instance->encoder.upload[index++] = level_duration_make(true, te_long);
} else if(prev_bit && !curr_bit) {
instance->encoder.upload[index++] = level_duration_make(false, te_long);
} else {
instance->encoder.upload[index++] = level_duration_make(false, te_short);
instance->encoder.upload[index++] = level_duration_make(true, te_short);
}
prev_bit = curr_bit;
}
if(prev_bit) {
instance->encoder.upload[index++] = level_duration_make(false, te_short);
}
instance->encoder.upload[index++] = level_duration_make(false, te_short * 8);
}
instance->encoder.size_upload = index;
instance->encoder.front = 0;
}
SubGhzProtocolStatus subghz_protocol_encoder_fiat_spa_deserialize(
void* context,
FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderFiatSpa* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
if(ret != SubGhzProtocolStatusOk) break;
instance->hop = (uint32_t)(instance->generic.data >> 32);
instance->fix = (uint32_t)(instance->generic.data & 0xFFFFFFFF);
uint32_t endbyte_temp = 0;
if(!flipper_format_read_uint32(flipper_format, "EndByte", &endbyte_temp, 1)) {
instance->endbyte = 0;
} else {
instance->endbyte = (uint8_t)endbyte_temp;
}
instance->generic.cnt = instance->hop;
instance->generic.serial = instance->fix;
instance->generic.btn = instance->endbyte;
subghz_protocol_encoder_fiat_spa_get_upload(instance);
instance->encoder.is_running = true;
ret = SubGhzProtocolStatusOk;
} while(false);
return ret;
}

View File

@@ -0,0 +1,32 @@
#pragma once
#include "base.h"
#define SUBGHZ_PROTOCOL_FIAT_SPA_NAME "FIAT SPA"
typedef struct SubGhzProtocolDecoderFiatSpa SubGhzProtocolDecoderFiatSpa;
typedef struct SubGhzProtocolEncoderFiatSpa SubGhzProtocolEncoderFiatSpa;
extern const SubGhzProtocol subghz_protocol_fiat_spa;
void* subghz_protocol_decoder_fiat_spa_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_fiat_spa_free(void* context);
void subghz_protocol_decoder_fiat_spa_reset(void* context);
void subghz_protocol_decoder_fiat_spa_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_fiat_spa_get_hash_data(void* context);
SubGhzProtocolStatus subghz_protocol_decoder_fiat_spa_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
SubGhzProtocolStatus subghz_protocol_decoder_fiat_spa_deserialize(
void* context,
FlipperFormat* flipper_format);
void subghz_protocol_decoder_fiat_spa_get_string(void* context, FuriString* output);
void* subghz_protocol_encoder_fiat_spa_alloc(SubGhzEnvironment* environment);
void subghz_protocol_encoder_fiat_spa_free(void* context);
SubGhzProtocolStatus subghz_protocol_encoder_fiat_spa_deserialize(
void* context,
FlipperFormat* flipper_format);
void subghz_protocol_encoder_fiat_spa_stop(void* context);
LevelDuration subghz_protocol_encoder_fiat_spa_yield(void* context);

View File

@@ -1,655 +0,0 @@
#include "fiat_v0.h"
#include <inttypes.h>
#include <lib/toolbox/manchester_decoder.h>
#define TAG "FiatProtocolV0"
#define FIAT_V0_PREAMBLE_PAIRS 150
#define FIAT_V0_GAP_US 800
#define FIAT_V0_TOTAL_BURSTS 3
#define FIAT_V0_INTER_BURST_GAP 25000
static const SubGhzBlockConst subghz_protocol_fiat_v0_const = {
.te_short = 200,
.te_long = 400,
.te_delta = 100,
.min_count_bit_for_found = 71,
};
struct SubGhzProtocolDecoderFiatV0 {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
ManchesterState manchester_state;
uint8_t decoder_state;
uint16_t preamble_count;
uint32_t data_low;
uint32_t data_high;
uint8_t bit_count;
uint32_t hop;
uint32_t fix;
uint8_t endbyte;
uint8_t final_count;
uint32_t te_last;
};
struct SubGhzProtocolEncoderFiatV0 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
uint32_t hop;
uint32_t fix;
uint8_t endbyte;
size_t upload_capacity;
};
typedef enum {
FiatV0DecoderStepReset = 0,
FiatV0DecoderStepPreamble = 1,
FiatV0DecoderStepData = 2,
} FiatV0DecoderStep;
// ============================================================================
// PROTOCOL INTERFACE DEFINITIONS
// ============================================================================
const SubGhzProtocolDecoder subghz_protocol_fiat_v0_decoder = {
.alloc = subghz_protocol_decoder_fiat_v0_alloc,
.free = subghz_protocol_decoder_fiat_v0_free,
.feed = subghz_protocol_decoder_fiat_v0_feed,
.reset = subghz_protocol_decoder_fiat_v0_reset,
.get_hash_data = subghz_protocol_decoder_fiat_v0_get_hash_data,
.serialize = subghz_protocol_decoder_fiat_v0_serialize,
.deserialize = subghz_protocol_decoder_fiat_v0_deserialize,
.get_string = subghz_protocol_decoder_fiat_v0_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_fiat_v0_encoder = {
.alloc = subghz_protocol_encoder_fiat_v0_alloc,
.free = subghz_protocol_encoder_fiat_v0_free,
.deserialize = subghz_protocol_encoder_fiat_v0_deserialize,
.stop = subghz_protocol_encoder_fiat_v0_stop,
.yield = subghz_protocol_encoder_fiat_v0_yield,
};
const SubGhzProtocol subghz_protocol_fiat_v0 = {
.name = FIAT_PROTOCOL_V0_NAME,
.type = SubGhzProtocolTypeStatic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_fiat_v0_decoder,
.encoder = &subghz_protocol_fiat_v0_encoder,
};
// ============================================================================
// ENCODER IMPLEMENTATION
// ============================================================================
static size_t fiat_v0_encoder_calc_required_upload(void) {
// Per burst:
// preamble: FIAT_V0_PREAMBLE_PAIRS pairs => 2 elements each
// data: 64 bits Manchester => 2 elements per bit
// endbyte: 7 bits Manchester => 2 elements per bit
// trailer: 1 element (extended low)
const size_t per_burst = (FIAT_V0_PREAMBLE_PAIRS * 2) + (64 * 2) + (7 * 2) + 1;
// Inter-burst gap: 1 element between each pair of bursts
return (FIAT_V0_TOTAL_BURSTS * per_burst) +
(FIAT_V0_TOTAL_BURSTS > 0 ? (FIAT_V0_TOTAL_BURSTS - 1) : 0);
}
void* subghz_protocol_encoder_fiat_v0_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderFiatV0* instance = calloc(1, sizeof(SubGhzProtocolEncoderFiatV0));
furi_check(instance);
instance->base.protocol = &subghz_protocol_fiat_v0;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 10;
instance->encoder.size_upload = 0;
instance->upload_capacity = fiat_v0_encoder_calc_required_upload();
instance->encoder.upload = calloc(instance->upload_capacity, sizeof(LevelDuration));
furi_check(instance->encoder.upload);
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_fiat_v0_free(void* context) {
furi_check(context);
SubGhzProtocolEncoderFiatV0* instance = context;
if(instance->encoder.upload) {
free(instance->encoder.upload);
}
free(instance);
}
static void subghz_protocol_encoder_fiat_v0_get_upload(SubGhzProtocolEncoderFiatV0* instance) {
furi_check(instance);
const size_t required = fiat_v0_encoder_calc_required_upload();
// Capacity is pre-allocated at alloc time — assert it is sufficient
furi_check(required <= instance->upload_capacity);
size_t index = 0;
uint32_t te_short = subghz_protocol_fiat_v0_const.te_short;
FURI_LOG_I(
TAG,
"Building upload: hop=0x%08lX, fix=0x%08lX, endbyte=0x%02X",
instance->hop,
instance->fix,
instance->endbyte & 0x7F);
for(uint8_t burst = 0; burst < FIAT_V0_TOTAL_BURSTS; burst++) {
if(burst > 0) {
instance->encoder.upload[index++] =
level_duration_make(false, FIAT_V0_INTER_BURST_GAP);
}
// Preamble: alternating short pulses
for(int i = 0; i < FIAT_V0_PREAMBLE_PAIRS; i++) {
instance->encoder.upload[index++] = level_duration_make(true, te_short);
instance->encoder.upload[index++] = level_duration_make(false, te_short);
}
// Extend last LOW to create the sync gap
instance->encoder.upload[index - 1] = level_duration_make(false, FIAT_V0_GAP_US);
// Combine hop and fix into 64-bit data word
uint64_t data = ((uint64_t)instance->hop << 32) | instance->fix;
// Manchester encode 64 bits of data (MSB first)
for(int bit = 63; bit >= 0; bit--) {
bool curr_bit = (data >> bit) & 1;
if(curr_bit) {
instance->encoder.upload[index++] = level_duration_make(true, te_short);
instance->encoder.upload[index++] = level_duration_make(false, te_short);
} else {
instance->encoder.upload[index++] = level_duration_make(false, te_short);
instance->encoder.upload[index++] = level_duration_make(true, te_short);
}
}
// Manchester encode 7 bits of endbyte (bits 6:0, MSB first)
uint8_t endbyte = (uint8_t)(instance->endbyte & 0x7F);
for(int bit = 6; bit >= 0; bit--) {
bool curr_bit = (endbyte >> bit) & 1;
if(curr_bit) {
instance->encoder.upload[index++] = level_duration_make(true, te_short);
instance->encoder.upload[index++] = level_duration_make(false, te_short);
} else {
instance->encoder.upload[index++] = level_duration_make(false, te_short);
instance->encoder.upload[index++] = level_duration_make(true, te_short);
}
}
// Burst trailer: extended LOW
instance->encoder.upload[index++] = level_duration_make(false, te_short * 4);
}
furi_check(index <= instance->upload_capacity);
instance->encoder.size_upload = index;
instance->encoder.front = 0;
FURI_LOG_I(TAG, "Upload built: %zu elements", instance->encoder.size_upload);
}
SubGhzProtocolStatus
subghz_protocol_encoder_fiat_v0_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolEncoderFiatV0* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
instance->encoder.is_running = false;
instance->encoder.front = 0;
instance->encoder.repeat = 10;
flipper_format_rewind(flipper_format);
FuriString* temp_str = furi_string_alloc();
furi_check(temp_str);
do {
if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) {
FURI_LOG_E(TAG, "Missing Protocol");
break;
}
if(!furi_string_equal(temp_str, instance->base.protocol->name)) {
FURI_LOG_E(TAG, "Wrong protocol: %s", furi_string_get_cstr(temp_str));
break;
}
uint32_t bit_count_temp = 0;
if(flipper_format_read_uint32(flipper_format, "Bit", &bit_count_temp, 1)) {
// Protocol transmits 71 bits: 64-bit key + 7-bit endbyte
if(bit_count_temp == 64 || bit_count_temp == 71) {
instance->generic.data_count_bit = bit_count_temp;
} else {
FURI_LOG_E(
TAG,
"Unexpected Bit value %lu, defaulting to 71",
(unsigned long)bit_count_temp);
instance->generic.data_count_bit = 71;
}
} else {
FURI_LOG_E(TAG, "Missing Bit");
break;
}
if(!flipper_format_read_string(flipper_format, "Key", temp_str)) {
FURI_LOG_E(TAG, "Missing Key");
break;
}
const char* key_str = furi_string_get_cstr(temp_str);
uint64_t key = 0;
size_t str_len = strlen(key_str);
size_t hex_pos = 0;
for(size_t i = 0; i < str_len && hex_pos < 16; i++) {
char c = key_str[i];
if(c == ' ') continue;
uint8_t nibble;
if(c >= '0' && c <= '9') {
nibble = (uint8_t)(c - '0');
} else if(c >= 'A' && c <= 'F') {
nibble = (uint8_t)(c - 'A' + 10);
} else if(c >= 'a' && c <= 'f') {
nibble = (uint8_t)(c - 'a' + 10);
} else {
break;
}
key = (key << 4) | nibble;
hex_pos++;
}
if(hex_pos != 16) {
FURI_LOG_E(TAG, "Key parse error: expected 16 hex nibbles, got %u", (unsigned)hex_pos);
break;
}
instance->generic.data = key;
instance->hop = (uint32_t)(key >> 32);
instance->fix = (uint32_t)(key & 0xFFFFFFFF);
uint32_t btn_temp = 0;
if(flipper_format_read_uint32(flipper_format, "Btn", &btn_temp, 1)) {
instance->endbyte = (uint8_t)(btn_temp & 0x7F);
} else {
instance->endbyte = 0;
}
instance->generic.btn = instance->endbyte;
instance->generic.cnt = instance->hop;
instance->generic.serial = instance->fix;
uint32_t repeat_temp = 0;
if(flipper_format_read_uint32(flipper_format, "Repeat", &repeat_temp, 1)) {
instance->encoder.repeat = repeat_temp;
} else {
instance->encoder.repeat = 10;
}
subghz_protocol_encoder_fiat_v0_get_upload(instance);
instance->encoder.is_running = true;
FURI_LOG_I(
TAG,
"Encoder ready: hop=0x%08lX, fix=0x%08lX, endbyte=0x%02X",
instance->hop,
instance->fix,
instance->endbyte);
ret = SubGhzProtocolStatusOk;
} while(false);
furi_string_free(temp_str);
return ret;
}
void subghz_protocol_encoder_fiat_v0_stop(void* context) {
furi_check(context);
SubGhzProtocolEncoderFiatV0* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_fiat_v0_yield(void* context) {
furi_check(context);
SubGhzProtocolEncoderFiatV0* instance = context;
if(!instance->encoder.is_running || instance->encoder.repeat == 0) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
// ============================================================================
// DECODER IMPLEMENTATION
// ============================================================================
void* subghz_protocol_decoder_fiat_v0_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderFiatV0* instance = calloc(1, sizeof(SubGhzProtocolDecoderFiatV0));
furi_check(instance);
instance->base.protocol = &subghz_protocol_fiat_v0;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_fiat_v0_free(void* context) {
furi_check(context);
SubGhzProtocolDecoderFiatV0* instance = context;
free(instance);
}
void subghz_protocol_decoder_fiat_v0_reset(void* context) {
furi_check(context);
SubGhzProtocolDecoderFiatV0* instance = context;
instance->decoder.parser_step = FiatV0DecoderStepReset;
instance->decoder_state = 0;
instance->preamble_count = 0;
instance->data_low = 0;
instance->data_high = 0;
instance->bit_count = 0;
instance->hop = 0;
instance->fix = 0;
instance->endbyte = 0;
instance->final_count = 0;
instance->te_last = 0;
instance->manchester_state = ManchesterStateMid1;
}
// Helper: transition decoder into data-collection state
static void
fiat_v0_decoder_enter_data_state(SubGhzProtocolDecoderFiatV0* instance, uint32_t duration) {
instance->decoder_state = FiatV0DecoderStepData;
instance->preamble_count = 0;
instance->data_low = 0;
instance->data_high = 0;
instance->bit_count = 0;
instance->te_last = duration;
manchester_advance(
instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
}
void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t duration) {
furi_check(context);
SubGhzProtocolDecoderFiatV0* instance = context;
uint32_t te_short = (uint32_t)subghz_protocol_fiat_v0_const.te_short;
uint32_t te_long = (uint32_t)subghz_protocol_fiat_v0_const.te_long;
uint32_t te_delta = (uint32_t)subghz_protocol_fiat_v0_const.te_delta;
uint32_t gap_threshold = FIAT_V0_GAP_US;
uint32_t diff;
switch(instance->decoder_state) {
case FiatV0DecoderStepReset:
if(!level) {
return;
}
if(duration < te_short) {
diff = te_short - duration;
} else {
diff = duration - te_short;
}
if(diff < te_delta) {
instance->data_low = 0;
instance->data_high = 0;
instance->decoder_state = FiatV0DecoderStepPreamble;
instance->te_last = duration;
instance->preamble_count = 0;
instance->bit_count = 0;
manchester_advance(
instance->manchester_state,
ManchesterEventReset,
&instance->manchester_state,
NULL);
}
break;
case FiatV0DecoderStepPreamble:
if(level) {
if(duration < te_short) {
diff = te_short - duration;
} else {
diff = duration - te_short;
}
if(diff < te_delta) {
instance->preamble_count++;
instance->te_last = duration;
} else {
instance->decoder_state = FiatV0DecoderStepReset;
}
return;
}
if(duration < te_short) {
diff = te_short - duration;
} else {
diff = duration - te_short;
}
if(diff < te_delta) {
instance->preamble_count++;
instance->te_last = duration;
} else {
if(instance->preamble_count >= 0x96) {
if(duration < gap_threshold) {
diff = gap_threshold - duration;
} else {
diff = duration - gap_threshold;
}
if(diff < te_delta) {
fiat_v0_decoder_enter_data_state(instance, duration);
return;
}
}
instance->decoder_state = FiatV0DecoderStepReset;
}
if(instance->preamble_count >= 0x96 &&
instance->decoder_state == FiatV0DecoderStepPreamble) {
if(duration < gap_threshold) {
diff = gap_threshold - duration;
} else {
diff = duration - gap_threshold;
}
if(diff < te_delta) {
fiat_v0_decoder_enter_data_state(instance, duration);
return;
}
}
break;
case FiatV0DecoderStepData:
ManchesterEvent event = ManchesterEventReset;
if(duration < te_short) {
diff = te_short - duration;
if(diff < te_delta) {
event = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
}
} else {
diff = duration - te_short;
if(diff < te_delta) {
event = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
} else {
if(duration < te_long) {
diff = te_long - duration;
} else {
diff = duration - te_long;
}
if(diff < te_delta) {
event = level ? ManchesterEventLongLow : ManchesterEventLongHigh;
}
}
}
if(event != ManchesterEventReset) {
bool data_bit_bool;
if(manchester_advance(
instance->manchester_state,
event,
&instance->manchester_state,
&data_bit_bool)) {
uint32_t new_bit = data_bit_bool ? 1 : 0;
uint32_t carry = (instance->data_low >> 31) & 1;
instance->data_low = (instance->data_low << 1) | new_bit;
instance->data_high = (instance->data_high << 1) | carry;
instance->bit_count++;
if(instance->bit_count == 0x40) {
instance->fix = instance->data_low;
instance->hop = instance->data_high;
instance->data_low = 0;
instance->data_high = 0;
}
if(instance->bit_count == 0x47) {
instance->final_count = instance->bit_count;
instance->endbyte = (uint8_t)instance->data_low;
FURI_LOG_I(TAG, "Decoded: hop=0x%08lX fix=0x%08lX endbyte=0x%02X",
instance->hop, instance->fix, instance->endbyte & 0x7F);
instance->generic.data = ((uint64_t)instance->hop << 32) | instance->fix;
instance->generic.data_count_bit = 71;
instance->generic.serial = instance->fix;
instance->generic.btn = instance->endbyte;
instance->generic.cnt = instance->hop;
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
instance->data_low = 0;
instance->data_high = 0;
instance->bit_count = 0;
instance->decoder_state = FiatV0DecoderStepReset;
}
}
} else {
if(instance->bit_count == 0x47) {
uint8_t data_low_byte = (uint8_t)instance->data_low;
instance->endbyte = data_low_byte;
FURI_LOG_I(TAG, "Decoded (gap): hop=0x%08lX fix=0x%08lX endbyte=0x%02X",
instance->hop, instance->fix, instance->endbyte & 0x7F);
instance->generic.data = ((uint64_t)instance->hop << 32) | instance->fix;
instance->generic.data_count_bit = 71;
instance->generic.serial = instance->fix;
instance->generic.btn = instance->endbyte;
instance->generic.cnt = instance->hop;
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
instance->data_low = 0;
instance->data_high = 0;
instance->bit_count = 0;
instance->decoder_state = FiatV0DecoderStepReset;
} else if(instance->bit_count < 0x40) {
instance->decoder_state = FiatV0DecoderStepReset;
}
}
instance->te_last = duration;
break;
}
}
uint8_t subghz_protocol_decoder_fiat_v0_get_hash_data(void* context) {
furi_check(context);
SubGhzProtocolDecoderFiatV0* instance = context;
SubGhzBlockDecoder decoder = {
.decode_data = instance->generic.data,
.decode_count_bit = instance->generic.data_count_bit};
return subghz_protocol_blocks_get_hash_data(&decoder, (decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_fiat_v0_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_check(context);
SubGhzProtocolDecoderFiatV0* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
// Use the standard generic serialize helper (handles Filetype, Version, Frequency, Preset, Protocol, Bit, Key)
ret = subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
if(ret == SubGhzProtocolStatusOk) {
// Save CRC - calculate from key bytes (use uint32_t as required by flipper_format_write_uint32)
uint64_t key64 = instance->generic.data;
uint32_t crc = 0;
for(int i = 0; i < 8; i++) {
crc ^= (uint32_t)((key64 >> (i * 8)) & 0xFF);
}
flipper_format_write_uint32(flipper_format, "CRC", &crc, 1);
// Save decoded fields
flipper_format_write_uint32(flipper_format, "Serial", &instance->generic.serial, 1);
uint32_t temp = instance->generic.btn;
flipper_format_write_uint32(flipper_format, "Btn", &temp, 1);
flipper_format_write_uint32(flipper_format, "Cnt", &instance->generic.cnt, 1);
}
return ret;
}
SubGhzProtocolStatus subghz_protocol_decoder_fiat_v0_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolDecoderFiatV0* instance = context;
// Use the standard generic deserialize helper
SubGhzProtocolStatus ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
if(ret == SubGhzProtocolStatusOk) {
// Extract hop and fix from the loaded key
instance->hop = (uint32_t)(instance->generic.data >> 32);
instance->fix = (uint32_t)(instance->generic.data & 0xFFFFFFFF);
// The btn value is already loaded by generic_deserialize into instance->generic.btn
instance->endbyte = instance->generic.btn;
}
return ret;
}
void subghz_protocol_decoder_fiat_v0_get_string(void* context, FuriString* output) {
furi_check(context);
SubGhzProtocolDecoderFiatV0* instance = context;
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%08lX%08lX\r\n"
"Hop:%08lX\r\n"
"Sn:%08lX\r\n"
"EndByte:%02X\r\n",
instance->generic.protocol_name,
instance->generic.data_count_bit,
instance->hop,
instance->fix,
instance->hop,
instance->fix,
instance->endbyte & 0x7F);
}

View File

@@ -1,41 +0,0 @@
#pragma once
#include <furi.h>
#include <lib/subghz/protocols/base.h>
#include <lib/subghz/types.h>
#include <lib/subghz/blocks/const.h>
#include <lib/subghz/blocks/decoder.h>
#include <lib/subghz/blocks/encoder.h>
#include <lib/subghz/blocks/generic.h>
#include <lib/subghz/blocks/math.h>
#include <lib/toolbox/manchester_decoder.h>
#include <flipper_format/flipper_format.h>
#define FIAT_PROTOCOL_V0_NAME "Fiat SpA"
typedef struct SubGhzProtocolDecoderFiatV0 SubGhzProtocolDecoderFiatV0;
typedef struct SubGhzProtocolEncoderFiatV0 SubGhzProtocolEncoderFiatV0;
extern const SubGhzProtocol subghz_protocol_fiat_v0;
// Decoder functions
void* subghz_protocol_decoder_fiat_v0_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_fiat_v0_free(void* context);
void subghz_protocol_decoder_fiat_v0_reset(void* context);
void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_fiat_v0_get_hash_data(void* context);
SubGhzProtocolStatus subghz_protocol_decoder_fiat_v0_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
SubGhzProtocolStatus
subghz_protocol_decoder_fiat_v0_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_fiat_v0_get_string(void* context, FuriString* output);
// Encoder functions
void* subghz_protocol_encoder_fiat_v0_alloc(SubGhzEnvironment* environment);
void subghz_protocol_encoder_fiat_v0_free(void* context);
SubGhzProtocolStatus
subghz_protocol_encoder_fiat_v0_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_encoder_fiat_v0_stop(void* context);
LevelDuration subghz_protocol_encoder_fiat_v0_yield(void* context);

View File

@@ -1,48 +1,76 @@
#include "protocol_items.h" // IWYU pragma: keep
const SubGhzProtocol* const subghz_protocol_registry_items[] = {
&subghz_protocol_gate_tx, &subghz_protocol_keeloq,
&subghz_protocol_nice_flo, &subghz_protocol_came,
&subghz_protocol_faac_slh, &subghz_protocol_nice_flor_s,
&subghz_protocol_came_twee, &subghz_protocol_came_atomo,
//&subghz_protocol_nero_sketch, //&subghz_protocol_ido,
&subghz_protocol_hormann, //&subghz_protocol_nero_radio,
&subghz_protocol_somfy_telis, &subghz_protocol_somfy_keytis,
&subghz_protocol_princeton, &subghz_protocol_raw,
&subghz_protocol_linear, &subghz_protocol_secplus_v2,
&subghz_protocol_secplus_v1, &subghz_protocol_megacode,
//&subghz_protocol_holtek,
&subghz_protocol_gate_tx,
&subghz_protocol_keeloq,
&subghz_protocol_nice_flo,
&subghz_protocol_came,
&subghz_protocol_faac_slh,
&subghz_protocol_nice_flor_s,
&subghz_protocol_came_twee,
&subghz_protocol_came_atomo,
//&subghz_protocol_nero_sketch,
//&subghz_protocol_ido,
&subghz_protocol_hormann,
//&subghz_protocol_nero_radio,
&subghz_protocol_somfy_telis,
&subghz_protocol_somfy_keytis,
&subghz_protocol_princeton,
&subghz_protocol_raw,
&subghz_protocol_linear,
&subghz_protocol_secplus_v2,
&subghz_protocol_secplus_v1,
&subghz_protocol_megacode,
&subghz_protocol_holtek,
&subghz_protocol_chamb_code,
//&subghz_protocol_power_smart,
&subghz_protocol_marantec,
//&subghz_protocol_bett,
&subghz_protocol_doitrand,
&subghz_protocol_phoenix_v2, //&subghz_protocol_honeywell_wdb,
&subghz_protocol_phoenix_v2,
//&subghz_protocol_honeywell_wdb,
//&subghz_protocol_magellan,
//&subghz_protocol_intertechno_v3,
//&subghz_protocol_clemsa, //&subghz_protocol_ansonic,
&subghz_protocol_smc5326, //&subghz_protocol_holtek_th12x,
&subghz_protocol_linear_delta3, //&subghz_protocol_dooya,
&subghz_protocol_alutech_at_4n, &subghz_protocol_kinggates_stylo_4k,
&subghz_protocol_bin_raw, &subghz_protocol_mastercode,
//&subghz_protocol_clemsa,
//&subghz_protocol_ansonic,
&subghz_protocol_smc5326,
&subghz_protocol_holtek_th12x,
&subghz_protocol_linear_delta3,
//&subghz_protocol_dooya,
&subghz_protocol_alutech_at_4n,
&subghz_protocol_kinggates_stylo_4k,
&subghz_protocol_bin_raw,
&subghz_protocol_mastercode,
//&subghz_protocol_honeywell,
//&subghz_protocol_legrand,
&subghz_protocol_dickert_mahs, //&subghz_protocol_gangqi,
&subghz_protocol_marantec24, //&subghz_protocol_hollarm,
&subghz_protocol_hay21, &subghz_protocol_revers_rb2,
&subghz_protocol_dickert_mahs,
//&subghz_protocol_gangqi,
&subghz_protocol_marantec24,
//&subghz_protocol_hollarm,
&subghz_protocol_hay21,
&subghz_protocol_revers_rb2,
//&subghz_protocol_feron,
&subghz_protocol_roger,
//&subghz_protocol_elplast,
//&subghz_protocol_treadmill37,
&subghz_protocol_beninca_arc, //&subghz_protocol_jarolift,
&subghz_protocol_vag, &subghz_protocol_porsche_cayenne, &subghz_protocol_ford_v0,
&subghz_protocol_beninca_arc,
//&subghz_protocol_jarolift,
&subghz_protocol_vag,
&subghz_protocol_porsche_cayenne,
&subghz_protocol_ford_v0,
&subghz_protocol_psa,
&subghz_protocol_fiat_v0, &subghz_protocol_fiat_marelli,
&subghz_protocol_subaru, &subghz_protocol_mazda_siemens,
&subghz_protocol_kia_v0, &subghz_protocol_kia_v1,
&subghz_protocol_kia_v2, &subghz_protocol_kia_v3_v4,
&subghz_protocol_kia_v5, &subghz_protocol_kia_v6,
&subghz_protocol_suzuki, &subghz_protocol_mitsubishi_v0,
&subghz_protocol_fiat_spa,
&subghz_protocol_fiat_marelli,
&subghz_protocol_subaru,
&subghz_protocol_mazda_siemens,
&subghz_protocol_kia_v0,
&subghz_protocol_kia_v1,
&subghz_protocol_kia_v2,
&subghz_protocol_kia_v3_v4,
&subghz_protocol_kia_v5,
&subghz_protocol_kia_v6,
&subghz_protocol_suzuki,
&subghz_protocol_mitsubishi_v0,
};
const SubGhzProtocolRegistry subghz_protocol_registry = {

View File

@@ -23,16 +23,16 @@
#include "secplus_v2.h"
#include "secplus_v1.h"
#include "megacode.h"
//#include "holtek.h"
#include "holtek.h"
#include "chamberlain_code.h"
#include "power_smart.h"
#include "marantec.h"
#include "bett.h"
#include "doitrand.h"
#include "phoenix_v2.h"
//#include "honeywell_wdb.h"
//#include "magellan.h"
//#include "intertechno_v3.h"
#include "honeywell_wdb.h"
#include "magellan.h"
#include "intertechno_v3.h"
#include "clemsa.h"
#include "ansonic.h"
#include "smc5326.h"
@@ -42,8 +42,8 @@
#include "kinggates_stylo_4k.h"
#include "bin_raw.h"
#include "mastercode.h"
//#include "honeywell.h"
//#include "legrand.h"
#include "honeywell.h"
#include "legrand.h"
#include "dickert_mahs.h"
#include "gangqi.h"
#include "marantec24.h"
@@ -60,7 +60,7 @@
#include "porsche_cayenne.h"
#include "ford_v0.h"
#include "psa.h"
#include "fiat_v0.h"
#include "fiat_spa.h"
#include "fiat_marelli.h"
#include "subaru.h"
#include "kia_generic.h"
@@ -73,4 +73,3 @@
#include "suzuki.h"
#include "mitsubishi_v0.h"
#include "mazda_siemens.h"
#include "keys.h"

View File

@@ -290,6 +290,32 @@ __attribute__((optimize("O3"), always_inline)) static inline void
*v1 = b;
}
typedef struct {
uint32_t s0[TEA_ROUNDS];
uint32_t s1[TEA_ROUNDS];
} PsaTeaSchedule;
__attribute__((optimize("O3"), always_inline)) static inline 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;
}
}
__attribute__((optimize("O3"), always_inline)) static inline void
psa_tea_encrypt_with_schedule(uint32_t* restrict v0, uint32_t* restrict v1, const PsaTeaSchedule* sched) {
uint32_t a = *v0, b = *v1;
for(int i = 0; i < TEA_ROUNDS; i++) {
a += (sched->s0[i] ^ (((b >> 5) ^ (b << 4)) + b));
b += (sched->s1[i] ^ (((a >> 5) ^ (a << 4)) + a));
}
*v0 = a;
*v1 = b;
}
static void psa_prepare_tea_data(uint8_t* buffer, uint32_t* w0, uint32_t* w1) {
*w0 = ((uint32_t)buffer[3] << 16) | ((uint32_t)buffer[2] << 24) |
((uint32_t)buffer[4] << 8) | (uint32_t)buffer[5];
@@ -380,6 +406,8 @@ static void psa_extract_fields_mode36(uint8_t* buffer, SubGhzProtocolDecoderPSA*
__attribute__((optimize("O3"))) static bool psa_brute_force_decrypt_bf1(SubGhzProtocolDecoderPSA* instance, uint8_t* buffer, uint32_t w0, uint32_t w1, PsaDecryptProgressCallback progress_cb, void* progress_ctx) {
uint32_t bf1_total = PSA_BF1_END - PSA_BF1_START;
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(progress_cb && ((counter - PSA_BF1_START) & 0xFFFF) == 0) {
uint8_t pct = (uint8_t)(((uint64_t)(counter - PSA_BF1_START) * 50) / bf1_total);
@@ -387,24 +415,24 @@ __attribute__((optimize("O3"))) static bool psa_brute_force_decrypt_bf1(SubGhzPr
}
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};
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_extract_fields_mode36(buffer, instance);
instance->decrypted_seed = counter; // bf1 found key
instance->decrypted_seed = counter;
return true;
}
}
@@ -1976,7 +2004,7 @@ bool subghz_protocol_psa_decrypt_file(FlipperFormat* flipper_format, FuriString*
if(result_str != NULL) {
furi_string_printf(result_str,
"Decrypted!\nType: %02X\nKey: %08lX",
"Type: %02X\nSeed: %08lX",
instance.decrypted_type,
instance.decrypted_seed);
}