mirror of
https://github.com/D4C1-Labs/Flipper-ARF.git
synced 2026-03-29 19:49:59 +00:00
Compare commits
6 Commits
dev-fb1c28
...
dev-f347d5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f347d5a976 | ||
|
|
3a6da87288 | ||
|
|
5d94639d81 | ||
|
|
5dcfc48e10 | ||
|
|
20a95b2fec | ||
|
|
3605669cc5 |
@@ -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
|
||||
@@ -54,16 +58,12 @@ 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 == 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;
|
||||
|
||||
ctx->candidate_count++;
|
||||
@@ -74,47 +74,13 @@ static void kl_ble_data_received(uint8_t* data, uint16_t size, void* context) {
|
||||
subghz_view_keeloq_decrypt_update_candidates(
|
||||
ctx->subghz->subghz_keeloq_decrypt, ctx->candidate_count);
|
||||
|
||||
if(!ctx->subghz->keeloq_keys_manager) {
|
||||
ctx->subghz->keeloq_keys_manager = subghz_keeloq_keys_alloc();
|
||||
}
|
||||
char key_name[32];
|
||||
snprintf(key_name, sizeof(key_name), "BF_%07lX_%lu", ctx->serial, ctx->candidate_count);
|
||||
subghz_keeloq_keys_add(
|
||||
ctx->subghz->keeloq_keys_manager,
|
||||
mfkey,
|
||||
learn_type,
|
||||
key_name);
|
||||
subghz_keeloq_keys_save(ctx->subghz->keeloq_keys_manager);
|
||||
view_dispatcher_send_custom_event(
|
||||
ctx->subghz->view_dispatcher, KL_DECRYPT_EVENT_CANDIDATE);
|
||||
|
||||
} else if(found == 2) {
|
||||
ctx->success = (ctx->candidate_count > 0);
|
||||
|
||||
if(ctx->candidate_count > 0) {
|
||||
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,
|
||||
ctx->recovered_cnt);
|
||||
|
||||
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 = ctx->recovered_cnt;
|
||||
flipper_format_rewind(fff);
|
||||
flipper_format_insert_or_update_uint32(fff, "Cnt", &cnt_val, 1);
|
||||
}
|
||||
|
||||
view_dispatcher_send_custom_event(ctx->subghz->view_dispatcher, KL_DECRYPT_EVENT_DONE);
|
||||
view_dispatcher_send_custom_event(
|
||||
ctx->subghz->view_dispatcher, KL_DECRYPT_EVENT_DONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,16 +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));
|
||||
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,
|
||||
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));
|
||||
@@ -242,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;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
|
||||
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];
|
||||
@@ -12,7 +14,7 @@ typedef struct {
|
||||
size_t valid_count;
|
||||
} KlCleanupCtx;
|
||||
|
||||
static bool kl_cleanup_validate_key(uint64_t key, uint32_t hop, uint8_t btn, uint16_t disc) {
|
||||
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;
|
||||
@@ -21,6 +23,18 @@ static bool kl_cleanup_validate_key(uint64_t key, uint32_t hop, uint8_t btn, uin
|
||||
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;
|
||||
|
||||
@@ -32,15 +46,19 @@ void subghz_scene_kl_bf_cleanup_on_enter(void* context) {
|
||||
|
||||
uint8_t key_data[8] = {0};
|
||||
if(flipper_format_read_hex(fff, "Key", key_data, 8)) {
|
||||
uint32_t fix = ((uint32_t)key_data[0] << 24) | ((uint32_t)key_data[1] << 16) |
|
||||
((uint32_t)key_data[2] << 8) | key_data[3];
|
||||
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 = fix & 0x0FFFFFFF;
|
||||
ctx->btn = fix >> 28;
|
||||
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);
|
||||
|
||||
@@ -48,9 +66,8 @@ void subghz_scene_kl_bf_cleanup_on_enter(void* context) {
|
||||
subghz->keeloq_keys_manager = subghz_keeloq_keys_alloc();
|
||||
}
|
||||
|
||||
char prefix[16];
|
||||
snprintf(prefix, sizeof(prefix), "BF_%07lX_", ctx->serial);
|
||||
size_t prefix_len = strlen(prefix);
|
||||
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;
|
||||
@@ -60,9 +77,9 @@ void subghz_scene_kl_bf_cleanup_on_enter(void* context) {
|
||||
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(strncmp(name, prefix, prefix_len) == 0) {
|
||||
if(strcmp(name, bf_name) == 0) {
|
||||
ctx->bf_indices[ctx->bf_count] = i;
|
||||
if(kl_cleanup_validate_key(k->key, ctx->hop, ctx->btn, ctx->disc)) {
|
||||
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++;
|
||||
@@ -83,26 +100,11 @@ void subghz_scene_kl_bf_cleanup_on_enter(void* context) {
|
||||
deleted++;
|
||||
}
|
||||
}
|
||||
|
||||
char new_name[24];
|
||||
snprintf(new_name, sizeof(new_name), "BF_%07lX", ctx->serial);
|
||||
|
||||
size_t new_user_count = subghz_keeloq_keys_user_count(subghz->keeloq_keys_manager);
|
||||
for(size_t i = 0; i < new_user_count; i++) {
|
||||
SubGhzKey* k = subghz_keeloq_keys_get(subghz->keeloq_keys_manager, i);
|
||||
if(!k || !k->name) continue;
|
||||
const char* n = furi_string_get_cstr(k->name);
|
||||
if(strncmp(n, prefix, prefix_len) == 0) {
|
||||
subghz_keeloq_keys_set(
|
||||
subghz->keeloq_keys_manager, i, k->key, k->type, new_name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
subghz_keeloq_keys_save(subghz->keeloq_keys_manager);
|
||||
|
||||
furi_string_printf(msg,
|
||||
"Cleaned %u keys.\nKept valid key:\n%s",
|
||||
deleted, new_name);
|
||||
deleted, bf_name);
|
||||
} else if(ctx->valid_count == 0) {
|
||||
furi_string_printf(msg,
|
||||
"%u BF keys found\nbut none validates\nhop. Kept all.",
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
|
||||
enum SubmenuIndex {
|
||||
SubmenuIndexEmulate,
|
||||
SubmenuIndexPsaDecrypt,
|
||||
SubmenuIndexEdit,
|
||||
SubmenuIndexDelete,
|
||||
SubmenuIndexSignalSettings,
|
||||
SubmenuIndexPsaDecrypt,
|
||||
SubmenuIndexCounterBf,
|
||||
SubmenuIndexKlBfCleanup,
|
||||
SubmenuIndexCounterBf
|
||||
};
|
||||
|
||||
void subghz_scene_saved_menu_submenu_callback(void* context, uint32_t index) {
|
||||
@@ -46,24 +45,23 @@ void subghz_scene_saved_menu_on_enter(void* context) {
|
||||
}
|
||||
}
|
||||
|
||||
bool has_bf_keys = false;
|
||||
if(fff) {
|
||||
FuriString* mfg = furi_string_alloc();
|
||||
flipper_format_rewind(fff);
|
||||
if(flipper_format_read_string(fff, "Manufacture", mfg)) {
|
||||
if(furi_string_start_with_str(mfg, "BF_")) {
|
||||
has_bf_keys = true;
|
||||
}
|
||||
}
|
||||
furi_string_free(mfg);
|
||||
if(!is_psa_encrypted) {
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
"Emulate",
|
||||
SubmenuIndexEmulate,
|
||||
subghz_scene_saved_menu_submenu_callback,
|
||||
subghz);
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -86,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,
|
||||
@@ -103,14 +94,6 @@ void subghz_scene_saved_menu_on_enter(void* context) {
|
||||
subghz_scene_saved_menu_submenu_callback,
|
||||
subghz);
|
||||
}
|
||||
if(has_bf_keys) {
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
"Clean BF Keys",
|
||||
SubmenuIndexKlBfCleanup,
|
||||
subghz_scene_saved_menu_submenu_callback,
|
||||
subghz);
|
||||
}
|
||||
|
||||
submenu_set_selected_item(
|
||||
subghz->submenu,
|
||||
@@ -128,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);
|
||||
@@ -143,21 +131,11 @@ 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);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneCounterBf);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexKlBfCleanup) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexKlBfCleanup);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneKlBfCleanup);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
517
lib/subghz/protocols/fiat_spa.c
Normal file
517
lib/subghz/protocols/fiat_spa.c
Normal 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;
|
||||
}
|
||||
32
lib/subghz/protocols/fiat_spa.h
Normal file
32
lib/subghz/protocols/fiat_spa.h
Normal 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);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user