From a4937375eaacccd6b9e75f0d1ea2e512589a8d74 Mon Sep 17 00:00:00 2001 From: RocketGod <57732082+RocketGod-git@users.noreply.github.com> Date: Sun, 11 Jan 2026 20:13:21 -0800 Subject: [PATCH] @xMasterX (MMX) --> Add StarLine and Scher-Khan protocol support plus encryptions and fixes This commit is from @xMasterX (MMX) and introduces implementation files for StarLine and Scher-Khan SubGhz protocols, including protocol decoders, encoders, and keeloq key management. Adds common protocol utilities, keystore handling, and protocol-specific logic for key generation, serialization, and deserialization. --- README.md | 4 + keystore/encrypted | 31 ++ protocols/keeloq_common.c | 142 +++++ protocols/keeloq_common.h | 115 ++++ protocols/keys.c | 40 ++ protocols/protocols_common.c | 18 + protocols/protocols_common.h | 5 + protocols/scher_khan.c | 361 ++++++++++++ protocols/scher_khan.h | 82 +++ protocols/star_line.c | 1009 ++++++++++++++++++++++++++++++++++ protocols/star_line.h | 117 ++++ protopirate_app.c | 98 ++-- 12 files changed, 1979 insertions(+), 43 deletions(-) create mode 100644 keystore/encrypted create mode 100644 protocols/keeloq_common.c create mode 100644 protocols/keeloq_common.h create mode 100644 protocols/keys.c create mode 100644 protocols/protocols_common.c create mode 100644 protocols/protocols_common.h create mode 100644 protocols/scher_khan.c create mode 100644 protocols/scher_khan.h create mode 100644 protocols/star_line.c create mode 100644 protocols/star_line.h diff --git a/README.md b/README.md index 0fd302e..79b258e 100644 --- a/README.md +++ b/README.md @@ -17,15 +17,19 @@ Decoders: - KIA V6 - Fiat - Ford V0 +- Scher-Khan +- StarLine - Subaru - Suzuki - Volkswagen (VW) Encoders: +- Ford V0 - KIA V0 - KIA V1 - KIA V2 +- StarLine - More Coming Soon ## **Features** diff --git a/keystore/encrypted b/keystore/encrypted new file mode 100644 index 0000000..bc3707f --- /dev/null +++ b/keystore/encrypted @@ -0,0 +1,31 @@ +Filetype: Flipper SubGhz Keystore File +Version: 0 +Encryption: 1 +IV: 4E 6F 20 70 72 65 79 2C 20 6E 6F 20 70 61 79 2E +EA7FE261D240CE6414D1D361E43AF320E38F6D922EB21E548D8D435464559BB7 +29BA3E03BB2DB4C615050DFB7946828673099C4F6D86D09441161FB10CDB7B4D +18F800AA5B0332F2C0F4BCEB3D59686800BAB251D8F0DBC53B1F4EAC8A2E2387 +7C097B02B18F69AE74596FB4DF6CE3351434CDAAE5412D231160BF91A1EDB0EF +08251F5CCE10B6BD15D9756FD52E67BCADBA41D15C995954BE029D44B5DB05AF +A5D615FF2348A9D96005C5E730633F4CC40D302BB2DF71FC64FD9E4E8196EAC9461F1D8F28E7B4A74226BD87D41B71C9 +07F37E323DA69045B0A1638CAD103026116C79C4E7E4C722D9F2174EE9E329AF +37ED45AA9A2F5A9417D37810EC3119B2F20DACF5CD844E5DEEED456A71FE7C4E +46186986D6729A519FE9625C1ADDE6DFE29C0CE839B02FDCE35D6D5FEA364DBC +AE4A7B44CA6BFBFF8F95B31CE369FE72558AD42382DCF0FD80760E0714B45995 +943C2F4CB7ACAA63D21B94AF50C2E0DB718E519B0021B2D8D7AF5769F6399F78 +252941621E705A49F60DF319A66D1C59A545029EDE8492A858659DC692B3429685D7042BCA24CBD281179C2CD73156A3 +8CE09F0770B9BA239E45F30A49EC136DAA0E4AC613B3E315B494DA424C7E5FE61F99FEF4DED839E4B2956FAA59657281 +CD2F173EDEB6BFB8754583D1E1C8CCD65CC6E3E79B5A868A53AA350E419B6607 +E49D6A039693731B6919CA29B416BE7067065D05F5B30BF9D8D1E0B8AC8EF364 +026BD7A08BE3A3D0C3EBDA6FDD0206D83A2CFA891272FF1629169B03EA37E300 +D9EE70D91292697EEAA6BA6564DB652182002B9A70848C3897C2CDBC129027DF +31AB2CAE71DA2C317EC216D90342B969660B7FE0956D24D4B3C8C9DFD8A69E7F4A36675B34EC109CDBB88C044985CA23 +9C3A04D257748CC33FB013977E675EEBCECA502A3D10F4D2D7A28871A14A9D95 +543D8FBCFA879F2DBB023AA07D80FB0BDAABA864811AB7E38FC9A425A04C48EF +DB8D083B1F209874629B046CC27E2DED5A251801C395CDF4A6940B4F05E13FA29CFC470020B217A3F94DF94D0C9C9EA4 +7FD6DD318E2D9E3BB137247BC70D42D9B5ED18C93ED07154B932A8EDD107F012AB65283CE6668CA555EFADE608DDA79C +9E381D3E7D3D58FBECB3512C7D5DBDFB5E1708C79DC68E2EA6B13D82D3F9F851 +EC148A795CA521C1EE70EA4FB1377DAE06CA957CA9DBDFB970314B95E4AD9A89 +A2BE6299F878A91A60D3C0F4DD882A50F20EED7DB00C28AAC0A9E3B4609EAAC1 +0889904CC32C152F3A5A6874E2C172075F46AB4E63C696E35211CC609953F8D7 +E654760783674373EB889D80597AA35C416E8086FCE5DE4C02CA2EA42EBD569CA4A67F691ABC0209A6B208BAD5593CEE diff --git a/protocols/keeloq_common.c b/protocols/keeloq_common.c new file mode 100644 index 0000000..a9a2238 --- /dev/null +++ b/protocols/keeloq_common.c @@ -0,0 +1,142 @@ +#include "keeloq_common.h" + +#include + +#include + +#define bit(x, n) (((x) >> (n)) & 1) +#define g5(x, a, b, c, d, e) \ + (bit(x, a) + bit(x, b) * 2 + bit(x, c) * 4 + bit(x, d) * 8 + bit(x, e) * 16) + +/** Simple Learning Encrypt + * @param data - 0xBSSSCCCC, B(4bit) key, S(10bit) serial&0x3FF, C(16bit) counter + * @param key - manufacture (64bit) + * @return keeloq encrypt data + */ +inline uint32_t subghz_protocol_keeloq_common_encrypt(const uint32_t data, const uint64_t key) { + uint32_t x = data, r; + for(r = 0; r < 528; r++) + x = (x >> 1) ^ ((bit(x, 0) ^ bit(x, 16) ^ (uint32_t)bit(key, r & 63) ^ + bit(KEELOQ_NLF, g5(x, 1, 9, 20, 26, 31))) + << 31); + return x; +} + +/** Simple Learning Decrypt + * @param data - keeloq encrypt data + * @param key - manufacture (64bit) + * @return 0xBSSSCCCC, B(4bit) key, S(10bit) serial&0x3FF, C(16bit) counter + */ +inline uint32_t subghz_protocol_keeloq_common_decrypt(const uint32_t data, const uint64_t key) { + uint32_t x = data, r; + for(r = 0; r < 528; r++) + x = (x << 1) ^ bit(x, 31) ^ bit(x, 15) ^ (uint32_t)bit(key, (15 - r) & 63) ^ + bit(KEELOQ_NLF, g5(x, 0, 8, 19, 25, 30)); + return x; +} + +/** Normal Learning + * @param data - serial number (28bit) + * @param key - manufacture (64bit) + * @return manufacture for this serial number (64bit) + */ +inline uint64_t subghz_protocol_keeloq_common_normal_learning(uint32_t data, const uint64_t key) { + uint32_t k1, k2; + + data &= 0x0FFFFFFF; + data |= 0x20000000; + k1 = subghz_protocol_keeloq_common_decrypt(data, key); + + data &= 0x0FFFFFFF; + data |= 0x60000000; + k2 = subghz_protocol_keeloq_common_decrypt(data, key); + + return ((uint64_t)k2 << 32) | k1; // key - shifrovanoya +} + +/** Secure Learning + * @param data - serial number (28bit) + * @param seed - seed number (32bit) + * @param key - manufacture (64bit) + * @return manufacture for this serial number (64bit) + */ + +inline uint64_t subghz_protocol_keeloq_common_secure_learning( + uint32_t data, + uint32_t seed, + const uint64_t key) { + uint32_t k1, k2; + + data &= 0x0FFFFFFF; + k1 = subghz_protocol_keeloq_common_decrypt(data, key); + k2 = subghz_protocol_keeloq_common_decrypt(seed, key); + + return ((uint64_t)k1 << 32) | k2; +} + +/** Magic_xor_type1 Learning + * @param data - serial number (28bit) + * @param xor - magic xor (64bit) + * @return manufacture for this serial number (64bit) + */ + +inline uint64_t + subghz_protocol_keeloq_common_magic_xor_type1_learning(uint32_t data, uint64_t xor) { + data &= 0x0FFFFFFF; + return (((uint64_t)data << 32) | data) ^ xor; +} + +/** Faac SLH (Spa) Learning + * @param seed - seed number (32bit) + * @param key - mfkey (64bit) + * @return man_learning for this seed number (64bit) + */ + +inline uint64_t + subghz_protocol_keeloq_common_faac_learning(const uint32_t seed, const uint64_t key) { + uint16_t hs = seed >> 16; + const uint16_t ending = 0x544D; + uint32_t lsb = (uint32_t)hs << 16 | ending; + uint64_t man = (uint64_t)subghz_protocol_keeloq_common_encrypt(seed, key) << 32 | + subghz_protocol_keeloq_common_encrypt(lsb, key); + return man; +} +/** Magic_serial_type1 Learning + * @param data - serial number (28bit) + * @param man - magic man (64bit) + * @return manufacture for this serial number (64bit) + */ + +inline uint64_t + subghz_protocol_keeloq_common_magic_serial_type1_learning(uint32_t data, uint64_t man) { + return (man & 0xFFFFFFFF) | ((uint64_t)data << 40) | + ((uint64_t)(((data & 0xff) + ((data >> 8) & 0xFF)) & 0xFF) << 32); +} + +/** Magic_serial_type2 Learning + * @param data - btn+serial number (32bit) + * @param man - magic man (64bit) + * @return manufacture for this serial number (64bit) + */ + +inline uint64_t + subghz_protocol_keeloq_common_magic_serial_type2_learning(uint32_t data, uint64_t man) { + uint8_t* p = (uint8_t*)&data; + uint8_t* m = (uint8_t*)&man; + m[7] = p[0]; + m[6] = p[1]; + m[5] = p[2]; + m[4] = p[3]; + return man; +} + +/** Magic_serial_type3 Learning + * @param data - serial number (24bit) + * @param man - magic man (64bit) + * @return manufacture for this serial number (64bit) + */ + +inline uint64_t + subghz_protocol_keeloq_common_magic_serial_type3_learning(uint32_t data, uint64_t man) { + return (man & 0xFFFFFFFFFF000000) | (data & 0xFFFFFF); +} diff --git a/protocols/keeloq_common.h b/protocols/keeloq_common.h new file mode 100644 index 0000000..e265f6a --- /dev/null +++ b/protocols/keeloq_common.h @@ -0,0 +1,115 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +struct SubGhzKeystore { + SubGhzKeyArray_t data; + const char* mfname; + uint8_t kl_type; +}; + +/* + * Keeloq + * https://ru.wikipedia.org/wiki/KeeLoq + * https://phreakerclub.com/forum/showthread.php?t=1094 + * + */ +#define KEELOQ_NLF 0x3A5C742E + +/* + * KeeLoq learning types + * https://phreakerclub.com/forum/showthread.php?t=67 + */ +#define KEELOQ_LEARNING_UNKNOWN 0u +#define KEELOQ_LEARNING_SIMPLE 1u +#define KEELOQ_LEARNING_NORMAL 2u +// #define KEELOQ_LEARNING_SECURE 3u +#define KEELOQ_LEARNING_MAGIC_XOR_TYPE_1 4u +// #define KEELOQ_LEARNING_FAAC 5u +#define KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_1 6u +#define KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_2 7u +#define KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_3 8u + +/** + * Simple Learning Encrypt + * @param data - 0xBSSSCCCC, B(4bit) key, S(10bit) serial&0x3FF, C(16bit) counter + * @param key - manufacture (64bit) + * @return keeloq encrypt data + */ +uint32_t subghz_protocol_keeloq_common_encrypt(const uint32_t data, const uint64_t key); + +/** + * Simple Learning Decrypt + * @param data - keeloq encrypt data + * @param key - manufacture (64bit) + * @return 0xBSSSCCCC, B(4bit) key, S(10bit) serial&0x3FF, C(16bit) counter + */ +uint32_t subghz_protocol_keeloq_common_decrypt(const uint32_t data, const uint64_t key); + +/** + * Normal Learning + * @param data - serial number (28bit) + * @param key - manufacture (64bit) + * @return manufacture for this serial number (64bit) + */ +uint64_t subghz_protocol_keeloq_common_normal_learning(uint32_t data, const uint64_t key); + +/** + * Secure Learning + * @param data - serial number (28bit) + * @param seed - seed number (32bit) + * @param key - manufacture (64bit) + * @return manufacture for this serial number (64bit) + */ +uint64_t + subghz_protocol_keeloq_common_secure_learning(uint32_t data, uint32_t seed, const uint64_t key); + +/** + * Magic_xor_type1 Learning + * @param data - serial number (28bit) + * @param xor - magic xor (64bit) + * @return manufacture for this serial number (64bit) + */ +uint64_t subghz_protocol_keeloq_common_magic_xor_type1_learning(uint32_t data, uint64_t xor); + +/** Faac SLH (Spa) Learning + * @param seed - seed number (32bit) + * @param key - mfkey (64bit) + * @return man_learning for this fix number (64bit) + */ +uint64_t subghz_protocol_keeloq_common_faac_learning(const uint32_t seed, const uint64_t key); + +/** Magic_serial_type1 Learning + * @param data - serial number (28bit) + * @param man - magic man (64bit) + * @return manufacture for this serial number (64bit) + */ + +uint64_t subghz_protocol_keeloq_common_magic_serial_type1_learning(uint32_t data, uint64_t man); + +/** Magic_serial_type2 Learning + * @param data - btn+serial number (32bit) + * @param man - magic man (64bit) + * @return manufacture for this serial number (64bit) + */ + +uint64_t subghz_protocol_keeloq_common_magic_serial_type2_learning(uint32_t data, uint64_t man); + +/** Magic_serial_type3 Learning + * @param data - btn+serial number (32bit) + * @param man - magic man (64bit) + * @return manufacture for this serial number (64bit) + */ + +uint64_t subghz_protocol_keeloq_common_magic_serial_type3_learning(uint32_t data, uint64_t man); diff --git a/protocols/keys.c b/protocols/keys.c new file mode 100644 index 0000000..b6ef2df --- /dev/null +++ b/protocols/keys.c @@ -0,0 +1,40 @@ +#include "keys.h" + +#define KIA_KEY1 10u +#define KIA_KEY2 11u +#define KIA_KEY3 12u + +uint64_t kia_mf_key = 0; +uint64_t kia_v6_a_key = 0; +uint64_t kia_v6_b_key = 0; + +void protopirate_keys_load(SubGhzEnvironment* environment) { + SubGhzKeystore* keystore = subghz_environment_get_keystore(environment); + // Load keys from secure keystore + for + M_EACH(manufacture_code, *subghz_keystore_get_data(keystore), SubGhzKeyArray_t) { + switch(manufacture_code->type) { + case KIA_KEY1: + kia_mf_key = manufacture_code->key; + break; + case KIA_KEY2: + kia_v6_a_key = manufacture_code->key; + break; + case KIA_KEY3: + kia_v6_b_key = manufacture_code->key; + break; + } + } +} + +uint64_t get_kia_mf_key() { + return kia_mf_key; +} + +uint64_t get_kia_v6_keystore_a() { + return kia_v6_a_key; +} + +uint64_t get_kia_v6_keystore_b() { + return kia_v6_b_key; +} \ No newline at end of file diff --git a/protocols/protocols_common.c b/protocols/protocols_common.c new file mode 100644 index 0000000..ee1ef7b --- /dev/null +++ b/protocols/protocols_common.c @@ -0,0 +1,18 @@ +#include "protocols_common.h" + +const char* protopirate_get_short_preset_name(const char* preset_name) { + if(!strcmp(preset_name, "FuriHalSubGhzPresetOok270Async")) { + return "AM270"; + } else if(!strcmp(preset_name, "FuriHalSubGhzPresetOok650Async")) { + return "AM650"; + } else if(!strcmp(preset_name, "FuriHalSubGhzPreset2FSKDev238Async")) { + return "FM238"; + } else if(!strcmp(preset_name, "FuriHalSubGhzPreset2FSKDev12KAsync")) { + return "FM12K"; + } else if(!strcmp(preset_name, "FuriHalSubGhzPreset2FSKDev476Async")) { + return "FM476"; + } else if(!strcmp(preset_name, "FuriHalSubGhzPresetCustom")) { + return "CUSTOM"; + } + return preset_name; +} \ No newline at end of file diff --git a/protocols/protocols_common.h b/protocols/protocols_common.h new file mode 100644 index 0000000..997b4fb --- /dev/null +++ b/protocols/protocols_common.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +const char* protopirate_get_short_preset_name(const char* preset_name); diff --git a/protocols/scher_khan.c b/protocols/scher_khan.c new file mode 100644 index 0000000..5871522 --- /dev/null +++ b/protocols/scher_khan.c @@ -0,0 +1,361 @@ +#include "scher_khan.h" + +#include "protocols_common.h" + +//https://phreakerclub.com/72 +//https://phreakerclub.com/forum/showthread.php?t=7&page=2 +//https://phreakerclub.com/forum/showthread.php?t=274&highlight=magicar +//!!! https://phreakerclub.com/forum/showthread.php?t=489&highlight=magicar&page=5 + +#define TAG "SubGhzProtocolScherKhan" + +static const SubGhzBlockConst subghz_protocol_scher_khan_const = { + .te_short = 750, + .te_long = 1100, + .te_delta = 180, + .min_count_bit_for_found = 35, +}; + +struct SubGhzProtocolDecoderScherKhan { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint16_t header_count; + const char* protocol_name; +}; + +struct SubGhzProtocolEncoderScherKhan { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + ScherKhanDecoderStepReset = 0, + ScherKhanDecoderStepCheckPreambula, + ScherKhanDecoderStepSaveDuration, + ScherKhanDecoderStepCheckDuration, +} ScherKhanDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_scher_khan_decoder = { + .alloc = subghz_protocol_decoder_scher_khan_alloc, + .free = subghz_protocol_decoder_scher_khan_free, + + .feed = subghz_protocol_decoder_scher_khan_feed, + .reset = subghz_protocol_decoder_scher_khan_reset, + + .get_hash_data = subghz_protocol_decoder_scher_khan_get_hash_data, + .serialize = subghz_protocol_decoder_scher_khan_serialize, + .deserialize = subghz_protocol_decoder_scher_khan_deserialize, + .get_string = subghz_protocol_decoder_scher_khan_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_scher_khan_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol subghz_protocol_scher_khan = { + .name = SUBGHZ_PROTOCOL_SCHER_KHAN_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable, + + .decoder = &subghz_protocol_scher_khan_decoder, + .encoder = &subghz_protocol_scher_khan_encoder, +}; + +void* subghz_protocol_decoder_scher_khan_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderScherKhan* instance = malloc(sizeof(SubGhzProtocolDecoderScherKhan)); + instance->base.protocol = &subghz_protocol_scher_khan; + instance->generic.protocol_name = instance->base.protocol->name; + + return instance; +} + +void subghz_protocol_decoder_scher_khan_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderScherKhan* instance = context; + free(instance); +} + +void subghz_protocol_decoder_scher_khan_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderScherKhan* instance = context; + instance->decoder.parser_step = ScherKhanDecoderStepReset; +} + +void subghz_protocol_decoder_scher_khan_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderScherKhan* instance = context; + + switch(instance->decoder.parser_step) { + case ScherKhanDecoderStepReset: + if((level) && (DURATION_DIFF(duration, subghz_protocol_scher_khan_const.te_short * 2) < + subghz_protocol_scher_khan_const.te_delta)) { + instance->decoder.parser_step = ScherKhanDecoderStepCheckPreambula; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + case ScherKhanDecoderStepCheckPreambula: + if(level) { + if((DURATION_DIFF(duration, subghz_protocol_scher_khan_const.te_short * 2) < + subghz_protocol_scher_khan_const.te_delta) || + (DURATION_DIFF(duration, subghz_protocol_scher_khan_const.te_short) < + subghz_protocol_scher_khan_const.te_delta)) { + instance->decoder.te_last = duration; + } else { + instance->decoder.parser_step = ScherKhanDecoderStepReset; + } + } else if( + (DURATION_DIFF(duration, subghz_protocol_scher_khan_const.te_short * 2) < + subghz_protocol_scher_khan_const.te_delta) || + (DURATION_DIFF(duration, subghz_protocol_scher_khan_const.te_short) < + subghz_protocol_scher_khan_const.te_delta)) { + if(DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_scher_khan_const.te_short * 2) < + subghz_protocol_scher_khan_const.te_delta) { + // Found header + instance->header_count++; + break; + } else if( + DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_scher_khan_const.te_short) < + subghz_protocol_scher_khan_const.te_delta) { + // Found start bit + if(instance->header_count >= 2) { + instance->decoder.parser_step = ScherKhanDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 1; + } else { + instance->decoder.parser_step = ScherKhanDecoderStepReset; + } + } else { + instance->decoder.parser_step = ScherKhanDecoderStepReset; + } + } else { + instance->decoder.parser_step = ScherKhanDecoderStepReset; + } + break; + case ScherKhanDecoderStepSaveDuration: + if(level) { + if(duration >= (subghz_protocol_scher_khan_const.te_delta * 2UL + + subghz_protocol_scher_khan_const.te_long)) { + //Found stop bit + instance->decoder.parser_step = ScherKhanDecoderStepReset; + if(instance->decoder.decode_count_bit >= + subghz_protocol_scher_khan_const.min_count_bit_for_found) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + break; + } else { + instance->decoder.te_last = duration; + instance->decoder.parser_step = ScherKhanDecoderStepCheckDuration; + } + + } else { + instance->decoder.parser_step = ScherKhanDecoderStepReset; + } + break; + case ScherKhanDecoderStepCheckDuration: + if(!level) { + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_scher_khan_const.te_short) < + subghz_protocol_scher_khan_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_scher_khan_const.te_short) < + subghz_protocol_scher_khan_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = ScherKhanDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_scher_khan_const.te_long) < + subghz_protocol_scher_khan_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_scher_khan_const.te_long) < + subghz_protocol_scher_khan_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = ScherKhanDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = ScherKhanDecoderStepReset; + } + } else { + instance->decoder.parser_step = ScherKhanDecoderStepReset; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param protocol_name + */ +static void subghz_protocol_scher_khan_check_remote_controller( + SubGhzBlockGeneric* instance, + const char** protocol_name) { + /* + * MAGICAR 51 bit 00000001A99121DE83C3 MAGIC CODE, Dynamic + * 0E8C1619E830C -> 000011101000110000010110 0001 1001 1110 1000001100001100 + * 0E8C1629D830D -> 000011101000110000010110 0010 1001 1101 1000001100001101 + * 0E8C1649B830E -> 000011101000110000010110 0100 1001 1011 1000001100001110 + * 0E8C16897830F -> 000011101000110000010110 1000 1001 0111 1000001100001111 + * Serial Key Ser ~Key CNT + */ + + switch(instance->data_count_bit) { + case 35: //MAGIC CODE, Static + *protocol_name = "MAGIC CODE, Static"; + instance->serial = 0; + instance->btn = 0; + instance->cnt = 0; + break; + case 51: //MAGIC CODE, Dynamic + *protocol_name = "MAGIC CODE, Dynamic"; + instance->serial = ((instance->data >> 24) & 0xFFFFFF0) | ((instance->data >> 20) & 0x0F); + instance->btn = (instance->data >> 24) & 0x0F; + instance->cnt = instance->data & 0xFFFF; + break; + case 57: //MAGIC CODE PRO / PRO2 + *protocol_name = "MAGIC CODE PRO/PRO2"; + instance->serial = 0; + instance->btn = 0; + instance->cnt = 0; + break; + case 63: //MAGIC CODE, Dynamic Response + *protocol_name = "MAGIC CODE, Response"; + instance->serial = 0; + instance->btn = 0; + instance->cnt = 0; + break; + case 64: //MAGICAR, Response ??? + *protocol_name = "MAGICAR, Response"; + instance->serial = 0; + instance->btn = 0; + instance->cnt = 0; + break; + case 81: // MAGIC CODE PRO / PRO2 Response ??? + case 82: // MAGIC CODE PRO / PRO2 Response ??? + *protocol_name = "MAGIC CODE PRO,\n Response"; + instance->serial = 0; + instance->btn = 0; + instance->cnt = 0; + break; + + default: + *protocol_name = "Unknown"; + instance->serial = 0; + instance->btn = 0; + instance->cnt = 0; + break; + } +} + +uint8_t subghz_protocol_decoder_scher_khan_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderScherKhan* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus subghz_protocol_decoder_scher_khan_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderScherKhan* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + + do { + if(preset != NULL) { + if(!flipper_format_insert_or_update_uint32( + flipper_format, "Frequency", &preset->frequency, 1)) { + break; + } + + const char* preset_name = furi_string_get_cstr(preset->name); + const char* short_preset = protopirate_get_short_preset_name(preset_name); + if(!flipper_format_insert_or_update_string_cstr( + flipper_format, "Preset", short_preset)) { + break; + } + } + + if(!flipper_format_insert_or_update_string_cstr( + flipper_format, "Protocol", instance->generic.protocol_name)) { + break; + } + + uint32_t bits = instance->generic.data_count_bit; + if(!flipper_format_insert_or_update_uint32(flipper_format, "Bit", &bits, 1)) { + break; + } + + char key_str[20]; + snprintf(key_str, sizeof(key_str), "%016llX", instance->generic.data); + if(!flipper_format_insert_or_update_string_cstr(flipper_format, "Key", key_str)) { + break; + } + + if(!flipper_format_insert_or_update_uint32( + flipper_format, "Serial", &instance->generic.serial, 1)) { + break; + } + + uint32_t temp = instance->generic.btn; + if(!flipper_format_insert_or_update_uint32(flipper_format, "Btn", &temp, 1)) { + break; + } + + if(!flipper_format_insert_or_update_uint32( + flipper_format, "Cnt", &instance->generic.cnt, 1)) { + break; + } + + ret = SubGhzProtocolStatusOk; + } while(false); + + return ret; +} + +SubGhzProtocolStatus + subghz_protocol_decoder_scher_khan_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderScherKhan* instance = context; + return subghz_block_generic_deserialize(&instance->generic, flipper_format); +} + +void subghz_protocol_decoder_scher_khan_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderScherKhan* instance = context; + + subghz_protocol_scher_khan_check_remote_controller( + &instance->generic, &instance->protocol_name); + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:%07lX Btn:%X\r\n" + "Cntr:%04lX\r\n" + "Pt: %s\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)instance->generic.data, + instance->generic.serial, + instance->generic.btn, + instance->generic.cnt, + instance->protocol_name); +} diff --git a/protocols/scher_khan.h b/protocols/scher_khan.h new file mode 100644 index 0000000..02025dd --- /dev/null +++ b/protocols/scher_khan.h @@ -0,0 +1,82 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SUBGHZ_PROTOCOL_SCHER_KHAN_NAME "Scher-Khan" + +typedef struct SubGhzProtocolDecoderScherKhan SubGhzProtocolDecoderScherKhan; +typedef struct SubGhzProtocolEncoderScherKhan SubGhzProtocolEncoderScherKhan; + +extern const SubGhzProtocolDecoder subghz_protocol_scher_khan_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_scher_khan_encoder; +extern const SubGhzProtocol subghz_protocol_scher_khan; + +/** + * Allocate SubGhzProtocolDecoderScherKhan. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderScherKhan* pointer to a SubGhzProtocolDecoderScherKhan instance + */ +void* subghz_protocol_decoder_scher_khan_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderScherKhan. + * @param context Pointer to a SubGhzProtocolDecoderScherKhan instance + */ +void subghz_protocol_decoder_scher_khan_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderScherKhan. + * @param context Pointer to a SubGhzProtocolDecoderScherKhan instance + */ +void subghz_protocol_decoder_scher_khan_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderScherKhan instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_scher_khan_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderScherKhan instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_scher_khan_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderScherKhan. + * @param context Pointer to a SubGhzProtocolDecoderScherKhan instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus subghz_protocol_decoder_scher_khan_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderScherKhan. + * @param context Pointer to a SubGhzProtocolDecoderScherKhan instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_decoder_scher_khan_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderScherKhan instance + * @param output Resulting text + */ +void subghz_protocol_decoder_scher_khan_get_string(void* context, FuriString* output); diff --git a/protocols/star_line.c b/protocols/star_line.c new file mode 100644 index 0000000..0dc5b26 --- /dev/null +++ b/protocols/star_line.c @@ -0,0 +1,1009 @@ +#include "star_line.h" +#include "keeloq_common.h" + +#include + +#include "protocols_common.h" + +#define TAG "SubGhzProtocolStarLine" + +static const SubGhzBlockConst subghz_protocol_star_line_const = { + .te_short = 250, + .te_long = 500, + .te_delta = 120, + .min_count_bit_for_found = 64, +}; + +struct SubGhzProtocolDecoderStarLine { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint16_t header_count; + SubGhzKeystore* keystore; + const char* manufacture_name; + + FuriString* manufacture_from_file; +}; + +struct SubGhzProtocolEncoderStarLine { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; + + SubGhzKeystore* keystore; + const char* manufacture_name; + + FuriString* manufacture_from_file; +}; + +typedef enum { + StarLineDecoderStepReset = 0, + StarLineDecoderStepCheckPreambula, + StarLineDecoderStepSaveDuration, + StarLineDecoderStepCheckDuration, +} StarLineDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_star_line_decoder = { + .alloc = subghz_protocol_decoder_star_line_alloc, + .free = subghz_protocol_decoder_star_line_free, + + .feed = subghz_protocol_decoder_star_line_feed, + .reset = subghz_protocol_decoder_star_line_reset, + + .get_hash_data = subghz_protocol_decoder_star_line_get_hash_data, + .serialize = subghz_protocol_decoder_star_line_serialize, + .deserialize = subghz_protocol_decoder_star_line_deserialize, + .get_string = subghz_protocol_decoder_star_line_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_star_line_encoder = { + .alloc = subghz_protocol_encoder_star_line_alloc, + .free = subghz_protocol_encoder_star_line_free, + + .deserialize = subghz_protocol_encoder_star_line_deserialize, + .stop = subghz_protocol_encoder_star_line_stop, + .yield = subghz_protocol_encoder_star_line_yield, +}; + +const SubGhzProtocol subghz_protocol_star_line = { + .name = SUBGHZ_PROTOCOL_STAR_LINE_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_star_line_decoder, + .encoder = &subghz_protocol_star_line_encoder, +}; + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param keystore Pointer to a SubGhzKeystore* instance + * @param manufacture_name + */ +static void subghz_protocol_star_line_check_remote_controller( + SubGhzBlockGeneric* instance, + SubGhzKeystore* keystore, + const char** manufacture_name); + +void* subghz_protocol_encoder_star_line_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolEncoderStarLine* instance = malloc(sizeof(SubGhzProtocolEncoderStarLine)); + + instance->base.protocol = &subghz_protocol_star_line; + instance->generic.protocol_name = instance->base.protocol->name; + instance->keystore = subghz_environment_get_keystore(environment); + + instance->manufacture_from_file = furi_string_alloc(); + + instance->encoder.repeat = 40; + instance->encoder.size_upload = 256; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + + return instance; +} + +void subghz_protocol_encoder_star_line_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderStarLine* instance = context; + furi_string_free(instance->manufacture_from_file); + free(instance->encoder.upload); + free(instance); +} + +/** + * Key generation from simple data + * @param instance Pointer to a SubGhzProtocolEncoderKeeloq* instance + * @param btn Button number, 4 bit + */ +static bool + subghz_protocol_star_line_gen_data(SubGhzProtocolEncoderStarLine* instance, uint8_t btn) { + // Increase counter + if((instance->generic.cnt + 1) > 0xFFFF) { + instance->generic.cnt = 0; + } else { + instance->generic.cnt += 1; + } + + uint32_t fix = btn << 24 | instance->generic.serial; + uint32_t decrypt = btn << 24 | (instance->generic.serial & 0xFF) << 16 | instance->generic.cnt; + uint32_t hop = 0; + uint64_t man = 0; + uint64_t code_found_reverse; + int res = 0; + + if(instance->manufacture_name == 0x0) { + instance->manufacture_name = ""; + } + + if(strcmp(instance->manufacture_name, "Unknown") == 0) { + code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + hop = code_found_reverse & 0x00000000ffffffff; + } else { + uint8_t kl_type_en = instance->keystore->kl_type; + for + M_EACH( + manufacture_code, + *subghz_keystore_get_data(instance->keystore), + SubGhzKeyArray_t) { + res = strcmp( + furi_string_get_cstr(manufacture_code->name), instance->manufacture_name); + if(res == 0) { + switch(manufacture_code->type) { + case KEELOQ_LEARNING_SIMPLE: + //Simple Learning + hop = + subghz_protocol_keeloq_common_encrypt(decrypt, manufacture_code->key); + break; + case KEELOQ_LEARNING_NORMAL: + //Normal Learning + man = subghz_protocol_keeloq_common_normal_learning( + fix, manufacture_code->key); + hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); + break; + case KEELOQ_LEARNING_UNKNOWN: + if(kl_type_en == 1) { + hop = subghz_protocol_keeloq_common_encrypt( + decrypt, manufacture_code->key); + } + if(kl_type_en == 2) { + man = subghz_protocol_keeloq_common_normal_learning( + fix, manufacture_code->key); + hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); + } + break; + } + break; + } + } + } + if(hop) { + uint64_t yek = (uint64_t)fix << 32 | hop; + instance->generic.data = + subghz_protocol_blocks_reverse_key(yek, instance->generic.data_count_bit); + return true; + } else { + instance->manufacture_name = "Unknown"; + return false; + } +} + +bool subghz_protocol_star_line_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + const char* manufacture_name, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolEncoderStarLine* instance = context; + instance->generic.serial = serial; + instance->generic.cnt = cnt; + instance->manufacture_name = manufacture_name; + instance->generic.data_count_bit = 64; + bool res = subghz_protocol_star_line_gen_data(instance, btn); + if(res) { + return SubGhzProtocolStatusOk == + subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + } + return res; +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderKeeloq instance + * @return true On success + */ +static bool subghz_protocol_encoder_star_line_get_upload( + SubGhzProtocolEncoderStarLine* instance, + uint8_t btn) { + furi_assert(instance); + + // Gen new key + if(!subghz_protocol_star_line_gen_data(instance, btn)) { + return false; + } + + size_t index = 0; + size_t size_upload = 6 * 2 + (instance->generic.data_count_bit * 2); + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + //Send header + for(uint8_t i = 6; i > 0; i--) { + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_star_line_const.te_long * 2); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_star_line_const.te_long * 2); + } + + //Send key data + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_star_line_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_star_line_const.te_long); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_star_line_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_star_line_const.te_short); + } + } + + return true; +} + +static SubGhzProtocolStatus subghz_protocol_encoder_star_line_serialize( + SubGhzProtocolEncoderStarLine* instance, + FlipperFormat* flipper_format) { + subghz_protocol_star_line_check_remote_controller( + &instance->generic, instance->keystore, &instance->manufacture_name); + + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + + do { + if(!flipper_format_insert_or_update_string_cstr( + flipper_format, "Protocol", instance->generic.protocol_name)) { + break; + } + + uint32_t bits = instance->generic.data_count_bit; + if(!flipper_format_insert_or_update_uint32(flipper_format, "Bit", &bits, 1)) { + break; + } + + char key_str[20]; + snprintf(key_str, sizeof(key_str), "%016llX", instance->generic.data); + if(!flipper_format_insert_or_update_string_cstr(flipper_format, "Key", key_str)) { + break; + } + + if(!flipper_format_insert_or_update_uint32( + flipper_format, "Serial", &instance->generic.serial, 1)) { + break; + } + + uint32_t temp = instance->generic.btn; + if(!flipper_format_insert_or_update_uint32(flipper_format, "Btn", &temp, 1)) { + break; + } + + if(!flipper_format_insert_or_update_uint32( + flipper_format, "Cnt", &instance->generic.cnt, 1)) { + break; + } + + if(!flipper_format_insert_or_update_string_cstr( + flipper_format, "Manufacture", instance->manufacture_name)) { + break; + } + + ret = SubGhzProtocolStatusOk; + } while(false); + + return ret; + //SubGhzProtocolStatus ret = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + subghz_protocol_encoder_star_line_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderStarLine* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + + flipper_format_rewind(flipper_format); + + do { + FuriString* temp_str = furi_string_alloc(); + if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) { + FURI_LOG_E(TAG, "Missing Protocol"); + furi_string_free(temp_str); + break; + } + + if(!furi_string_equal(temp_str, instance->base.protocol->name)) { + FURI_LOG_E( + TAG, + "Wrong protocol %s != %s", + furi_string_get_cstr(temp_str), + instance->base.protocol->name); + furi_string_free(temp_str); + break; + } + furi_string_free(temp_str); + + uint32_t bit_count_temp; + if(!flipper_format_read_uint32(flipper_format, "Bit", &bit_count_temp, 1)) { + FURI_LOG_E(TAG, "Missing Bit"); + break; + } + + instance->generic.data_count_bit = subghz_protocol_star_line_const.min_count_bit_for_found; + + temp_str = furi_string_alloc(); + if(!flipper_format_read_string(flipper_format, "Key", temp_str)) { + FURI_LOG_E(TAG, "Missing Key"); + furi_string_free(temp_str); + 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 = c - '0'; + } else if(c >= 'A' && c <= 'F') { + nibble = c - 'A' + 10; + } else if(c >= 'a' && c <= 'f') { + nibble = c - 'a' + 10; + } else { + FURI_LOG_E(TAG, "Invalid hex character: %c", c); + furi_string_free(temp_str); + break; + } + + key = (key << 4) | nibble; + hex_pos++; + } + + furi_string_free(temp_str); + + if(hex_pos != 16) { + FURI_LOG_E(TAG, "Invalid key length: %zu nibbles (expected 16)", hex_pos); + break; + } + + instance->generic.data = key; + FURI_LOG_I(TAG, "Parsed key: 0x%016llX", instance->generic.data); + + if(instance->generic.data == 0) { + FURI_LOG_E(TAG, "Key is zero after parsing!"); + break; + } + + if(!flipper_format_read_uint32(flipper_format, "Serial", &instance->generic.serial, 1)) { + instance->generic.serial = instance->generic.data >> 24; + FURI_LOG_I(TAG, "Extracted serial: 0x%08lX", instance->generic.serial); + } else { + FURI_LOG_I(TAG, "Read serial: 0x%08lX", instance->generic.serial); + } + + uint32_t btn_temp; + if(flipper_format_read_uint32(flipper_format, "Btn", &btn_temp, 1)) { + instance->generic.btn = (uint8_t)btn_temp; + FURI_LOG_I(TAG, "Read button: 0x%02X", instance->generic.btn); + } else { + instance->generic.btn = (instance->generic.data >> 16) & 0xFF; + FURI_LOG_I(TAG, "Extracted button: 0x%02X", instance->generic.btn); + } + + uint32_t cnt_temp; + if(flipper_format_read_uint32(flipper_format, "Cnt", &cnt_temp, 1)) { + instance->generic.cnt = (uint16_t)cnt_temp; + FURI_LOG_I(TAG, "Read counter: 0x%03lX", (unsigned long)instance->generic.cnt); + } + + if(!flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1)) { + instance->encoder.repeat = 40; + FURI_LOG_D( + TAG, "Repeat not found in file, using default 40 for continuous transmission"); + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + // Read manufacturer from file + if(flipper_format_read_string( + flipper_format, "Manufacture", instance->manufacture_from_file)) { + instance->manufacture_name = furi_string_get_cstr(instance->manufacture_from_file); + instance->keystore->mfname = instance->manufacture_name; + } else { + FURI_LOG_D(TAG, "ENCODER: Missing Manufacture"); + } + + subghz_protocol_star_line_check_remote_controller( + &instance->generic, instance->keystore, &instance->manufacture_name); + + subghz_protocol_encoder_star_line_get_upload(instance, instance->generic.btn); + + subghz_protocol_encoder_star_line_serialize(instance, flipper_format); + + instance->encoder.is_running = true; + + FURI_LOG_I( + TAG, + "Encoder deserialized: repeat=%u, size_upload=%zu, is_running=%d, front=%zu", + instance->encoder.repeat, + instance->encoder.size_upload, + instance->encoder.is_running, + instance->encoder.front); + + ret = SubGhzProtocolStatusOk; + } while(false); + + return ret; +} + +void subghz_protocol_encoder_star_line_stop(void* context) { + SubGhzProtocolEncoderStarLine* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_star_line_yield(void* context) { + SubGhzProtocolEncoderStarLine* 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) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_star_line_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderStarLine* instance = malloc(sizeof(SubGhzProtocolDecoderStarLine)); + instance->base.protocol = &subghz_protocol_star_line; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->manufacture_from_file = furi_string_alloc(); + + instance->keystore = subghz_environment_get_keystore(environment); + + return instance; +} + +void subghz_protocol_decoder_star_line_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderStarLine* instance = context; + furi_string_free(instance->manufacture_from_file); + + free(instance); +} + +void subghz_protocol_decoder_star_line_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderStarLine* instance = context; + instance->decoder.parser_step = StarLineDecoderStepReset; + // TODO + instance->keystore->mfname = ""; + instance->keystore->kl_type = 0; +} + +void subghz_protocol_decoder_star_line_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderStarLine* instance = context; + + switch(instance->decoder.parser_step) { + case StarLineDecoderStepReset: + if(level) { + if(DURATION_DIFF(duration, subghz_protocol_star_line_const.te_long * 2) < + subghz_protocol_star_line_const.te_delta * 2) { + instance->decoder.parser_step = StarLineDecoderStepCheckPreambula; + instance->header_count++; + } else if(instance->header_count > 4) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.te_last = duration; + instance->decoder.parser_step = StarLineDecoderStepCheckDuration; + } + } else { + instance->header_count = 0; + } + break; + case StarLineDecoderStepCheckPreambula: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_star_line_const.te_long * 2) < + subghz_protocol_star_line_const.te_delta * 2)) { + //Found Preambula + instance->decoder.parser_step = StarLineDecoderStepReset; + } else { + instance->header_count = 0; + instance->decoder.parser_step = StarLineDecoderStepReset; + } + break; + case StarLineDecoderStepSaveDuration: + if(level) { + if(duration >= (subghz_protocol_star_line_const.te_long + + subghz_protocol_star_line_const.te_delta)) { + instance->decoder.parser_step = StarLineDecoderStepReset; + if((instance->decoder.decode_count_bit >= + subghz_protocol_star_line_const.min_count_bit_for_found) && + (instance->decoder.decode_count_bit <= + subghz_protocol_star_line_const.min_count_bit_for_found + 2)) { + if(instance->generic.data != instance->decoder.decode_data) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = + subghz_protocol_star_line_const.min_count_bit_for_found; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->header_count = 0; + break; + } else { + instance->decoder.te_last = duration; + instance->decoder.parser_step = StarLineDecoderStepCheckDuration; + } + + } else { + instance->decoder.parser_step = StarLineDecoderStepReset; + } + break; + case StarLineDecoderStepCheckDuration: + if(!level) { + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_star_line_const.te_short) < + subghz_protocol_star_line_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_star_line_const.te_short) < + subghz_protocol_star_line_const.te_delta)) { + if(instance->decoder.decode_count_bit < + subghz_protocol_star_line_const.min_count_bit_for_found) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } else { + instance->decoder.decode_count_bit++; + } + instance->decoder.parser_step = StarLineDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_star_line_const.te_long) < + subghz_protocol_star_line_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_star_line_const.te_long) < + subghz_protocol_star_line_const.te_delta)) { + if(instance->decoder.decode_count_bit < + subghz_protocol_star_line_const.min_count_bit_for_found) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } else { + instance->decoder.decode_count_bit++; + } + instance->decoder.parser_step = StarLineDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = StarLineDecoderStepReset; + } + } else { + instance->decoder.parser_step = StarLineDecoderStepReset; + } + break; + } +} + +/** + * Validation of decrypt data. + * @param instance Pointer to a SubGhzBlockGeneric instance + * @param decrypt Decrypd data + * @param btn Button number, 4 bit + * @param end_serial decrement the last 10 bits of the serial number + * @return true On success + */ +static inline bool subghz_protocol_star_line_check_decrypt( + SubGhzBlockGeneric* instance, + uint32_t decrypt, + uint8_t btn, + uint32_t end_serial) { + furi_assert(instance); + if((decrypt >> 24 == btn) && ((((uint16_t)(decrypt >> 16)) & 0x00FF) == end_serial)) { + instance->cnt = decrypt & 0x0000FFFF; + return true; + } + return false; +} + +/** + * Checking the accepted code against the database manafacture key + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param fix Fix part of the parcel + * @param hop Hop encrypted part of the parcel + * @param keystore Pointer to a SubGhzKeystore* instance + * @param manufacture_name + * @return true on successful search + */ +static uint8_t subghz_protocol_star_line_check_remote_controller_selector( + SubGhzBlockGeneric* instance, + uint32_t fix, + uint32_t hop, + SubGhzKeystore* keystore, + const char** manufacture_name) { + uint16_t end_serial = (uint16_t)(fix & 0xFF); + uint8_t btn = (uint8_t)(fix >> 24); + uint32_t decrypt = 0; + uint64_t man_normal_learning; + bool mf_not_set = false; + // TODO: + // if(mfname == 0x0) { + // mfname = ""; + // } + + const char* mfname = keystore->mfname; + + if(strcmp(mfname, "Unknown") == 0) { + return 1; + } else if(strcmp(mfname, "") == 0) { + mf_not_set = true; + } + for + M_EACH(manufacture_code, *subghz_keystore_get_data(keystore), SubGhzKeyArray_t) { + if(mf_not_set || (strcmp(furi_string_get_cstr(manufacture_code->name), mfname) == 0)) { + switch(manufacture_code->type) { + case KEELOQ_LEARNING_SIMPLE: + // Simple Learning + decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); + if(subghz_protocol_star_line_check_decrypt( + instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + keystore->mfname = *manufacture_name; + return 1; + } + break; + case KEELOQ_LEARNING_NORMAL: + // Normal Learning + // https://phreakerclub.com/forum/showpost.php?p=43557&postcount=37 + man_normal_learning = + subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_normal_learning); + if(subghz_protocol_star_line_check_decrypt( + instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + keystore->mfname = *manufacture_name; + return 1; + } + break; + case KEELOQ_LEARNING_UNKNOWN: + // Simple Learning + decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); + if(subghz_protocol_star_line_check_decrypt( + instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + keystore->mfname = *manufacture_name; + keystore->kl_type = 1; + return 1; + } + // Check for mirrored man + uint64_t man_rev = 0; + uint64_t man_rev_byte = 0; + for(uint8_t i = 0; i < 64; i += 8) { + man_rev_byte = (uint8_t)(manufacture_code->key >> i); + man_rev = man_rev | man_rev_byte << (56 - i); + } + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_rev); + if(subghz_protocol_star_line_check_decrypt( + instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + keystore->mfname = *manufacture_name; + keystore->kl_type = 1; + return 1; + } + //########################### + // Normal Learning + // https://phreakerclub.com/forum/showpost.php?p=43557&postcount=37 + man_normal_learning = + subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_normal_learning); + if(subghz_protocol_star_line_check_decrypt( + instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + keystore->mfname = *manufacture_name; + keystore->kl_type = 2; + return 1; + } + // Check for mirrored man + man_normal_learning = + subghz_protocol_keeloq_common_normal_learning(fix, man_rev); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_normal_learning); + if(subghz_protocol_star_line_check_decrypt( + instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + keystore->mfname = *manufacture_name; + keystore->kl_type = 2; + return 1; + } + break; + } + } + } + + *manufacture_name = "Unknown"; + keystore->mfname = "Unknown"; + instance->cnt = 0; + + return 0; +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param keystore Pointer to a SubGhzKeystore* instance + * @param manufacture_name + */ +static void subghz_protocol_star_line_check_remote_controller( + SubGhzBlockGeneric* instance, + SubGhzKeystore* keystore, + const char** manufacture_name) { + uint64_t key = subghz_protocol_blocks_reverse_key(instance->data, instance->data_count_bit); + uint32_t key_fix = key >> 32; + uint32_t key_hop = key & 0x00000000ffffffff; + + subghz_protocol_star_line_check_remote_controller_selector( + instance, key_fix, key_hop, keystore, manufacture_name); + + instance->serial = key_fix & 0x00FFFFFF; + instance->btn = key_fix >> 24; +} + +uint8_t subghz_protocol_decoder_star_line_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderStarLine* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus subghz_protocol_decoder_star_line_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderStarLine* instance = context; + subghz_protocol_star_line_check_remote_controller( + &instance->generic, instance->keystore, &instance->manufacture_name); + + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + + do { + if(preset != NULL) { + if(!flipper_format_insert_or_update_uint32( + flipper_format, "Frequency", &preset->frequency, 1)) { + break; + } + + const char* preset_name = furi_string_get_cstr(preset->name); + const char* short_preset = protopirate_get_short_preset_name(preset_name); + if(!flipper_format_insert_or_update_string_cstr( + flipper_format, "Preset", short_preset)) { + break; + } + } + + if(!flipper_format_insert_or_update_string_cstr( + flipper_format, "Protocol", instance->generic.protocol_name)) { + break; + } + + uint32_t bits = instance->generic.data_count_bit; + if(!flipper_format_insert_or_update_uint32(flipper_format, "Bit", &bits, 1)) { + break; + } + + char key_str[20]; + snprintf(key_str, sizeof(key_str), "%016llX", instance->generic.data); + if(!flipper_format_insert_or_update_string_cstr(flipper_format, "Key", key_str)) { + break; + } + + if(!flipper_format_insert_or_update_uint32( + flipper_format, "Serial", &instance->generic.serial, 1)) { + break; + } + + uint32_t temp = instance->generic.btn; + if(!flipper_format_insert_or_update_uint32(flipper_format, "Btn", &temp, 1)) { + break; + } + + if(!flipper_format_insert_or_update_uint32( + flipper_format, "Cnt", &instance->generic.cnt, 1)) { + break; + } + + if(!flipper_format_insert_or_update_string_cstr( + flipper_format, "Manufacture", instance->manufacture_name)) { + break; + } + + ret = SubGhzProtocolStatusOk; + } while(false); + + return ret; + //SubGhzProtocolStatus ret = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + subghz_protocol_decoder_star_line_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderStarLine* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + + flipper_format_rewind(flipper_format); + + do { + FuriString* temp_str = furi_string_alloc(); + if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) { + FURI_LOG_E(TAG, "Missing Protocol"); + furi_string_free(temp_str); + break; + } + + if(!furi_string_equal(temp_str, instance->base.protocol->name)) { + FURI_LOG_E( + TAG, + "Wrong protocol %s != %s", + furi_string_get_cstr(temp_str), + instance->base.protocol->name); + furi_string_free(temp_str); + break; + } + furi_string_free(temp_str); + + uint32_t bit_count_temp; + if(!flipper_format_read_uint32(flipper_format, "Bit", &bit_count_temp, 1)) { + FURI_LOG_E(TAG, "Missing Bit"); + break; + } + + instance->generic.data_count_bit = subghz_protocol_star_line_const.min_count_bit_for_found; + + temp_str = furi_string_alloc(); + if(!flipper_format_read_string(flipper_format, "Key", temp_str)) { + FURI_LOG_E(TAG, "Missing Key"); + furi_string_free(temp_str); + 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 = c - '0'; + } else if(c >= 'A' && c <= 'F') { + nibble = c - 'A' + 10; + } else if(c >= 'a' && c <= 'f') { + nibble = c - 'a' + 10; + } else { + FURI_LOG_E(TAG, "Invalid hex character: %c", c); + furi_string_free(temp_str); + break; + } + + key = (key << 4) | nibble; + hex_pos++; + } + + furi_string_free(temp_str); + + if(hex_pos != 16) { + FURI_LOG_E(TAG, "Invalid key length: %zu nibbles (expected 16)", hex_pos); + break; + } + + instance->generic.data = key; + FURI_LOG_I(TAG, "Parsed key: 0x%016llX", instance->generic.data); + + if(instance->generic.data == 0) { + FURI_LOG_E(TAG, "Key is zero after parsing!"); + break; + } + + if(!flipper_format_read_uint32(flipper_format, "Serial", &instance->generic.serial, 1)) { + instance->generic.serial = instance->generic.data >> 24; + FURI_LOG_I(TAG, "Extracted serial: 0x%08lX", instance->generic.serial); + } else { + FURI_LOG_I(TAG, "Read serial: 0x%08lX", instance->generic.serial); + } + + uint32_t btn_temp; + if(flipper_format_read_uint32(flipper_format, "Btn", &btn_temp, 1)) { + instance->generic.btn = (uint8_t)btn_temp; + FURI_LOG_I(TAG, "Read button: 0x%02X", instance->generic.btn); + } else { + instance->generic.btn = (instance->generic.data >> 16) & 0xFF; + FURI_LOG_I(TAG, "Extracted button: 0x%02X", instance->generic.btn); + } + + uint32_t cnt_temp; + if(flipper_format_read_uint32(flipper_format, "Cnt", &cnt_temp, 1)) { + instance->generic.cnt = (uint16_t)cnt_temp; + FURI_LOG_I(TAG, "Read counter: 0x%03lX", (unsigned long)instance->generic.cnt); + } + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + // Read manufacturer from file + if(flipper_format_read_string( + flipper_format, "Manufacture", instance->manufacture_from_file)) { + instance->manufacture_name = furi_string_get_cstr(instance->manufacture_from_file); + instance->keystore->mfname = instance->manufacture_name; + } else { + FURI_LOG_D(TAG, "DECODER: Missing Manufacture"); + } + + FURI_LOG_I(TAG, "Decoder deserialized"); + + ret = SubGhzProtocolStatusOk; + } while(false); + + return ret; +} + +void subghz_protocol_decoder_star_line_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderStarLine* instance = context; + + subghz_protocol_star_line_check_remote_controller( + &instance->generic, instance->keystore, &instance->manufacture_name); + + uint32_t code_found_hi = instance->generic.data >> 32; + uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + uint32_t code_found_reverse_hi = code_found_reverse >> 32; + uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%08lX%08lX\r\n" + "Fix:0x%08lX Cnt:%04lX\r\n" + "Hop:0x%08lX Btn:%02X\r\n" + "MF:%s\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_hi, + code_found_lo, + code_found_reverse_hi, + instance->generic.cnt, + code_found_reverse_lo, + instance->generic.btn, + instance->manufacture_name); +} diff --git a/protocols/star_line.h b/protocols/star_line.h new file mode 100644 index 0000000..ccd25a1 --- /dev/null +++ b/protocols/star_line.h @@ -0,0 +1,117 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SUBGHZ_PROTOCOL_STAR_LINE_NAME "Star Line" + +typedef struct SubGhzProtocolDecoderStarLine SubGhzProtocolDecoderStarLine; +typedef struct SubGhzProtocolEncoderStarLine SubGhzProtocolEncoderStarLine; + +extern const SubGhzProtocolDecoder subghz_protocol_star_line_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_star_line_encoder; +extern const SubGhzProtocol subghz_protocol_star_line; + +/** + * Allocate SubGhzProtocolEncoderStarLine. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderStarLine* pointer to a SubGhzProtocolEncoderStarLine instance + */ +void* subghz_protocol_encoder_star_line_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderStarLine. + * @param context Pointer to a SubGhzProtocolEncoderStarLine instance + */ +void subghz_protocol_encoder_star_line_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderStarLine instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +SubGhzProtocolStatus + subghz_protocol_encoder_star_line_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderStarLine instance + */ +void subghz_protocol_encoder_star_line_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderStarLine instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_star_line_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderStarLine. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderStarLine* pointer to a SubGhzProtocolDecoderStarLine instance + */ +void* subghz_protocol_decoder_star_line_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderStarLine. + * @param context Pointer to a SubGhzProtocolDecoderStarLine instance + */ +void subghz_protocol_decoder_star_line_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderStarLine. + * @param context Pointer to a SubGhzProtocolDecoderStarLine instance + */ +void subghz_protocol_decoder_star_line_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderStarLine instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_star_line_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderStarLine instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_star_line_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderStarLine. + * @param context Pointer to a SubGhzProtocolDecoderStarLine instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus subghz_protocol_decoder_star_line_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderStarLine. + * @param context Pointer to a SubGhzProtocolDecoderStarLine instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_decoder_star_line_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderStarLine instance + * @param output Resulting text + */ +void subghz_protocol_decoder_star_line_get_string(void* context, FuriString* output); diff --git a/protopirate_app.c b/protopirate_app.c index 4e345d7..68796a9 100644 --- a/protopirate_app.c +++ b/protopirate_app.c @@ -5,34 +5,31 @@ #include #include "protocols/protocol_items.h" #include "helpers/protopirate_settings.h" -#include "helpers/protopirate_storage.h" +#include "helpers/protopirate_storage.h" +#include "protocols/keys.h" #define TAG "ProtoPirateApp" -static bool protopirate_app_custom_event_callback(void *context, uint32_t event) -{ +static bool protopirate_app_custom_event_callback(void* context, uint32_t event) { furi_assert(context); - ProtoPirateApp *app = context; + ProtoPirateApp* app = context; return scene_manager_handle_custom_event(app->scene_manager, event); } -static bool protopirate_app_back_event_callback(void *context) -{ +static bool protopirate_app_back_event_callback(void* context) { furi_assert(context); - ProtoPirateApp *app = context; + ProtoPirateApp* app = context; return scene_manager_handle_back_event(app->scene_manager); } -static void protopirate_app_tick_event_callback(void *context) -{ +static void protopirate_app_tick_event_callback(void* context) { furi_assert(context); - ProtoPirateApp *app = context; + ProtoPirateApp* app = context; scene_manager_handle_tick_event(app->scene_manager); } -ProtoPirateApp *protopirate_app_alloc() -{ - ProtoPirateApp *app = malloc(sizeof(ProtoPirateApp)); +ProtoPirateApp* protopirate_app_alloc() { + ProtoPirateApp* app = malloc(sizeof(ProtoPirateApp)); FURI_LOG_I(TAG, "Allocating ProtoPirate Decoder App"); @@ -99,7 +96,7 @@ ProtoPirateApp *protopirate_app_alloc() // Load saved settings ProtoPirateSettings settings; protopirate_settings_load(&settings); - + // Apply auto-save setting app->auto_save = settings.auto_save; @@ -114,7 +111,7 @@ ProtoPirateApp *protopirate_app_alloc() // Apply loaded frequency and preset, with validation uint32_t frequency = settings.frequency; uint8_t preset_index = settings.preset_index; - + // Validate frequency - check if it exists in settings bool frequency_valid = false; for(size_t i = 0; i < subghz_setting_get_frequency_count(app->setting); i++) { @@ -127,26 +124,31 @@ ProtoPirateApp *protopirate_app_alloc() frequency = subghz_setting_get_default_frequency(app->setting); FURI_LOG_W(TAG, "Saved frequency invalid, using default: %lu", frequency); } - + // Validate preset index if(preset_index >= subghz_setting_get_preset_count(app->setting)) { preset_index = 0; FURI_LOG_W(TAG, "Saved preset index invalid, using default"); } - + // Get preset name and data const char* preset_name = subghz_setting_get_preset_name(app->setting, preset_index); uint8_t* preset_data = subghz_setting_get_preset_data(app->setting, preset_index); size_t preset_data_size = subghz_setting_get_preset_data_size(app->setting, preset_index); - - FURI_LOG_I(TAG, "Applying settings: freq=%lu, preset=%s, auto_save=%d, hopping=%d", - frequency, preset_name, settings.auto_save, settings.hopping_enabled); - + + FURI_LOG_I( + TAG, + "Applying settings: freq=%lu, preset=%s, auto_save=%d, hopping=%d", + frequency, + preset_name, + settings.auto_save, + settings.hopping_enabled); + protopirate_preset_init(app, preset_name, frequency, preset_data, preset_data_size); // Apply hopping state from settings - app->txrx->hopper_state = settings.hopping_enabled ? - ProtoPirateHopperStateRunning : ProtoPirateHopperStateOFF; + app->txrx->hopper_state = settings.hopping_enabled ? ProtoPirateHopperStateRunning : + ProtoPirateHopperStateOFF; app->txrx->hopper_idx_frequency = 0; app->txrx->hopper_timeout = 0; app->txrx->idx_menu_chosen = 0; @@ -157,9 +159,17 @@ ProtoPirateApp *protopirate_app_alloc() // Create environment with our custom protocols app->txrx->environment = subghz_environment_alloc(); + // Load keystores + subghz_environment_load_keystore(app->txrx->environment, PROTOPIRATE_KEYSTORE_DIR_NAME); + subghz_environment_load_keystore(app->txrx->environment, SUBGHZ_KEYSTORE_DIR_USER_NAME); + FURI_LOG_I(TAG, "Registering %zu ProtoPirate protocols", protopirate_protocol_registry.size); subghz_environment_set_protocol_registry( - app->txrx->environment, (void *)&protopirate_protocol_registry); + app->txrx->environment, (void*)&protopirate_protocol_registry); + + // Load ProtoPirate specific keys + protopirate_keys_load(app->txrx->environment); + FURI_LOG_I(TAG, "Loaded ProtoPirate secure keys"); // Create receiver app->txrx->receiver = subghz_receiver_alloc_init(app->txrx->environment); @@ -168,15 +178,16 @@ ProtoPirateApp *protopirate_app_alloc() subghz_devices_init(); // Try external CC1101 first, fallback to internal - app->txrx->radio_device = - radio_device_loader_set(NULL, SubGhzRadioDeviceTypeExternalCC1101); + app->txrx->radio_device = radio_device_loader_set(NULL, SubGhzRadioDeviceTypeExternalCC1101); if(!app->txrx->radio_device) { FURI_LOG_E(TAG, "Failed to initialize any radio device!"); } else { const char* device_name = subghz_devices_get_name(app->txrx->radio_device); bool is_external = device_name && strstr(device_name, "ext"); - FURI_LOG_I(TAG, "Radio device initialized: %s (%s)", + FURI_LOG_I( + TAG, + "Radio device initialized: %s (%s)", device_name ? device_name : "unknown", is_external ? "external" : "internal"); } @@ -201,21 +212,20 @@ ProtoPirateApp *protopirate_app_alloc() return app; } -void protopirate_app_free(ProtoPirateApp *app) -{ +void protopirate_app_free(ProtoPirateApp* app) { furi_assert(app); FURI_LOG_I(TAG, "Freeing ProtoPirate Decoder App"); // Free the storage file list cache - protopirate_storage_free_file_list(); + protopirate_storage_free_file_list(); // Save settings before exiting ProtoPirateSettings settings; settings.frequency = app->txrx->preset->frequency; settings.auto_save = app->auto_save; settings.hopping_enabled = (app->txrx->hopper_state != ProtoPirateHopperStateOFF); - + // Find current preset index settings.preset_index = 0; const char* current_preset = furi_string_get_cstr(app->txrx->preset->name); @@ -225,21 +235,24 @@ void protopirate_app_free(ProtoPirateApp *app) break; } } - - FURI_LOG_I(TAG, "Saving settings: freq=%lu, preset=%u, auto_save=%d, hopping=%d", - settings.frequency, settings.preset_index, settings.auto_save, settings.hopping_enabled); - + + FURI_LOG_I( + TAG, + "Saving settings: freq=%lu, preset=%u, auto_save=%d, hopping=%d", + settings.frequency, + settings.preset_index, + settings.auto_save, + settings.hopping_enabled); + protopirate_settings_save(&settings); // Make sure we're not receiving - if (app->txrx->txrx_state == ProtoPirateTxRxStateRx) - { + if(app->txrx->txrx_state == ProtoPirateTxRxStateRx) { subghz_worker_stop(app->txrx->worker); subghz_devices_stop_async_rx(app->txrx->radio_device); } - if (app->loaded_file_path) - { + if(app->loaded_file_path) { furi_string_free(app->loaded_file_path); } @@ -300,14 +313,13 @@ void protopirate_app_free(ProtoPirateApp *app) free(app); } -int32_t protopirate_app(void *p) -{ +int32_t protopirate_app(void* p) { UNUSED(p); - ProtoPirateApp *protopirate_app = protopirate_app_alloc(); + ProtoPirateApp* protopirate_app = protopirate_app_alloc(); view_dispatcher_run(protopirate_app->view_dispatcher); protopirate_app_free(protopirate_app); return 0; -} \ No newline at end of file +}