Compare commits

...

12 Commits

Author SHA1 Message Date
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
d4rks1d33
0f24f8c105 Added warning on counter bruteforce 2026-03-16 19:45:54 -03:00
Andrea Santaniello
238f39d0d8 Fixes 2026-03-16 23:39:22 +01:00
Andrea Santaniello
4c3581735b Better handling of the keeloq bf
All checks were successful
Build Dev Firmware / build (push) Successful in 6m23s
2026-03-16 22:31:16 +01:00
Andrea Santaniello
689df5262d Compiler bitch fix
All checks were successful
Build Dev Firmware / build (push) Successful in 6m28s
2026-03-16 17:57:09 +01:00
Andrea Santaniello
86c740d923 Preliminary stuff for phone accellerate Keeloq bruteforce
Some checks failed
Build Dev Firmware / build (push) Failing after 2m35s
2026-03-16 16:55:25 +01:00
Andrea Santaniello
0aef017c15 New assets by GONZOsint (https://github.com/GONZOsint)
All checks were successful
Build Dev Firmware / build (push) Successful in 6m46s
2026-03-16 13:57:23 +01:00
grugnoymeme
cea3bc3b6a fmt fiat marelli displayed datas and removed duplicates variant declaration in feed
All checks were successful
Build Dev Firmware / build (push) Successful in 6m25s
2026-03-16 05:58:05 +01:00
d4rks1d33
f3d08573a1 small fix
All checks were successful
Build Dev Firmware / build (push) Successful in 6m28s
2026-03-15 18:31:15 -03:00
Andrea
9e52a6eb6b Update Fiat Marelli entry in README.md
All checks were successful
Build Dev Firmware / build (push) Successful in 6m28s
2026-03-15 18:10:58 +01:00
Andrea Santaniello
faf669b457 Encoder for marelli/delphi
All checks were successful
Build Dev Firmware / build (push) Successful in 6m39s
2026-03-15 17:03:44 +01:00
21 changed files with 1088 additions and 193 deletions

View File

@@ -49,7 +49,7 @@ This project may incorporate, adapt, or build upon **other open-source projects*
| PSA (Peugeot/Citroën/DS) | PSA GROUP | 433 MHz | AM/FM | Yes | Yes | Yes | | PSA (Peugeot/Citroën/DS) | PSA GROUP | 433 MHz | AM/FM | Yes | Yes | Yes |
| Ford | Ford V0 | 315/433 MHz | AM | Yes | Yes | Yes | | Ford | Ford V0 | 315/433 MHz | AM | Yes | Yes | Yes |
| Fiat | Fiat SpA | 433 MHz | AM | Yes | Yes | Yes | | Fiat | Fiat SpA | 433 MHz | AM | Yes | Yes | Yes |
| Fiat | Fiat Marelli | 433 MHz | AM | No | Yes | No | | Fiat | Fiat Marelli/Delphi | 433 MHz | AM | No | Yes | No |
| Subaru | Subaru | 433 MHz | AM | Yes | Yes | No | | Subaru | Subaru | 433 MHz | AM | Yes | Yes | No |
| Mazda | Siemens (5WK49365D) | 315/433 MHz | FM | Yes | Yes | Yes | | Mazda | Siemens (5WK49365D) | 315/433 MHz | FM | Yes | Yes | Yes |
| Kia/Hyundai | Kia V0 | 433 MHz | FM | Yes | Yes | Yes | | Kia/Hyundai | Kia V0 | 433 MHz | FM | Yes | Yes | Yes |

View File

@@ -93,6 +93,7 @@ typedef enum {
SubGhzViewIdFrequencyAnalyzer, SubGhzViewIdFrequencyAnalyzer,
SubGhzViewIdReadRAW, SubGhzViewIdReadRAW,
SubGhzViewIdPsaDecrypt, SubGhzViewIdPsaDecrypt,
SubGhzViewIdKeeloqDecrypt,
} SubGhzViewId; } SubGhzViewId;

View File

@@ -30,4 +30,6 @@ ADD_SCENE(subghz, protocol_list, ProtocolList)
ADD_SCENE(subghz, keeloq_keys, KeeloqKeys) ADD_SCENE(subghz, keeloq_keys, KeeloqKeys)
ADD_SCENE(subghz, keeloq_key_edit, KeeloqKeyEdit) ADD_SCENE(subghz, keeloq_key_edit, KeeloqKeyEdit)
ADD_SCENE(subghz, psa_decrypt, PsaDecrypt) ADD_SCENE(subghz, psa_decrypt, PsaDecrypt)
ADD_SCENE(subghz, keeloq_decrypt, KeeloqDecrypt)
ADD_SCENE(subghz, keeloq_bf2, KeeloqBf2)
ADD_SCENE(subghz, counter_bf, CounterBf) ADD_SCENE(subghz, counter_bf, CounterBf)

View File

@@ -5,9 +5,10 @@
#define TAG "SubGhzCounterBf" #define TAG "SubGhzCounterBf"
// How many ticks to wait between transmissions (1 tick ~100ms) // 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 { typedef enum {
CounterBfStateWarning,
CounterBfStateIdle, CounterBfStateIdle,
CounterBfStateRunning, CounterBfStateRunning,
CounterBfStateStopped, CounterBfStateStopped,
@@ -22,8 +23,16 @@ typedef struct {
uint32_t tick_wait; uint32_t tick_wait;
} CounterBfContext; } CounterBfContext;
#define CounterBfEventStart (0xC0) #define CounterBfEventStart (0xC0)
#define CounterBfEventStop (0xC1) #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) { static void counter_bf_widget_callback(GuiButtonType result, InputType type, void* context) {
SubGhz* subghz = context; SubGhz* subghz = context;
@@ -32,18 +41,36 @@ 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) { static void counter_bf_draw(SubGhz* subghz, CounterBfContext* ctx) {
widget_reset(subghz->widget); widget_reset(subghz->widget);
FuriString* str = furi_string_alloc(); FuriString* str = furi_string_alloc();
furi_string_printf( furi_string_printf(
str, str,
"Counter BruteForce\n" "Counter BruteForce\n"
"Cnt: 0x%08lX\n" "Cnt: 0x%06lX\n"
"Sent: %lu pkts\n" "Start: 0x%06lX\n"
"Start: 0x%08lX", "Sent: %lu",
ctx->current_cnt, ctx->current_cnt & 0xFFFFFF,
ctx->packets_sent, ctx->start_cnt & 0xFFFFFF,
ctx->start_cnt); ctx->packets_sent);
widget_add_string_multiline_element( widget_add_string_multiline_element(
subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(str)); subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(str));
furi_string_free(str); furi_string_free(str);
@@ -57,14 +84,12 @@ static void counter_bf_draw(SubGhz* subghz, CounterBfContext* ctx) {
} }
static void counter_bf_save(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); Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* file_fff = flipper_format_buffered_file_alloc(storage); FlipperFormat* file_fff = flipper_format_buffered_file_alloc(storage);
if(flipper_format_buffered_file_open_existing( if(flipper_format_buffered_file_open_existing(
file_fff, furi_string_get_cstr(subghz->file_path))) { 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"); FURI_LOG_E(TAG, "Failed to update Cnt in .sub file");
} }
} else { } else {
@@ -77,16 +102,15 @@ static void counter_bf_save(SubGhz* subghz, CounterBfContext* ctx) {
static void counter_bf_send(SubGhz* subghz, CounterBfContext* ctx) { static void counter_bf_send(SubGhz* subghz, CounterBfContext* ctx) {
subghz_txrx_stop(subghz->txrx); 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; uint32_t repeat = 20;
flipper_format_rewind(fff); flipper_format_rewind(fff);
flipper_format_update_uint32(fff, "Repeat", &repeat, 1); 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); subghz_tx_start(subghz, fff);
ctx->packets_sent++; ctx->packets_sent++;
@@ -98,42 +122,38 @@ void subghz_scene_counter_bf_on_enter(void* context) {
CounterBfContext* ctx = malloc(sizeof(CounterBfContext)); CounterBfContext* ctx = malloc(sizeof(CounterBfContext));
memset(ctx, 0, sizeof(CounterBfContext)); memset(ctx, 0, sizeof(CounterBfContext));
ctx->state = CounterBfStateIdle; ctx->state = CounterBfStateWarning;
ctx->step = 1; 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* fff = subghz_txrx_get_fff_data(subghz->txrx);
FlipperFormat* file_fff = flipper_format_buffered_file_alloc(storage); flipper_format_rewind(fff);
if(flipper_format_buffered_file_open_existing( uint32_t cnt = 0;
file_fff, furi_string_get_cstr(subghz->file_path))) { if(flipper_format_read_uint32(fff, "Cnt", &cnt, 1)) {
uint32_t cnt = 0; ctx->current_cnt = cnt & 0xFFFFFF;
if(flipper_format_read_uint32(file_fff, "Cnt", &cnt, 1)) { ctx->start_cnt = cnt & 0xFFFFFF;
ctx->current_cnt = cnt;
ctx->start_cnt = cnt;
} else {
FURI_LOG_W(TAG, "Cnt field not found in file");
}
} else { } 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( scene_manager_set_scene_state(
subghz->scene_manager, SubGhzSceneCounterBf, (uint32_t)(uintptr_t)ctx); subghz->scene_manager, SubGhzSceneCounterBf, (uint32_t)(uintptr_t)ctx);
// Deshabilitar auto-increment del protocolo para controlar el Cnt manualmente counter_bf_draw_warning(subghz);
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);
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget);
} }
@@ -144,15 +164,21 @@ bool subghz_scene_counter_bf_on_event(void* context, SceneManagerEvent event) {
if(!ctx) return false; if(!ctx) return false;
if(event.type == SceneManagerEventTypeCustom) { if(event.type == SceneManagerEventTypeCustom) {
if(event.event == CounterBfEventWarningOk) {
ctx->state = CounterBfStateIdle;
counter_bf_draw(subghz, ctx);
return true;
}
if(event.event == CounterBfEventStart) { if(event.event == CounterBfEventStart) {
if(ctx->state == CounterBfStateWarning) return true;
if(ctx->state != CounterBfStateRunning) { if(ctx->state != CounterBfStateRunning) {
ctx->state = CounterBfStateRunning; ctx->state = CounterBfStateRunning;
ctx->tick_wait = 0; ctx->tick_wait = 0;
subghz->state_notifications = SubGhzNotificationStateTx; subghz->state_notifications = SubGhzNotificationStateTx;
counter_bf_send(subghz, ctx); counter_bf_send(subghz, ctx);
} else { } 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; ctx->state = CounterBfStateStopped;
subghz_txrx_stop(subghz->txrx); subghz_txrx_stop(subghz->txrx);
subghz->state_notifications = SubGhzNotificationStateIDLE; subghz->state_notifications = SubGhzNotificationStateIDLE;
@@ -167,19 +193,24 @@ bool subghz_scene_counter_bf_on_event(void* context, SceneManagerEvent event) {
if(ctx->tick_wait > 0) { if(ctx->tick_wait > 0) {
ctx->tick_wait--; ctx->tick_wait--;
} else { } else {
ctx->current_cnt += ctx->step; ctx->current_cnt = (ctx->current_cnt + ctx->step) & 0xFFFFFF;
counter_bf_send(subghz, ctx); counter_bf_send(subghz, ctx);
counter_bf_save(subghz, ctx);
counter_bf_draw(subghz, ctx); counter_bf_draw(subghz, ctx);
} }
} }
return true; return true;
} else if(event.type == SceneManagerEventTypeBack) { } 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_txrx_stop(subghz->txrx);
subghz->state_notifications = SubGhzNotificationStateIDLE; subghz->state_notifications = SubGhzNotificationStateIDLE;
// FIX 2 (también en Back): guardar siempre al salir
counter_bf_save(subghz, ctx); counter_bf_save(subghz, ctx);
furi_hal_subghz_set_rolling_counter_mult(1); furi_hal_subghz_set_rolling_counter_mult(1);
free(ctx); free(ctx);
scene_manager_previous_scene(subghz->scene_manager); scene_manager_previous_scene(subghz->scene_manager);

View File

@@ -0,0 +1,222 @@
#include "../subghz_i.h"
#include <lib/subghz/protocols/keeloq.h>
#include <dialogs/dialogs.h>
enum {
KlBf2IndexLoadSig1,
KlBf2IndexLoadSig2,
KlBf2IndexStartBf,
};
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);
uint8_t key_data[8] = {0};
if(!flipper_format_read_hex(fff, "Key", key_data, 8)) return false;
*out_fix = ((uint32_t)key_data[0] << 24) | ((uint32_t)key_data[1] << 16) |
((uint32_t)key_data[2] << 8) | key_data[3];
*out_hop = ((uint32_t)key_data[4] << 24) | ((uint32_t)key_data[5] << 16) |
((uint32_t)key_data[6] << 8) | key_data[7];
return true;
}
static bool kl_bf2_is_keeloq(SubGhz* subghz) {
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
flipper_format_rewind(fff);
FuriString* proto = furi_string_alloc();
bool ok = flipper_format_read_string(fff, "Protocol", proto) &&
furi_string_equal_str(proto, "KeeLoq");
furi_string_free(proto);
return ok;
}
static void kl_bf2_submenu_callback(void* context, uint32_t index) {
SubGhz* subghz = context;
view_dispatcher_send_custom_event(subghz->view_dispatcher, index);
}
static bool kl_bf2_load_signal(SubGhz* subghz, FuriString* out_path) {
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(
&browser_options, SUBGHZ_APP_FILENAME_EXTENSION, &I_sub1_10px);
browser_options.base_path = SUBGHZ_APP_FOLDER;
FuriString* selected = furi_string_alloc();
furi_string_set(selected, SUBGHZ_APP_FOLDER);
bool res = dialog_file_browser_show(subghz->dialogs, selected, selected, &browser_options);
if(res) {
res = subghz_key_load(subghz, furi_string_get_cstr(selected), true);
if(res) {
furi_string_set(out_path, selected);
}
}
furi_string_free(selected);
return res;
}
static void kl_bf2_rebuild_menu(SubGhz* subghz) {
submenu_reset(subghz->submenu);
char label1[64];
char label2[64];
if(subghz->keeloq_bf2.sig1_loaded) {
FuriString* name = furi_string_alloc();
path_extract_filename(subghz->keeloq_bf2.sig1_path, name, true);
snprintf(label1, sizeof(label1), "Sig 1: %s", furi_string_get_cstr(name));
furi_string_free(name);
} else {
snprintf(label1, sizeof(label1), "Load Signal 1");
}
if(subghz->keeloq_bf2.sig2_loaded) {
FuriString* name = furi_string_alloc();
path_extract_filename(subghz->keeloq_bf2.sig2_path, name, true);
snprintf(label2, sizeof(label2), "Sig 2: %s", furi_string_get_cstr(name));
furi_string_free(name);
} else {
snprintf(label2, sizeof(label2), "Load Signal 2");
}
submenu_add_item(
subghz->submenu, label1, KlBf2IndexLoadSig1,
kl_bf2_submenu_callback, subghz);
submenu_add_item(
subghz->submenu, label2, KlBf2IndexLoadSig2,
kl_bf2_submenu_callback, subghz);
if(subghz->keeloq_bf2.sig1_loaded && subghz->keeloq_bf2.sig2_loaded) {
submenu_add_item(
subghz->submenu, "Start BF", KlBf2IndexStartBf,
kl_bf2_submenu_callback, subghz);
}
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdMenu);
}
void subghz_scene_keeloq_bf2_on_enter(void* context) {
SubGhz* subghz = context;
subghz->keeloq_bf2.sig1_loaded = false;
subghz->keeloq_bf2.sig2_loaded = false;
kl_bf2_rebuild_menu(subghz);
}
bool subghz_scene_keeloq_bf2_on_event(void* context, SceneManagerEvent event) {
SubGhz* subghz = context;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == KlBf2IndexLoadSig1) {
FuriString* path = furi_string_alloc();
if(kl_bf2_load_signal(subghz, path)) {
if(!kl_bf2_is_keeloq(subghz)) {
dialog_message_show_storage_error(
subghz->dialogs, "Not a KeeLoq\nprotocol file");
furi_string_free(path);
kl_bf2_rebuild_menu(subghz);
return true;
}
uint32_t fix, hop;
if(!kl_bf2_extract_key(subghz, &fix, &hop)) {
dialog_message_show_storage_error(
subghz->dialogs, "Cannot read Key\nfrom file");
furi_string_free(path);
kl_bf2_rebuild_menu(subghz);
return true;
}
subghz->keeloq_bf2.fix = fix;
subghz->keeloq_bf2.hop1 = hop;
subghz->keeloq_bf2.serial = fix & 0x0FFFFFFF;
subghz->keeloq_bf2.sig1_loaded = true;
furi_string_set(subghz->keeloq_bf2.sig1_path, path);
subghz->keeloq_bf2.sig2_loaded = false;
}
furi_string_free(path);
kl_bf2_rebuild_menu(subghz);
return true;
} else if(event.event == KlBf2IndexLoadSig2) {
if(!subghz->keeloq_bf2.sig1_loaded) {
dialog_message_show_storage_error(
subghz->dialogs, "Load Signal 1 first");
kl_bf2_rebuild_menu(subghz);
return true;
}
FuriString* path = furi_string_alloc();
if(kl_bf2_load_signal(subghz, path)) {
if(!kl_bf2_is_keeloq(subghz)) {
dialog_message_show_storage_error(
subghz->dialogs, "Not a KeeLoq\nprotocol file");
furi_string_free(path);
kl_bf2_rebuild_menu(subghz);
return true;
}
uint32_t fix2, hop2;
if(!kl_bf2_extract_key(subghz, &fix2, &hop2)) {
dialog_message_show_storage_error(
subghz->dialogs, "Cannot read Key\nfrom file");
furi_string_free(path);
kl_bf2_rebuild_menu(subghz);
return true;
}
uint32_t serial2 = fix2 & 0x0FFFFFFF;
if(serial2 != subghz->keeloq_bf2.serial) {
dialog_message_show_storage_error(
subghz->dialogs, "Serial mismatch!\nMust be same remote");
furi_string_free(path);
kl_bf2_rebuild_menu(subghz);
return true;
}
if(hop2 == subghz->keeloq_bf2.hop1) {
dialog_message_show_storage_error(
subghz->dialogs, "Same hop code!\nUse a different\ncapture");
furi_string_free(path);
kl_bf2_rebuild_menu(subghz);
return true;
}
subghz->keeloq_bf2.hop2 = hop2;
subghz->keeloq_bf2.sig2_loaded = true;
furi_string_set(subghz->keeloq_bf2.sig2_path, path);
}
furi_string_free(path);
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;
}
if(!subghz_key_load(
subghz,
furi_string_get_cstr(subghz->keeloq_bf2.sig1_path),
true)) {
dialog_message_show_storage_error(
subghz->dialogs, "Cannot reload\nSignal 1");
kl_bf2_rebuild_menu(subghz);
return true;
}
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneKeeloqDecrypt);
return true;
}
}
return false;
}
void subghz_scene_keeloq_bf2_on_exit(void* context) {
SubGhz* subghz = context;
submenu_reset(subghz->submenu);
}

View File

@@ -0,0 +1,259 @@
#include "../subghz_i.h"
#include <lib/subghz/protocols/keeloq.h>
#include <lib/subghz/protocols/keeloq_common.h>
#include <furi.h>
#include <bt/bt_service/bt.h>
#define KL_DECRYPT_EVENT_DONE (0xD2)
#define KL_TOTAL_KEYS 0x100000000ULL
#define KL_MSG_BF_REQUEST 0x10
#define KL_MSG_BF_PROGRESS 0x11
#define KL_MSG_BF_RESULT 0x12
#define KL_MSG_BF_CANCEL 0x13
typedef struct {
SubGhz* subghz;
volatile bool cancel;
uint32_t start_tick;
bool success;
FuriString* result;
uint32_t fix;
uint32_t hop;
uint32_t serial;
uint8_t btn;
uint16_t disc;
uint32_t hop2;
uint64_t recovered_mfkey;
uint16_t recovered_type;
bool ble_offload;
} KlDecryptCtx;
static void kl_ble_data_received(uint8_t* data, uint16_t size, void* context) {
KlDecryptCtx* ctx = context;
if(size < 1 || ctx->cancel) return;
if(data[0] == KL_MSG_BF_PROGRESS && size >= 10) {
uint32_t keys_tested, keys_per_sec;
memcpy(&keys_tested, data + 2, 4);
memcpy(&keys_per_sec, data + 6, 4);
uint32_t elapsed_sec = (furi_get_tick() - ctx->start_tick) / 1000;
uint32_t remaining = (keys_tested > 0) ? (0xFFFFFFFFU - keys_tested) : 0xFFFFFFFFU;
uint32_t eta_sec = (keys_per_sec > 0) ? (remaining / keys_per_sec) : 0;
uint8_t pct = (uint8_t)((uint64_t)keys_tested * 100 / 0xFFFFFFFFULL);
subghz_view_keeloq_decrypt_update_stats(
ctx->subghz->subghz_keeloq_decrypt, pct, keys_tested, keys_per_sec, elapsed_sec, eta_sec);
} 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) {
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->recovered_mfkey = mfkey;
ctx->recovered_type = learn_type;
ctx->success = true;
}
view_dispatcher_send_custom_event(ctx->subghz->view_dispatcher, KL_DECRYPT_EVENT_DONE);
}
}
static void kl_ble_cleanup(KlDecryptCtx* ctx) {
if(!ctx->ble_offload) return;
Bt* bt = furi_record_open(RECORD_BT);
bt_set_custom_data_callback(bt, NULL, NULL);
furi_record_close(RECORD_BT);
ctx->ble_offload = false;
}
static bool kl_ble_start_offload(KlDecryptCtx* ctx) {
Bt* bt = furi_record_open(RECORD_BT);
if(!bt_is_connected(bt)) {
furi_record_close(RECORD_BT);
return false;
}
bt_set_custom_data_callback(bt, kl_ble_data_received, ctx);
uint8_t req[18];
req[0] = KL_MSG_BF_REQUEST;
req[1] = 0;
memcpy(req + 2, &ctx->fix, 4);
memcpy(req + 6, &ctx->hop, 4);
memcpy(req + 10, &ctx->hop2, 4);
memcpy(req + 14, &ctx->serial, 4);
bt_custom_data_tx(bt, req, sizeof(req));
furi_record_close(RECORD_BT);
ctx->ble_offload = true;
subghz_view_keeloq_decrypt_set_status(
ctx->subghz->subghz_keeloq_decrypt, "[BT] Offloading...");
return true;
}
static void kl_decrypt_view_callback(SubGhzCustomEvent event, void* context) {
SubGhz* subghz = context;
view_dispatcher_send_custom_event(subghz->view_dispatcher, event);
}
void subghz_scene_keeloq_decrypt_on_enter(void* context) {
SubGhz* subghz = context;
KlDecryptCtx* ctx = malloc(sizeof(KlDecryptCtx));
memset(ctx, 0, sizeof(KlDecryptCtx));
ctx->subghz = subghz;
ctx->result = furi_string_alloc_set("No result");
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 = subghz->keeloq_bf2.sig2_loaded ? subghz->keeloq_bf2.hop2 : 0;
scene_manager_set_scene_state(
subghz->scene_manager, SubGhzSceneKeeloqDecrypt, (uint32_t)(uintptr_t)ctx);
subghz_view_keeloq_decrypt_reset(subghz->subghz_keeloq_decrypt);
subghz_view_keeloq_decrypt_set_callback(
subghz->subghz_keeloq_decrypt, kl_decrypt_view_callback, subghz);
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdKeeloqDecrypt);
ctx->start_tick = furi_get_tick();
if(!kl_ble_start_offload(ctx)) {
char msg[128];
snprintf(msg, sizeof(msg),
"No BLE connection!\n"
"Connect companion app\n"
"and try again.\n\n"
"Fix:0x%08lX\nHop:0x%08lX",
ctx->fix, ctx->hop);
subghz_view_keeloq_decrypt_set_result(
subghz->subghz_keeloq_decrypt, false, msg);
}
}
bool subghz_scene_keeloq_decrypt_on_event(void* context, SceneManagerEvent event) {
SubGhz* subghz = context;
KlDecryptCtx* ctx = (KlDecryptCtx*)(uintptr_t)scene_manager_get_scene_state(
subghz->scene_manager, SubGhzSceneKeeloqDecrypt);
if(!ctx) return false;
if(event.type == SceneManagerEventTypeCustom) {
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,
ctx->recovered_type,
key_name);
subghz_keeloq_keys_save(subghz->keeloq_keys_manager);
subghz_view_keeloq_decrypt_set_result(
subghz->subghz_keeloq_decrypt, true, furi_string_get_cstr(ctx->result));
} else if(!ctx->cancel) {
subghz_view_keeloq_decrypt_set_result(
subghz->subghz_keeloq_decrypt, false,
"Key NOT found.\nNo matching key in\n2^32 search space.");
} else {
subghz_view_keeloq_decrypt_set_result(
subghz->subghz_keeloq_decrypt, false, "Cancelled.");
}
return true;
} else if(event.event == SubGhzCustomEventViewTransmitterBack) {
if(ctx->ble_offload) {
Bt* bt = furi_record_open(RECORD_BT);
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;
}
}
return false;
}
void subghz_scene_keeloq_decrypt_on_exit(void* context) {
SubGhz* subghz = context;
KlDecryptCtx* ctx = (KlDecryptCtx*)(uintptr_t)scene_manager_get_scene_state(
subghz->scene_manager, SubGhzSceneKeeloqDecrypt);
if(ctx) {
kl_ble_cleanup(ctx);
ctx->cancel = true;
furi_string_free(ctx->result);
free(ctx);
scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneKeeloqDecrypt, 0);
}
}

View File

@@ -17,7 +17,6 @@ void subghz_scene_saved_menu_submenu_callback(void* context, uint32_t index) {
void subghz_scene_saved_menu_on_enter(void* context) { void subghz_scene_saved_menu_on_enter(void* context) {
SubGhz* subghz = context; SubGhz* subghz = context;
// Check protocol type for conditional menu items
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx); FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
bool is_psa_encrypted = false; bool is_psa_encrypted = false;
bool has_counter = false; bool has_counter = false;
@@ -26,7 +25,6 @@ void subghz_scene_saved_menu_on_enter(void* context) {
flipper_format_rewind(fff); flipper_format_rewind(fff);
if(flipper_format_read_string(fff, "Protocol", proto)) { if(flipper_format_read_string(fff, "Protocol", proto)) {
if(furi_string_equal_str(proto, "PSA GROUP")) { if(furi_string_equal_str(proto, "PSA GROUP")) {
// Check if Type field is missing or zero (not yet decrypted)
FuriString* type_str = furi_string_alloc(); FuriString* type_str = furi_string_alloc();
flipper_format_rewind(fff); flipper_format_rewind(fff);
if(!flipper_format_read_string(fff, "Type", type_str) || if(!flipper_format_read_string(fff, "Type", type_str) ||
@@ -39,7 +37,6 @@ void subghz_scene_saved_menu_on_enter(void* context) {
furi_string_free(proto); furi_string_free(proto);
} }
// Check if protocol has a Cnt field (supports counter bruteforce)
if(fff) { if(fff) {
uint32_t cnt_tmp = 0; uint32_t cnt_tmp = 0;
flipper_format_rewind(fff); flipper_format_rewind(fff);

View File

@@ -55,6 +55,12 @@ void subghz_scene_start_on_enter(void* context) {
SubmenuIndexKeeloqKeys, SubmenuIndexKeeloqKeys,
subghz_scene_start_submenu_callback, subghz_scene_start_submenu_callback,
subghz); subghz);
submenu_add_item(
subghz->submenu,
"KeeLoq BF (2 Signals)",
SubmenuIndexKeeloqBf2,
subghz_scene_start_submenu_callback,
subghz);
submenu_set_selected_item( submenu_set_selected_item(
subghz->submenu, scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneStart)); subghz->submenu, scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneStart));
@@ -112,6 +118,11 @@ bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) {
subghz->scene_manager, SubGhzSceneStart, SubmenuIndexKeeloqKeys); subghz->scene_manager, SubGhzSceneStart, SubmenuIndexKeeloqKeys);
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneKeeloqKeys); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneKeeloqKeys);
return true; return true;
} else if(event.event == SubmenuIndexKeeloqBf2) {
scene_manager_set_scene_state(
subghz->scene_manager, SubGhzSceneStart, SubmenuIndexKeeloqBf2);
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneKeeloqBf2);
return true;
} }
} }
return false; return false;

View File

@@ -10,4 +10,5 @@ enum SubmenuIndex {
SubmenuIndexProtocolList, SubmenuIndexProtocolList,
SubmenuIndexRadioSetting, SubmenuIndexRadioSetting,
SubmenuIndexKeeloqKeys, SubmenuIndexKeeloqKeys,
SubmenuIndexKeeloqBf2,
}; };

View File

@@ -95,6 +95,11 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
subghz->keeloq_keys_manager = NULL; subghz->keeloq_keys_manager = NULL;
subghz->keeloq_bf2.sig1_loaded = false;
subghz->keeloq_bf2.sig2_loaded = false;
subghz->keeloq_bf2.sig1_path = furi_string_alloc();
subghz->keeloq_bf2.sig2_path = furi_string_alloc();
subghz->file_path = furi_string_alloc(); subghz->file_path = furi_string_alloc();
subghz->file_path_tmp = furi_string_alloc(); subghz->file_path_tmp = furi_string_alloc();
@@ -195,6 +200,12 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
SubGhzViewIdPsaDecrypt, SubGhzViewIdPsaDecrypt,
subghz_view_psa_decrypt_get_view(subghz->subghz_psa_decrypt)); subghz_view_psa_decrypt_get_view(subghz->subghz_psa_decrypt));
subghz->subghz_keeloq_decrypt = subghz_view_keeloq_decrypt_alloc();
view_dispatcher_add_view(
subghz->view_dispatcher,
SubGhzViewIdKeeloqDecrypt,
subghz_view_keeloq_decrypt_get_view(subghz->subghz_keeloq_decrypt));
//init threshold rssi //init threshold rssi
subghz->threshold_rssi = subghz_threshold_rssi_alloc(); subghz->threshold_rssi = subghz_threshold_rssi_alloc();
@@ -306,6 +317,10 @@ void subghz_free(SubGhz* subghz, bool alloc_for_tx_only) {
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdPsaDecrypt); view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdPsaDecrypt);
subghz_view_psa_decrypt_free(subghz->subghz_psa_decrypt); subghz_view_psa_decrypt_free(subghz->subghz_psa_decrypt);
// KeeLoq Decrypt
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdKeeloqDecrypt);
subghz_view_keeloq_decrypt_free(subghz->subghz_keeloq_decrypt);
// Read RAW // Read RAW
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdReadRAW); view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdReadRAW);
subghz_read_raw_free(subghz->subghz_read_raw); subghz_read_raw_free(subghz->subghz_read_raw);
@@ -353,7 +368,9 @@ void subghz_free(SubGhz* subghz, bool alloc_for_tx_only) {
furi_string_free(subghz->file_path); furi_string_free(subghz->file_path);
furi_string_free(subghz->file_path_tmp); furi_string_free(subghz->file_path_tmp);
// KeeLoq key manager (may still be live if app exited from within the edit scene) furi_string_free(subghz->keeloq_bf2.sig1_path);
furi_string_free(subghz->keeloq_bf2.sig2_path);
if(subghz->keeloq_keys_manager) { if(subghz->keeloq_keys_manager) {
subghz_keeloq_keys_free(subghz->keeloq_keys_manager); subghz_keeloq_keys_free(subghz->keeloq_keys_manager);
subghz->keeloq_keys_manager = NULL; subghz->keeloq_keys_manager = NULL;

View File

@@ -9,6 +9,7 @@
#include "views/subghz_frequency_analyzer.h" #include "views/subghz_frequency_analyzer.h"
#include "views/subghz_read_raw.h" #include "views/subghz_read_raw.h"
#include "views/subghz_psa_decrypt.h" #include "views/subghz_psa_decrypt.h"
#include "views/subghz_keeloq_decrypt.h"
#include <gui/gui.h> #include <gui/gui.h>
#include <assets_icons.h> #include <assets_icons.h>
@@ -74,6 +75,7 @@ struct SubGhz {
SubGhzFrequencyAnalyzer* subghz_frequency_analyzer; SubGhzFrequencyAnalyzer* subghz_frequency_analyzer;
SubGhzReadRAW* subghz_read_raw; SubGhzReadRAW* subghz_read_raw;
SubGhzViewPsaDecrypt* subghz_psa_decrypt; SubGhzViewPsaDecrypt* subghz_psa_decrypt;
SubGhzViewKeeloqDecrypt* subghz_keeloq_decrypt;
bool raw_send_only; bool raw_send_only;
bool save_datetime_set; bool save_datetime_set;
@@ -102,13 +104,24 @@ struct SubGhz {
// KeeLoq key management // KeeLoq key management
SubGhzKeeloqKeysManager* keeloq_keys_manager; SubGhzKeeloqKeysManager* keeloq_keys_manager;
struct { struct {
uint8_t key_bytes[8]; // ByteInput result uint8_t key_bytes[8];
char name[65]; // TextInput result char name[65];
uint16_t type; // selected learning type 1..8 uint16_t type;
bool is_new; // true = add, false = edit bool is_new;
size_t edit_index; // valid when is_new == false size_t edit_index;
uint8_t edit_step; // 0 = key, 1 = name, 2 = type uint8_t edit_step;
} keeloq_edit; } keeloq_edit;
struct {
uint32_t fix;
uint32_t hop1;
uint32_t hop2;
uint32_t serial;
bool sig1_loaded;
bool sig2_loaded;
FuriString* sig1_path;
FuriString* sig2_path;
} keeloq_bf2;
}; };
void subghz_blink_start(SubGhz* subghz); void subghz_blink_start(SubGhz* subghz);

View File

@@ -0,0 +1,227 @@
#include "subghz_keeloq_decrypt.h"
#include <gui/elements.h>
#include <furi.h>
#define KL_TOTAL_KEYS 0x100000000ULL
struct SubGhzViewKeeloqDecrypt {
View* view;
SubGhzViewKeeloqDecryptCallback callback;
void* context;
};
typedef struct {
uint8_t progress;
uint32_t keys_tested;
uint32_t keys_per_sec;
uint32_t elapsed_sec;
uint32_t eta_sec;
bool done;
bool success;
FuriString* result_str;
char status_line[40];
} SubGhzKeeloqDecryptModel;
static void subghz_view_keeloq_decrypt_format_count(char* buf, size_t len, uint32_t count) {
if(count >= 1000000) {
snprintf(buf, len, "%lu.%luM", count / 1000000, (count % 1000000) / 100000);
} else if(count >= 1000) {
snprintf(buf, len, "%luK", count / 1000);
} else {
snprintf(buf, len, "%lu", count);
}
}
static void subghz_view_keeloq_decrypt_draw(Canvas* canvas, void* _model) {
SubGhzKeeloqDecryptModel* model = (SubGhzKeeloqDecryptModel*)_model;
canvas_clear(canvas);
if(!model->done) {
canvas_set_font(canvas, FontPrimary);
if(model->status_line[0]) {
canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, model->status_line);
} else {
canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, "KeeLoq BF");
}
canvas_draw_rframe(canvas, 3, 15, 122, 12, 2);
uint8_t fill = (uint8_t)((uint16_t)model->progress * 116 / 100);
if(fill > 0) {
canvas_draw_rbox(canvas, 5, 17, fill, 8, 1);
}
canvas_set_font(canvas, FontSecondary);
char keys_str[32];
char tested_buf[12];
subghz_view_keeloq_decrypt_format_count(tested_buf, sizeof(tested_buf), model->keys_tested);
snprintf(keys_str, sizeof(keys_str), "%d%% - %s / 4G keys", model->progress, tested_buf);
canvas_draw_str(canvas, 2, 38, keys_str);
char speed_str[40];
char speed_buf[12];
subghz_view_keeloq_decrypt_format_count(speed_buf, sizeof(speed_buf), model->keys_per_sec);
uint32_t eta_m = model->eta_sec / 60;
uint32_t eta_s = model->eta_sec % 60;
if(eta_m > 0) {
snprintf(speed_str, sizeof(speed_str), "%s keys/sec ETA %lum %lus", speed_buf, eta_m, eta_s);
} else {
snprintf(speed_str, sizeof(speed_str), "%s keys/sec ETA %lus", speed_buf, eta_s);
}
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);
} else {
snprintf(elapsed_str, sizeof(elapsed_str), "Elapsed: %lus", el_s);
}
canvas_draw_str(canvas, 2, 58, elapsed_str);
canvas_draw_str_aligned(canvas, 126, 64, AlignRight, AlignBottom, "Hold BACK");
} else {
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));
}
}
}
static bool subghz_view_keeloq_decrypt_input(InputEvent* event, void* context) {
SubGhzViewKeeloqDecrypt* instance = (SubGhzViewKeeloqDecrypt*)context;
if(event->key == InputKeyBack) {
if(instance->callback) {
instance->callback(SubGhzCustomEventViewTransmitterBack, instance->context);
}
return true;
}
return false;
}
SubGhzViewKeeloqDecrypt* subghz_view_keeloq_decrypt_alloc(void) {
SubGhzViewKeeloqDecrypt* instance = malloc(sizeof(SubGhzViewKeeloqDecrypt));
instance->view = view_alloc();
view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(SubGhzKeeloqDecryptModel));
view_set_context(instance->view, instance);
view_set_draw_callback(instance->view, subghz_view_keeloq_decrypt_draw);
view_set_input_callback(instance->view, subghz_view_keeloq_decrypt_input);
with_view_model(
instance->view,
SubGhzKeeloqDecryptModel * model,
{
model->result_str = furi_string_alloc();
model->progress = 0;
model->keys_tested = 0;
model->keys_per_sec = 0;
model->elapsed_sec = 0;
model->eta_sec = 0;
model->done = false;
model->success = false;
},
false);
return instance;
}
void subghz_view_keeloq_decrypt_free(SubGhzViewKeeloqDecrypt* instance) {
furi_check(instance);
with_view_model(
instance->view,
SubGhzKeeloqDecryptModel * model,
{ furi_string_free(model->result_str); },
false);
view_free(instance->view);
free(instance);
}
View* subghz_view_keeloq_decrypt_get_view(SubGhzViewKeeloqDecrypt* instance) {
furi_check(instance);
return instance->view;
}
void subghz_view_keeloq_decrypt_set_callback(
SubGhzViewKeeloqDecrypt* instance,
SubGhzViewKeeloqDecryptCallback callback,
void* context) {
furi_check(instance);
instance->callback = callback;
instance->context = context;
}
void subghz_view_keeloq_decrypt_update_stats(
SubGhzViewKeeloqDecrypt* instance,
uint8_t progress,
uint32_t keys_tested,
uint32_t keys_per_sec,
uint32_t elapsed_sec,
uint32_t eta_sec) {
furi_check(instance);
with_view_model(
instance->view,
SubGhzKeeloqDecryptModel * model,
{
model->progress = progress;
model->keys_tested = keys_tested;
model->keys_per_sec = keys_per_sec;
model->elapsed_sec = elapsed_sec;
model->eta_sec = eta_sec;
},
true);
}
void subghz_view_keeloq_decrypt_set_result(
SubGhzViewKeeloqDecrypt* instance,
bool success,
const char* result) {
furi_check(instance);
with_view_model(
instance->view,
SubGhzKeeloqDecryptModel * model,
{
model->done = true;
model->success = success;
furi_string_set_str(model->result_str, result);
},
true);
}
void subghz_view_keeloq_decrypt_reset(SubGhzViewKeeloqDecrypt* instance) {
furi_check(instance);
with_view_model(
instance->view,
SubGhzKeeloqDecryptModel * model,
{
model->progress = 0;
model->keys_tested = 0;
model->keys_per_sec = 0;
model->elapsed_sec = 0;
model->eta_sec = 0;
model->done = false;
model->success = false;
furi_string_reset(model->result_str);
model->status_line[0] = '\0';
},
false);
}
void subghz_view_keeloq_decrypt_set_status(SubGhzViewKeeloqDecrypt* instance, const char* status) {
furi_check(instance);
with_view_model(
instance->view,
SubGhzKeeloqDecryptModel * model,
{
if(status) {
strlcpy(model->status_line, status, sizeof(model->status_line));
} else {
model->status_line[0] = '\0';
}
},
true);
}

View File

@@ -0,0 +1,34 @@
#pragma once
#include <gui/view.h>
#include "../helpers/subghz_custom_event.h"
typedef struct SubGhzViewKeeloqDecrypt SubGhzViewKeeloqDecrypt;
typedef void (*SubGhzViewKeeloqDecryptCallback)(SubGhzCustomEvent event, void* context);
SubGhzViewKeeloqDecrypt* subghz_view_keeloq_decrypt_alloc(void);
void subghz_view_keeloq_decrypt_free(SubGhzViewKeeloqDecrypt* instance);
View* subghz_view_keeloq_decrypt_get_view(SubGhzViewKeeloqDecrypt* instance);
void subghz_view_keeloq_decrypt_set_callback(
SubGhzViewKeeloqDecrypt* instance,
SubGhzViewKeeloqDecryptCallback callback,
void* context);
void subghz_view_keeloq_decrypt_update_stats(
SubGhzViewKeeloqDecrypt* instance,
uint8_t progress,
uint32_t keys_tested,
uint32_t keys_per_sec,
uint32_t elapsed_sec,
uint32_t eta_sec);
void subghz_view_keeloq_decrypt_set_result(
SubGhzViewKeeloqDecrypt* instance,
bool success,
const char* result);
void subghz_view_keeloq_decrypt_reset(SubGhzViewKeeloqDecrypt* instance);
void subghz_view_keeloq_decrypt_set_status(SubGhzViewKeeloqDecrypt* instance, const char* status);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 511 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 518 B

After

Width:  |  Height:  |  Size: 968 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 483 B

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,10 +1,18 @@
#include "fiat_marelli.h" #include "fiat_marelli.h"
#include <inttypes.h>
#include "../blocks/const.h"
#include "../blocks/decoder.h"
#include "../blocks/encoder.h"
#include "../blocks/generic.h"
#include "../blocks/math.h"
#include "../blocks/custom_btn_i.h"
#include <lib/toolbox/manchester_decoder.h> #include <lib/toolbox/manchester_decoder.h>
#include <lib/toolbox/manchester_encoder.h>
#include <furi_hal_subghz.h>
#define TAG "FiatMarelli" #define TAG "FiatMarelli"
// Magneti Marelli BSI keyfob protocol // Magneti Marelli BSI keyfob protocol (PCF7946)
// Found on: Fiat Panda, Grande Punto (and possibly other Fiat/Lancia/Alfa ~2003-2012) // Found on: Fiat Panda, Grande Punto (and possibly other Fiat/Lancia/Alfa ~2003-2012)
// //
// RF: 433.92 MHz, Manchester encoding // RF: 433.92 MHz, Manchester encoding
@@ -13,44 +21,25 @@
// Type B (e.g. Grande Punto): te_short ~100us, te_long ~200us // Type B (e.g. Grande Punto): te_short ~100us, te_long ~200us
// TE is auto-detected from preamble pulse averaging. // TE is auto-detected from preamble pulse averaging.
// //
// Preamble: many short-short pairs (alternating TE HIGH/LOW)
// Gap: ~12x TE LOW
// Sync: ~8x TE HIGH
// Data: 103-104 Manchester bits (13 bytes), first 14-16 bits are 0xFFF preamble residue
// Retransmissions: 7-10 per press
//
// Frame layout (103-104 bits = 13 bytes): // Frame layout (103-104 bits = 13 bytes):
// Bytes 0-1: 0xFFFF/0xFFFC preamble residue // Bytes 0-1: 0xFFFF/0xFFFC preamble residue
// Bytes 2-5: Fixed ID / Serial (32 bits) // Bytes 2-5: Serial (32 bits)
// Byte 6: [Button:4 | Epoch:4] // Byte 6: [Button:4 | Epoch:4]
// Button (upper nibble): 0x7=Lock, 0xB=Unlock, 0xD=Trunk
// Epoch (lower nibble): 4-bit counter extension (decrements on counter wrap)
// Byte 7: [Counter:5 | Scramble:2 | Fixed:1] // Byte 7: [Counter:5 | Scramble:2 | Fixed:1]
// Counter: 5-bit plaintext decrementing counter (MSBs of byte)
// Scramble: 2 bits dependent on counter/button/epoch
// LSB: fixed (1 for Type A, 0 for Type B)
// Bytes 8-12: Encrypted payload (40 bits) // Bytes 8-12: Encrypted payload (40 bits)
// Fixed bits: bit 37=0, bit 38=1, bit 47=0 (relative to rolling code)
//
// Full counter: 52 bits = (Epoch << 48) | Rolling_48bit (shared across all buttons)
// Cipher: proprietary, ~38 effective encrypted bits, weak MSB diffusion
// Preamble: accept short pulses in this range for auto-TE detection
#define FIAT_MARELLI_PREAMBLE_PULSE_MIN 50 #define FIAT_MARELLI_PREAMBLE_PULSE_MIN 50
#define FIAT_MARELLI_PREAMBLE_PULSE_MAX 350 #define FIAT_MARELLI_PREAMBLE_PULSE_MAX 350
#define FIAT_MARELLI_PREAMBLE_MIN 80 // Min preamble pulses before gap detection #define FIAT_MARELLI_PREAMBLE_MIN 80
#define FIAT_MARELLI_MAX_DATA_BITS 104 // Max data bits to collect (13 bytes) #define FIAT_MARELLI_MAX_DATA_BITS 104
#define FIAT_MARELLI_MIN_DATA_BITS 80 // Min bits for a valid frame #define FIAT_MARELLI_MIN_DATA_BITS 80
// Gap/sync relative multipliers (applied to auto-detected te_short) #define FIAT_MARELLI_GAP_TE_MULT 4
#define FIAT_MARELLI_GAP_TE_MULT 4 // Gap > 4 * te_short #define FIAT_MARELLI_SYNC_TE_MIN_MULT 4
#define FIAT_MARELLI_SYNC_TE_MIN_MULT 4 // Sync >= 4 * te_short #define FIAT_MARELLI_SYNC_TE_MAX_MULT 12
#define FIAT_MARELLI_SYNC_TE_MAX_MULT 12 // Sync <= 12 * te_short #define FIAT_MARELLI_RETX_GAP_MIN 5000
// Fallback for retransmission detection (no preamble) #define FIAT_MARELLI_RETX_SYNC_MIN 400
#define FIAT_MARELLI_RETX_GAP_MIN 5000 // Direct gap detection from Reset (us) #define FIAT_MARELLI_RETX_SYNC_MAX 2800
#define FIAT_MARELLI_RETX_SYNC_MIN 400 // Retx sync min (us) #define FIAT_MARELLI_TE_TYPE_AB_BOUNDARY 180
#define FIAT_MARELLI_RETX_SYNC_MAX 2800 // Retx sync max (us)
// TE boundary for variant classification
#define FIAT_MARELLI_TE_TYPE_AB_BOUNDARY 180 // < 180 = Type B, >= 180 = Type A
static const SubGhzBlockConst subghz_protocol_fiat_marelli_const = { static const SubGhzBlockConst subghz_protocol_fiat_marelli_const = {
.te_short = 260, .te_short = 260,
@@ -66,20 +55,23 @@ struct SubGhzProtocolDecoderFiatMarelli {
ManchesterState manchester_state; ManchesterState manchester_state;
uint8_t decoder_state; uint8_t decoder_state;
uint16_t preamble_count; uint16_t preamble_count;
uint8_t raw_data[13]; // Up to 104 bits (13 bytes) uint8_t raw_data[13];
uint8_t bit_count; uint8_t bit_count;
uint32_t extra_data; // Bits beyond first 64, right-aligned uint32_t extra_data;
uint32_t te_last; uint32_t te_last;
// Auto-TE detection uint32_t te_sum;
uint32_t te_sum; // Sum of preamble pulse durations uint16_t te_count;
uint16_t te_count; // Number of preamble pulses averaged uint32_t te_detected;
uint32_t te_detected; // Auto-detected te_short (0 = not yet detected)
}; };
struct SubGhzProtocolEncoderFiatMarelli { struct SubGhzProtocolEncoderFiatMarelli {
SubGhzProtocolEncoderBase base; SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder; SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic; SubGhzBlockGeneric generic;
uint8_t raw_data[13];
uint32_t extra_data;
uint8_t bit_count;
uint32_t te_detected;
}; };
typedef enum { typedef enum {
@@ -87,13 +79,9 @@ typedef enum {
FiatMarelliDecoderStepPreamble = 1, FiatMarelliDecoderStepPreamble = 1,
FiatMarelliDecoderStepSync = 2, FiatMarelliDecoderStepSync = 2,
FiatMarelliDecoderStepData = 3, FiatMarelliDecoderStepData = 3,
FiatMarelliDecoderStepRetxSync = 4, // Waiting for sync after large gap (no preamble) FiatMarelliDecoderStepRetxSync = 4,
} FiatMarelliDecoderStep; } FiatMarelliDecoderStep;
// ============================================================================
// PROTOCOL INTERFACE DEFINITIONS
// ============================================================================
const SubGhzProtocolDecoder subghz_protocol_fiat_marelli_decoder = { const SubGhzProtocolDecoder subghz_protocol_fiat_marelli_decoder = {
.alloc = subghz_protocol_decoder_fiat_marelli_alloc, .alloc = subghz_protocol_decoder_fiat_marelli_alloc,
.free = subghz_protocol_decoder_fiat_marelli_free, .free = subghz_protocol_decoder_fiat_marelli_free,
@@ -117,21 +105,29 @@ const SubGhzProtocol subghz_protocol_fiat_marelli = {
.name = FIAT_MARELLI_PROTOCOL_NAME, .name = FIAT_MARELLI_PROTOCOL_NAME,
.type = SubGhzProtocolTypeDynamic, .type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save, SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_fiat_marelli_decoder, .decoder = &subghz_protocol_fiat_marelli_decoder,
.encoder = &subghz_protocol_fiat_marelli_encoder, .encoder = &subghz_protocol_fiat_marelli_encoder,
}; };
// ============================================================================ // ============================================================================
// ENCODER STUBS (decode-only protocol) // Encoder
// ============================================================================ // ============================================================================
#define FIAT_MARELLI_ENCODER_UPLOAD_MAX 1500
#define FIAT_MARELLI_ENCODER_REPEAT 3
#define FIAT_MARELLI_PREAMBLE_PAIRS 100
void* subghz_protocol_encoder_fiat_marelli_alloc(SubGhzEnvironment* environment) { void* subghz_protocol_encoder_fiat_marelli_alloc(SubGhzEnvironment* environment) {
UNUSED(environment); UNUSED(environment);
SubGhzProtocolEncoderFiatMarelli* instance = calloc(1, sizeof(SubGhzProtocolEncoderFiatMarelli)); SubGhzProtocolEncoderFiatMarelli* instance = calloc(1, sizeof(SubGhzProtocolEncoderFiatMarelli));
furi_check(instance); furi_check(instance);
instance->base.protocol = &subghz_protocol_fiat_marelli; instance->base.protocol = &subghz_protocol_fiat_marelli;
instance->generic.protocol_name = instance->base.protocol->name; instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = FIAT_MARELLI_ENCODER_REPEAT;
instance->encoder.size_upload = FIAT_MARELLI_ENCODER_UPLOAD_MAX;
instance->encoder.upload = malloc(FIAT_MARELLI_ENCODER_UPLOAD_MAX * sizeof(LevelDuration));
furi_check(instance->encoder.upload);
instance->encoder.is_running = false; instance->encoder.is_running = false;
return instance; return instance;
} }
@@ -139,14 +135,142 @@ void* subghz_protocol_encoder_fiat_marelli_alloc(SubGhzEnvironment* environment)
void subghz_protocol_encoder_fiat_marelli_free(void* context) { void subghz_protocol_encoder_fiat_marelli_free(void* context) {
furi_check(context); furi_check(context);
SubGhzProtocolEncoderFiatMarelli* instance = context; SubGhzProtocolEncoderFiatMarelli* instance = context;
free(instance->encoder.upload);
free(instance); free(instance);
} }
// Manchester encoding from decoder FSM:
// From Mid1: bit 1 = LOW_TE + HIGH_TE, bit 0 = LOW_2TE
// From Mid0: bit 0 = HIGH_TE + LOW_TE, bit 1 = HIGH_2TE
static bool fiat_marelli_encoder_get_upload(SubGhzProtocolEncoderFiatMarelli* instance) {
uint32_t te = instance->te_detected;
if(te == 0) te = subghz_protocol_fiat_marelli_const.te_short;
uint32_t te_short = te;
uint32_t te_long = te * 2;
uint32_t gap_duration = te * 12;
uint32_t sync_duration = te * 8;
size_t index = 0;
size_t max_upload = FIAT_MARELLI_ENCODER_UPLOAD_MAX;
uint8_t data_bits = instance->bit_count;
if(data_bits == 0) data_bits = instance->generic.data_count_bit;
if(data_bits < FIAT_MARELLI_MIN_DATA_BITS || data_bits > FIAT_MARELLI_MAX_DATA_BITS) {
return false;
}
for(uint8_t i = 0; i < FIAT_MARELLI_PREAMBLE_PAIRS && (index + 1) < max_upload; i++) {
instance->encoder.upload[index++] = level_duration_make(true, te_short);
if(i < FIAT_MARELLI_PREAMBLE_PAIRS - 1) {
instance->encoder.upload[index++] = level_duration_make(false, te_short);
}
}
if(index < max_upload) {
instance->encoder.upload[index++] = level_duration_make(false, te_short + gap_duration);
}
if(index < max_upload) {
instance->encoder.upload[index++] = level_duration_make(true, sync_duration);
}
bool in_mid1 = true;
for(uint8_t bit_i = 0; bit_i < data_bits && (index + 1) < max_upload; bit_i++) {
uint8_t byte_idx = bit_i / 8;
uint8_t bit_pos = 7 - (bit_i % 8);
bool data_bit = (instance->raw_data[byte_idx] >> bit_pos) & 1;
if(in_mid1) {
if(data_bit) {
instance->encoder.upload[index++] = level_duration_make(false, te_short);
instance->encoder.upload[index++] = level_duration_make(true, te_short);
} else {
instance->encoder.upload[index++] = level_duration_make(false, te_long);
in_mid1 = false;
}
} else {
if(data_bit) {
instance->encoder.upload[index++] = level_duration_make(true, te_long);
in_mid1 = true;
} else {
instance->encoder.upload[index++] = level_duration_make(true, te_short);
instance->encoder.upload[index++] = level_duration_make(false, te_short);
}
}
}
if(in_mid1) {
if(index < max_upload) {
instance->encoder.upload[index++] =
level_duration_make(false, te_short + gap_duration * 3);
}
} else {
if(index > 0) {
instance->encoder.upload[index - 1] =
level_duration_make(false, te_short + gap_duration * 3);
}
}
instance->encoder.size_upload = index;
return index > 0;
}
static void fiat_marelli_encoder_rebuild_raw_data(SubGhzProtocolEncoderFiatMarelli* instance) {
memset(instance->raw_data, 0, sizeof(instance->raw_data));
uint64_t key = instance->generic.data;
for(int i = 0; i < 8; i++) {
instance->raw_data[i] = (uint8_t)(key >> (56 - i * 8));
}
uint8_t extra_bits =
instance->generic.data_count_bit > 64 ? (instance->generic.data_count_bit - 64) : 0;
for(uint8_t i = 0; i < extra_bits && i < 32; i++) {
uint8_t byte_idx = 8 + (i / 8);
uint8_t bit_pos = 7 - (i % 8);
if(instance->extra_data & (1UL << (extra_bits - 1 - i))) {
instance->raw_data[byte_idx] |= (1 << bit_pos);
}
}
instance->bit_count = instance->generic.data_count_bit;
}
SubGhzProtocolStatus SubGhzProtocolStatus
subghz_protocol_encoder_fiat_marelli_deserialize(void* context, FlipperFormat* flipper_format) { subghz_protocol_encoder_fiat_marelli_deserialize(void* context, FlipperFormat* flipper_format) {
UNUSED(context); furi_check(context);
UNUSED(flipper_format); SubGhzProtocolEncoderFiatMarelli* instance = context;
return SubGhzProtocolStatusError; SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
if(ret != SubGhzProtocolStatusOk) break;
uint32_t extra = 0;
if(flipper_format_read_uint32(flipper_format, "Extra", &extra, 1)) {
instance->extra_data = extra;
}
uint32_t te = 0;
if(flipper_format_read_uint32(flipper_format, "TE", &te, 1)) {
instance->te_detected = te;
}
fiat_marelli_encoder_rebuild_raw_data(instance);
if(!fiat_marelli_encoder_get_upload(instance)) {
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
break;
}
instance->encoder.repeat = FIAT_MARELLI_ENCODER_REPEAT;
instance->encoder.front = 0;
instance->encoder.is_running = true;
} while(false);
return ret;
} }
void subghz_protocol_encoder_fiat_marelli_stop(void* context) { void subghz_protocol_encoder_fiat_marelli_stop(void* context) {
@@ -156,25 +280,38 @@ void subghz_protocol_encoder_fiat_marelli_stop(void* context) {
} }
LevelDuration subghz_protocol_encoder_fiat_marelli_yield(void* context) { LevelDuration subghz_protocol_encoder_fiat_marelli_yield(void* context) {
UNUSED(context); furi_check(context);
return level_duration_reset(); SubGhzProtocolEncoderFiatMarelli* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
if(!subghz_block_generic_global.endless_tx) {
instance->encoder.repeat--;
}
instance->encoder.front = 0;
}
return ret;
} }
// ============================================================================ // ============================================================================
// DECODER IMPLEMENTATION // Decoder
// ============================================================================ // ============================================================================
// Helper: rebuild raw_data[] from generic.data + extra_data
static void fiat_marelli_rebuild_raw_data(SubGhzProtocolDecoderFiatMarelli* instance) { static void fiat_marelli_rebuild_raw_data(SubGhzProtocolDecoderFiatMarelli* instance) {
memset(instance->raw_data, 0, sizeof(instance->raw_data)); memset(instance->raw_data, 0, sizeof(instance->raw_data));
// First 64 bits from generic.data
uint64_t key = instance->generic.data; uint64_t key = instance->generic.data;
for(int i = 0; i < 8; i++) { for(int i = 0; i < 8; i++) {
instance->raw_data[i] = (uint8_t)(key >> (56 - i * 8)); instance->raw_data[i] = (uint8_t)(key >> (56 - i * 8));
} }
// Remaining bits from extra_data (right-aligned)
uint8_t extra_bits = uint8_t extra_bits =
instance->generic.data_count_bit > 64 ? (instance->generic.data_count_bit - 64) : 0; instance->generic.data_count_bit > 64 ? (instance->generic.data_count_bit - 64) : 0;
for(uint8_t i = 0; i < extra_bits && i < 32; i++) { for(uint8_t i = 0; i < extra_bits && i < 32; i++) {
@@ -187,7 +324,6 @@ static void fiat_marelli_rebuild_raw_data(SubGhzProtocolDecoderFiatMarelli* inst
instance->bit_count = instance->generic.data_count_bit; instance->bit_count = instance->generic.data_count_bit;
// Re-extract protocol fields from raw_data (needed after deserialize)
if(instance->bit_count >= 56) { if(instance->bit_count >= 56) {
instance->generic.serial = instance->generic.serial =
((uint32_t)instance->raw_data[2] << 24) | ((uint32_t)instance->raw_data[2] << 24) |
@@ -199,7 +335,6 @@ static void fiat_marelli_rebuild_raw_data(SubGhzProtocolDecoderFiatMarelli* inst
} }
} }
// Helper: prepare data collection state for Manchester decoding
static void fiat_marelli_prepare_data(SubGhzProtocolDecoderFiatMarelli* instance) { static void fiat_marelli_prepare_data(SubGhzProtocolDecoderFiatMarelli* instance) {
instance->bit_count = 0; instance->bit_count = 0;
instance->extra_data = 0; instance->extra_data = 0;
@@ -249,12 +384,9 @@ void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32
furi_check(context); furi_check(context);
SubGhzProtocolDecoderFiatMarelli* instance = context; SubGhzProtocolDecoderFiatMarelli* instance = context;
// Use auto-detected TE if available, otherwise fall back to defaults
uint32_t te_short = instance->te_detected ? instance->te_detected uint32_t te_short = instance->te_detected ? instance->te_detected
: (uint32_t)subghz_protocol_fiat_marelli_const.te_short; : (uint32_t)subghz_protocol_fiat_marelli_const.te_short;
uint32_t te_long = te_short * 2; uint32_t te_long = te_short * 2;
// Delta = te_short/2: maximum that avoids short/long overlap (boundary at 1.5*TE).
// Must be this wide for Type B asymmetric timing (pos~140us, neg~68us, avg~100us).
uint32_t te_delta = te_short / 2; uint32_t te_delta = te_short / 2;
if(te_delta < 30) te_delta = 30; if(te_delta < 30) te_delta = 30;
uint32_t diff; uint32_t diff;
@@ -262,7 +394,6 @@ void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32
switch(instance->decoder_state) { switch(instance->decoder_state) {
case FiatMarelliDecoderStepReset: case FiatMarelliDecoderStepReset:
if(level) { if(level) {
// Check for preamble-like short HIGH pulse (50-350us range)
if(duration >= FIAT_MARELLI_PREAMBLE_PULSE_MIN && if(duration >= FIAT_MARELLI_PREAMBLE_PULSE_MIN &&
duration <= FIAT_MARELLI_PREAMBLE_PULSE_MAX) { duration <= FIAT_MARELLI_PREAMBLE_PULSE_MAX) {
instance->decoder_state = FiatMarelliDecoderStepPreamble; instance->decoder_state = FiatMarelliDecoderStepPreamble;
@@ -272,7 +403,6 @@ void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32
instance->te_last = duration; instance->te_last = duration;
} }
} else { } else {
// Large LOW gap without preamble -> retransmission path
if(duration > FIAT_MARELLI_RETX_GAP_MIN) { if(duration > FIAT_MARELLI_RETX_GAP_MIN) {
instance->decoder_state = FiatMarelliDecoderStepRetxSync; instance->decoder_state = FiatMarelliDecoderStepRetxSync;
instance->te_last = duration; instance->te_last = duration;
@@ -283,20 +413,16 @@ void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32
case FiatMarelliDecoderStepPreamble: case FiatMarelliDecoderStepPreamble:
if(duration >= FIAT_MARELLI_PREAMBLE_PULSE_MIN && if(duration >= FIAT_MARELLI_PREAMBLE_PULSE_MIN &&
duration <= FIAT_MARELLI_PREAMBLE_PULSE_MAX) { duration <= FIAT_MARELLI_PREAMBLE_PULSE_MAX) {
// Short pulse (HIGH or LOW) - preamble continues
instance->preamble_count++; instance->preamble_count++;
instance->te_sum += duration; instance->te_sum += duration;
instance->te_count++; instance->te_count++;
instance->te_last = duration; instance->te_last = duration;
} else if(!level) { } else if(!level) {
// Non-short LOW pulse - could be gap after preamble
if(instance->preamble_count >= FIAT_MARELLI_PREAMBLE_MIN && instance->te_count > 0) { if(instance->preamble_count >= FIAT_MARELLI_PREAMBLE_MIN && instance->te_count > 0) {
// Compute auto-detected TE from preamble average
instance->te_detected = instance->te_sum / instance->te_count; instance->te_detected = instance->te_sum / instance->te_count;
uint32_t gap_threshold = instance->te_detected * FIAT_MARELLI_GAP_TE_MULT; uint32_t gap_threshold = instance->te_detected * FIAT_MARELLI_GAP_TE_MULT;
if(duration > gap_threshold) { if(duration > gap_threshold) {
// Gap detected - wait for sync
instance->decoder_state = FiatMarelliDecoderStepSync; instance->decoder_state = FiatMarelliDecoderStepSync;
instance->te_last = duration; instance->te_last = duration;
} else { } else {
@@ -306,13 +432,11 @@ void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32
instance->decoder_state = FiatMarelliDecoderStepReset; instance->decoder_state = FiatMarelliDecoderStepReset;
} }
} else { } else {
// Non-short HIGH pulse during preamble - reset
instance->decoder_state = FiatMarelliDecoderStepReset; instance->decoder_state = FiatMarelliDecoderStepReset;
} }
break; break;
case FiatMarelliDecoderStepSync: { case FiatMarelliDecoderStepSync: {
// Expect sync HIGH pulse (scaled to detected TE)
uint32_t sync_min = instance->te_detected * FIAT_MARELLI_SYNC_TE_MIN_MULT; uint32_t sync_min = instance->te_detected * FIAT_MARELLI_SYNC_TE_MIN_MULT;
uint32_t sync_max = instance->te_detected * FIAT_MARELLI_SYNC_TE_MAX_MULT; uint32_t sync_max = instance->te_detected * FIAT_MARELLI_SYNC_TE_MAX_MULT;
@@ -326,14 +450,10 @@ void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32
} }
case FiatMarelliDecoderStepRetxSync: case FiatMarelliDecoderStepRetxSync:
// Retransmission path: expect sync HIGH pulse after large gap
// Use broad range since we don't know TE yet
if(level && duration >= FIAT_MARELLI_RETX_SYNC_MIN && if(level && duration >= FIAT_MARELLI_RETX_SYNC_MIN &&
duration <= FIAT_MARELLI_RETX_SYNC_MAX) { duration <= FIAT_MARELLI_RETX_SYNC_MAX) {
// Auto-detect TE from sync pulse (sync is ~8x TE)
if(!instance->te_detected) { if(!instance->te_detected) {
instance->te_detected = duration / 8; instance->te_detected = duration / 8;
// Clamp to reasonable range
if(instance->te_detected < 70) instance->te_detected = 100; if(instance->te_detected < 70) instance->te_detected = 100;
if(instance->te_detected > 350) instance->te_detected = 260; if(instance->te_detected > 350) instance->te_detected = 260;
} }
@@ -348,7 +468,6 @@ void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32
ManchesterEvent event = ManchesterEventReset; ManchesterEvent event = ManchesterEventReset;
bool frame_complete = false; bool frame_complete = false;
// Classify duration as short or long Manchester edge using detected TE
diff = (duration > te_short) ? (duration - te_short) : (te_short - duration); diff = (duration > te_short) ? (duration - te_short) : (te_short - duration);
if(diff < te_delta) { if(diff < te_delta) {
event = level ? ManchesterEventShortLow : ManchesterEventShortHigh; event = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
@@ -399,42 +518,14 @@ void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32
if(frame_complete) { if(frame_complete) {
instance->generic.data_count_bit = instance->bit_count; instance->generic.data_count_bit = instance->bit_count;
// Frame layout: bytes 0-1 are preamble residue (0xFFFF or 0xFFFC)
// Bytes 2-5: Fixed ID (serial)
// Byte 6: [Button:4 | Epoch:4]
// Byte 7: [Counter:5 | Scramble:2 | Fixed:1]
// Bytes 8-12: Encrypted payload (40 bits)
instance->generic.serial = instance->generic.serial =
((uint32_t)instance->raw_data[2] << 24) | ((uint32_t)instance->raw_data[2] << 24) |
((uint32_t)instance->raw_data[3] << 16) | ((uint32_t)instance->raw_data[3] << 16) |
((uint32_t)instance->raw_data[4] << 8) | ((uint32_t)instance->raw_data[4] << 8) |
((uint32_t)instance->raw_data[5]); ((uint32_t)instance->raw_data[5]);
instance->generic.btn = (instance->raw_data[6] >> 4) & 0xF; instance->generic.btn = (instance->raw_data[6] >> 4) & 0xF;
// cnt: 5-bit plaintext counter from byte 7 upper bits
instance->generic.cnt = (instance->raw_data[7] >> 3) & 0x1F; instance->generic.cnt = (instance->raw_data[7] >> 3) & 0x1F;
const char* variant = (instance->te_detected &&
instance->te_detected < FIAT_MARELLI_TE_TYPE_AB_BOUNDARY)
? "B"
: "A";
FURI_LOG_I(
TAG,
"Type%s TE:%lu %db Sn:%08lX Btn:0x%X Ep:%X Ctr:%lu Roll:%02X%02X%02X%02X%02X%02X",
variant,
instance->te_detected ? instance->te_detected : te_short,
instance->bit_count,
instance->generic.serial,
instance->generic.btn,
instance->raw_data[6] & 0xF,
instance->generic.cnt,
instance->raw_data[7],
instance->raw_data[8],
instance->raw_data[9],
instance->raw_data[10],
instance->raw_data[11],
instance->raw_data[12]);
if(instance->base.callback) { if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context); instance->base.callback(&instance->base, instance->base.context);
} }
@@ -471,16 +562,13 @@ SubGhzProtocolStatus subghz_protocol_decoder_fiat_marelli_serialize(
subghz_block_generic_serialize(&instance->generic, flipper_format, preset); subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
if(ret == SubGhzProtocolStatusOk) { if(ret == SubGhzProtocolStatusOk) {
// Save extra data (bits 64+ right-aligned in uint32_t)
flipper_format_write_uint32(flipper_format, "Extra", &instance->extra_data, 1); flipper_format_write_uint32(flipper_format, "Extra", &instance->extra_data, 1);
// Save total bit count explicitly (generic serialize also saves it, but Extra needs context)
uint32_t extra_bits = instance->generic.data_count_bit > 64 uint32_t extra_bits = instance->generic.data_count_bit > 64
? (instance->generic.data_count_bit - 64) ? (instance->generic.data_count_bit - 64)
: 0; : 0;
flipper_format_write_uint32(flipper_format, "Extra_bits", &extra_bits, 1); flipper_format_write_uint32(flipper_format, "Extra_bits", &extra_bits, 1);
// Save detected TE for variant identification on reload
uint32_t te = instance->te_detected; uint32_t te = instance->te_detected;
flipper_format_write_uint32(flipper_format, "TE", &te, 1); flipper_format_write_uint32(flipper_format, "TE", &te, 1);
} }
@@ -531,34 +619,35 @@ void subghz_protocol_decoder_fiat_marelli_get_string(void* context, FuriString*
furi_check(context); furi_check(context);
SubGhzProtocolDecoderFiatMarelli* instance = context; SubGhzProtocolDecoderFiatMarelli* instance = context;
uint8_t total_bytes = (instance->bit_count + 7) / 8;
if(total_bytes > 13) total_bytes = 13;
uint8_t epoch = instance->raw_data[6] & 0xF; uint8_t epoch = instance->raw_data[6] & 0xF;
uint8_t counter = (instance->raw_data[7] >> 3) & 0x1F; uint8_t counter = (instance->raw_data[7] >> 3) & 0x1F;
const char* variant = (instance->te_detected && const char* variant = (instance->te_detected &&
instance->te_detected < FIAT_MARELLI_TE_TYPE_AB_BOUNDARY) instance->te_detected < FIAT_MARELLI_TE_TYPE_AB_BOUNDARY)
? "B" ? "B"
: "A"; : "A";
uint8_t scramble = (instance->raw_data[7] >> 1) & 0x3;
uint8_t fixed = instance->raw_data[7] & 0x1;
furi_string_cat_printf( furi_string_cat_printf(
output, output,
"%s %dbit Type%s\r\n" "%s %dbit\r\n"
"Sn:%08lX Btn:%s\r\n" "Enc:%02X%02X%02X%02X%02X Scr:%02X\r\n"
"Ep:%X Ctr:%02d\r\n" "Raw:%02X%02X Fixed:%X\r\n"
"R:%02X%02X%02X%02X%02X%02X", "Sn:%08X Cnt:%02X\r\n"
"Btn:%02X:[%s] Ep:%02X\r\n"
"Tp:%s\r\n",
instance->generic.protocol_name, instance->generic.protocol_name,
instance->bit_count, (int)instance->bit_count,
variant, instance->raw_data[8], instance->raw_data[9],
instance->generic.serial, instance->raw_data[10], instance->raw_data[11],
instance->raw_data[12],
(unsigned)scramble,
instance->raw_data[6], instance->raw_data[7],
(unsigned)fixed,
(unsigned int)instance->generic.serial,
(unsigned)counter,
(unsigned)instance->generic.btn,
fiat_marelli_button_name(instance->generic.btn), fiat_marelli_button_name(instance->generic.btn),
epoch, (unsigned)epoch,
counter, variant);
instance->raw_data[7],
instance->raw_data[8],
instance->raw_data[9],
(total_bytes > 10) ? instance->raw_data[10] : 0,
(total_bytes > 11) ? instance->raw_data[11] : 0,
(total_bytes > 12) ? instance->raw_data[12] : 0);
} }

View File

@@ -1,14 +1,6 @@
#pragma once #pragma once
#include <furi.h> #include "base.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> #include <flipper_format/flipper_format.h>
#define FIAT_MARELLI_PROTOCOL_NAME "Fiat Marelli" #define FIAT_MARELLI_PROTOCOL_NAME "Fiat Marelli"
@@ -31,7 +23,6 @@ SubGhzProtocolStatus
subghz_protocol_decoder_fiat_marelli_deserialize(void* context, FlipperFormat* flipper_format); subghz_protocol_decoder_fiat_marelli_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_fiat_marelli_get_string(void* context, FuriString* output); void subghz_protocol_decoder_fiat_marelli_get_string(void* context, FuriString* output);
// Encoder stubs
void* subghz_protocol_encoder_fiat_marelli_alloc(SubGhzEnvironment* environment); void* subghz_protocol_encoder_fiat_marelli_alloc(SubGhzEnvironment* environment);
void subghz_protocol_encoder_fiat_marelli_free(void* context); void subghz_protocol_encoder_fiat_marelli_free(void* context);
SubGhzProtocolStatus SubGhzProtocolStatus