From d03567f09f7aa4e11e93f08f83c1d96d0f9559cf Mon Sep 17 00:00:00 2001 From: 0mega <171701900+zero-mega@users.noreply.github.com> Date: Wed, 4 Mar 2026 17:24:42 +0100 Subject: [PATCH] Add Kia V6 Encoder --- helpers/protopirate_storage.c | 4 + protocols/kia_v6.c | 372 +++++++++++++++++++++++ protocols/kia_v6.h | 9 + scenes/protopirate_scene_receiver_info.c | 1 - scenes/protopirate_scene_saved_info.c | 2 - 5 files changed, 385 insertions(+), 3 deletions(-) diff --git a/helpers/protopirate_storage.c b/helpers/protopirate_storage.c index 71fbbfb..71f4db7 100644 --- a/helpers/protopirate_storage.c +++ b/helpers/protopirate_storage.c @@ -212,6 +212,10 @@ static bool protopirate_storage_write_capture_data( /* Key_2 */ PROTOPIRATE_COPY_STRING_OPTIONAL("Key_2"); + PROTOPIRATE_COPY_U32_OPTIONAL("Key_2"); + PROTOPIRATE_COPY_U32_OPTIONAL("Key_3"); + PROTOPIRATE_COPY_U32_OPTIONAL("Key_4"); + PROTOPIRATE_COPY_U32_OPTIONAL("Fx"); /* Key1 */ uint8_t key1_buf[8]; diff --git a/protocols/kia_v6.c b/protocols/kia_v6.c index d28d30c..b71020d 100644 --- a/protocols/kia_v6.c +++ b/protocols/kia_v6.c @@ -86,6 +86,12 @@ struct SubGhzProtocolEncoderKiaV6 { SubGhzProtocolEncoderBase base; SubGhzProtocolBlockEncoder encoder; SubGhzBlockGeneric generic; + uint32_t stored_part1_low; + uint32_t stored_part1_high; + uint32_t stored_part2_low; + uint32_t stored_part2_high; + uint16_t data_part3; + uint8_t fx_field; }; typedef enum { @@ -107,6 +113,15 @@ const SubGhzProtocolDecoder kia_protocol_v6_decoder = { .get_string = kia_protocol_decoder_v6_get_string, }; +#ifdef ENABLE_EMULATE_FEATURE +const SubGhzProtocolEncoder kia_protocol_v6_encoder = { + .alloc = kia_protocol_encoder_v6_alloc, + .free = kia_protocol_encoder_v6_free, + .deserialize = kia_protocol_encoder_v6_deserialize, + .stop = kia_protocol_encoder_v6_stop, + .yield = kia_protocol_encoder_v6_yield, +}; +#else const SubGhzProtocolEncoder kia_protocol_v6_encoder = { .alloc = NULL, .free = NULL, @@ -114,6 +129,7 @@ const SubGhzProtocolEncoder kia_protocol_v6_encoder = { .stop = NULL, .yield = NULL, }; +#endif const SubGhzProtocol kia_protocol_v6 = { .name = KIA_PROTOCOL_V6_NAME, @@ -213,6 +229,64 @@ static void aes_addroundkey(uint8_t* state, const uint8_t* round_key) { } } +static void aes_subbytes(uint8_t* state) { + for(int row = 0; row < 4; row++) { + for(int col = 0; col < 4; col++) { + state[row + col * 4] = aes_sbox[state[row + col * 4]]; + } + } +} + +static void aes_shiftrows(uint8_t* state) { + uint8_t temp; + temp = state[1]; + state[1] = state[5]; + state[5] = state[9]; + state[9] = state[13]; + state[13] = temp; + temp = state[2]; + state[2] = state[10]; + state[10] = temp; + temp = state[6]; + state[6] = state[14]; + state[14] = temp; + temp = state[3]; + state[3] = state[15]; + state[15] = state[11]; + state[11] = state[7]; + state[7] = temp; +} + +static void aes_mixcolumns(uint8_t* state) { + uint8_t a, b, c, d; + for(int i = 0; i < 4; i++) { + a = state[i * 4]; + b = state[i * 4 + 1]; + c = state[i * 4 + 2]; + d = state[i * 4 + 3]; + state[i * 4] = gf_mul2(a) ^ gf_mul2(b) ^ b ^ c ^ d; + state[i * 4 + 1] = a ^ gf_mul2(b) ^ gf_mul2(c) ^ c ^ d; + state[i * 4 + 2] = a ^ b ^ gf_mul2(c) ^ gf_mul2(d) ^ d; + state[i * 4 + 3] = gf_mul2(a) ^ a ^ b ^ c ^ gf_mul2(d); + } +} + +static void aes128_encrypt(const uint8_t* expanded_key, uint8_t* data) { + uint8_t state[16]; + memcpy(state, data, 16); + aes_addroundkey(state, &expanded_key[0]); + for(int round = 1; round < 10; round++) { + aes_subbytes(state); + aes_shiftrows(state); + aes_mixcolumns(state); + aes_addroundkey(state, &expanded_key[round * 16]); + } + aes_subbytes(state); + aes_shiftrows(state); + aes_addroundkey(state, &expanded_key[160]); + memcpy(data, state, 16); +} + static void aes_key_expansion(const uint8_t* key, uint8_t* round_keys) { for(int i = 0; i < 16; i++) { round_keys[i] = key[i]; @@ -296,6 +370,49 @@ static void get_kia_v6_aes_key(uint8_t* aes_key) { } } +static void kia_v6_encrypt_payload( + uint8_t fx_field, + uint32_t serial, + uint8_t btn, + uint32_t cnt, + uint32_t* out_part1_low, + uint32_t* out_part1_high, + uint32_t* out_part2_low, + uint32_t* out_part2_high, + uint16_t* out_part3) { + uint8_t plain[16]; + memset(plain, 0, 16); + plain[0] = fx_field; + plain[4] = (serial >> 16) & 0xFF; + plain[5] = (serial >> 8) & 0xFF; + plain[6] = serial & 0xFF; + plain[7] = btn & 0x0F; + plain[8] = (cnt >> 24) & 0xFF; + plain[9] = (cnt >> 16) & 0xFF; + plain[10] = (cnt >> 8) & 0xFF; + plain[11] = cnt & 0xFF; + plain[12] = aes_sbox[cnt & 0xFF]; + plain[15] = kia_v6_crc8(plain, 15, 0xFF, 0x07); + + uint8_t aes_key[16]; + get_kia_v6_aes_key(aes_key); + uint8_t expanded_key[176]; + aes_key_expansion(aes_key, expanded_key); + aes128_encrypt(expanded_key, plain); + + uint8_t fx_hi = 0x20 | (fx_field >> 4); + uint8_t fx_lo = fx_field & 0x0F; + *out_part1_high = ((uint32_t)fx_hi << 24) | ((uint32_t)fx_lo << 16) | + ((uint32_t)plain[0] << 8) | plain[1]; + *out_part1_low = ((uint32_t)plain[2] << 24) | ((uint32_t)plain[3] << 16) | + ((uint32_t)plain[4] << 8) | plain[5]; + *out_part2_high = ((uint32_t)plain[6] << 24) | ((uint32_t)plain[7] << 16) | + ((uint32_t)plain[8] << 8) | plain[9]; + *out_part2_low = ((uint32_t)plain[10] << 24) | ((uint32_t)plain[11] << 16) | + ((uint32_t)plain[12] << 8) | plain[13]; + *out_part3 = ((uint16_t)plain[14] << 8) | plain[15]; +} + static bool kia_v6_decrypt(SubGhzProtocolDecoderKiaV6* instance) { uint8_t encrypted_data[16]; @@ -610,6 +727,9 @@ SubGhzProtocolStatus kia_protocol_decoder_v6_serialize( uint32_t key3 = instance->data_part3; if(!flipper_format_write_uint32(flipper_format, "Key_4", &key3, 1)) break; + uint32_t fx = instance->fx_field; + if(!flipper_format_write_uint32(flipper_format, "Fx", &fx, 1)) break; + ret = SubGhzProtocolStatusOk; } while(false); @@ -683,6 +803,9 @@ SubGhzProtocolStatus if(flipper_format_read_uint32(flipper_format, "Cnt", &temp, 1)) { instance->generic.cnt = (uint16_t)temp; } + if(flipper_format_read_uint32(flipper_format, "Fx", &temp, 1)) { + instance->fx_field = (uint8_t)temp; + } furi_string_free(temp_str); ret = SubGhzProtocolStatusOk; @@ -733,3 +856,252 @@ void kia_protocol_decoder_v6_get_string(void* context, FuriString* output) { instance->generic.cnt, instance->crc2_field); } + +#ifdef ENABLE_EMULATE_FEATURE + +#define KIA_V6_PREAMBLE_PAIRS_1 640 +#define KIA_V6_PREAMBLE_PAIRS_2 38 +#define KIA_V6_TE_SHORT 200 +#define KIA_V6_TE_LONG 400 +#define KIA_V6_UPLOAD_SIZE 1928 + +static inline void kia_v6_encode_manchester_bit( + LevelDuration* upload, + size_t* index, + bool bit, + uint32_t te) { + if(bit) { + upload[(*index)++] = level_duration_make(false, te); + upload[(*index)++] = level_duration_make(true, te); + } else { + upload[(*index)++] = level_duration_make(true, te); + upload[(*index)++] = level_duration_make(false, te); + } +} + +static void kia_v6_encode_message( + LevelDuration* upload, + size_t* index, + int preamble_pairs, + uint32_t p1_lo, + uint32_t p1_hi, + uint32_t p2_lo, + uint32_t p2_hi, + uint16_t p3) { + const uint32_t te_short = KIA_V6_TE_SHORT; + const uint32_t te_long = KIA_V6_TE_LONG; + + for(int i = 0; i < preamble_pairs; i++) { + upload[(*index)++] = level_duration_make(true, te_short); + upload[(*index)++] = level_duration_make(false, te_short); + } + + upload[(*index)++] = level_duration_make(false, te_short); + upload[(*index)++] = level_duration_make(true, te_long); + upload[(*index)++] = level_duration_make(false, te_short); + + for(int b = 60; b >= 0; b--) { + uint32_t word = (b >= 32) ? p1_hi : p1_lo; + int shift = (b >= 32) ? (b - 32) : b; + kia_v6_encode_manchester_bit(upload, index, ((~word) >> shift) & 1, te_short); + } + + for(int b = 63; b >= 0; b--) { + uint32_t word = (b >= 32) ? p2_hi : p2_lo; + int shift = (b >= 32) ? (b - 32) : b; + kia_v6_encode_manchester_bit(upload, index, ((~word) >> shift) & 1, te_short); + } + + for(int b = 15; b >= 0; b--) { + kia_v6_encode_manchester_bit(upload, index, ((~p3) >> b) & 1, te_short); + } +} + +static void kia_protocol_encoder_v6_build_upload(SubGhzProtocolEncoderKiaV6* instance) { + furi_check(instance); + + kia_v6_encrypt_payload( + instance->fx_field, + instance->generic.serial, + instance->generic.btn & 0x0F, + instance->generic.cnt, + &instance->stored_part1_low, + &instance->stored_part1_high, + &instance->stored_part2_low, + &instance->stored_part2_high, + &instance->data_part3); + + uint32_t p1_lo = instance->stored_part1_low; + uint32_t p1_hi = instance->stored_part1_high; + uint32_t p2_lo = instance->stored_part2_low; + uint32_t p2_hi = instance->stored_part2_high; + uint16_t p3 = instance->data_part3; + + size_t index = 0; + + kia_v6_encode_message( + instance->encoder.upload, &index, KIA_V6_PREAMBLE_PAIRS_1, + p1_lo, p1_hi, p2_lo, p2_hi, p3); + + instance->encoder.upload[index++] = level_duration_make(false, KIA_V6_TE_LONG); + + kia_v6_encode_message( + instance->encoder.upload, &index, KIA_V6_PREAMBLE_PAIRS_2, + p1_lo, p1_hi, p2_lo, p2_hi, p3); + + instance->encoder.upload[index++] = level_duration_make(false, KIA_V6_TE_LONG); + + instance->encoder.size_upload = index; + +#ifndef REMOVE_LOGS + FURI_LOG_I( + TAG, + "Encrypted payload (TX): P1=%08lX%08lX P2=%08lX%08lX P3=%04X", + (unsigned long)p1_hi, + (unsigned long)p1_lo, + (unsigned long)p2_hi, + (unsigned long)p2_lo, + (unsigned int)p3); + FURI_LOG_I( + TAG, + "Encoder input: Ser=0x%06lX Btn=%u Cnt=0x%08lX -> upload %u entries, repeat %ld", + (unsigned long)instance->generic.serial, + (unsigned int)(instance->generic.btn & 0x0F), + (unsigned long)instance->generic.cnt, + (unsigned)index, + (long)instance->encoder.repeat); +#endif + instance->encoder.front = 0; +} + +void* kia_protocol_encoder_v6_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderKiaV6* instance = malloc(sizeof(SubGhzProtocolEncoderKiaV6)); + if(!instance) return NULL; + memset(instance, 0, sizeof(SubGhzProtocolEncoderKiaV6)); + instance->base.protocol = &kia_protocol_v6; + instance->generic.protocol_name = instance->base.protocol->name; + instance->encoder.size_upload = 2000; + instance->encoder.upload = + malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + if(!instance->encoder.upload) { + free(instance); + return NULL; + } + instance->encoder.repeat = 10; + instance->encoder.front = 0; + instance->encoder.is_running = false; + return instance; +} + +void kia_protocol_encoder_v6_free(void* context) { + furi_check(context); + SubGhzProtocolEncoderKiaV6* instance = context; + if(instance->encoder.upload) { + free(instance->encoder.upload); + } + free(instance); +} + +SubGhzProtocolStatus + kia_protocol_encoder_v6_deserialize(void* context, FlipperFormat* flipper_format) { + furi_check(context); + SubGhzProtocolEncoderKiaV6* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + FuriString* temp_str = furi_string_alloc(); + + instance->encoder.is_running = false; + instance->encoder.front = 0; + instance->encoder.repeat = 10; + instance->generic.data_count_bit = kia_protocol_v6_const.min_count_bit_for_found; + + flipper_format_rewind(flipper_format); + + 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; + } + + flipper_format_rewind(flipper_format); + if(!flipper_format_read_uint32(flipper_format, "Serial", &instance->generic.serial, 1)) { + FURI_LOG_E(TAG, "Missing Serial"); + break; + } + flipper_format_rewind(flipper_format); + uint32_t btn_temp; + if(!flipper_format_read_uint32(flipper_format, "Btn", &btn_temp, 1)) { + FURI_LOG_E(TAG, "Missing Btn"); + break; + } + instance->generic.btn = (uint8_t)(btn_temp & 0x0F); + flipper_format_rewind(flipper_format); + if(!flipper_format_read_uint32(flipper_format, "Cnt", &instance->generic.cnt, 1)) { + FURI_LOG_E(TAG, "Missing Cnt"); + break; + } + + flipper_format_rewind(flipper_format); + uint32_t fx_temp = 0; + if(flipper_format_read_uint32(flipper_format, "Fx", &fx_temp, 1)) { + instance->fx_field = (uint8_t)(fx_temp & 0xFF); + } else { + FURI_LOG_W(TAG, "Missing Fx field, defaulting to 0"); + instance->fx_field = 0; + } + + flipper_format_rewind(flipper_format); + uint32_t repeat_val; + if(flipper_format_read_uint32(flipper_format, "Repeat", &repeat_val, 1)) { + instance->encoder.repeat = (int32_t)(repeat_val & 0x7FFFFFFF); + } + +#ifndef REMOVE_LOGS + FURI_LOG_I( + TAG, + "Encoder deserialize read: Fx=0x%02X Serial=0x%06lX Btn=%u Cnt=0x%08lX Repeat=%ld", + (unsigned int)instance->fx_field, + (unsigned long)instance->generic.serial, + (unsigned int)(instance->generic.btn & 0x0F), + (unsigned long)instance->generic.cnt, + (long)instance->encoder.repeat); +#endif + + kia_protocol_encoder_v6_build_upload(instance); + instance->encoder.is_running = true; + ret = SubGhzProtocolStatusOk; + } while(false); + + furi_string_free(temp_str); + return ret; +} + +void kia_protocol_encoder_v6_stop(void* context) { + furi_check(context); + SubGhzProtocolEncoderKiaV6* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration kia_protocol_encoder_v6_yield(void* context) { + furi_check(context); + SubGhzProtocolEncoderKiaV6* 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]; + instance->encoder.front++; + if(instance->encoder.front >= instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + return ret; +} + +#endif diff --git a/protocols/kia_v6.h b/protocols/kia_v6.h index e7c7406..9ca5160 100644 --- a/protocols/kia_v6.h +++ b/protocols/kia_v6.h @@ -36,3 +36,12 @@ SubGhzProtocolStatus kia_protocol_decoder_v6_serialize( SubGhzProtocolStatus kia_protocol_decoder_v6_deserialize(void* context, FlipperFormat* flipper_format); void kia_protocol_decoder_v6_get_string(void* context, FuriString* output); + +#ifdef ENABLE_EMULATE_FEATURE +void* kia_protocol_encoder_v6_alloc(SubGhzEnvironment* environment); +void kia_protocol_encoder_v6_free(void* context); +SubGhzProtocolStatus + kia_protocol_encoder_v6_deserialize(void* context, FlipperFormat* flipper_format); +void kia_protocol_encoder_v6_stop(void* context); +LevelDuration kia_protocol_encoder_v6_yield(void* context); +#endif diff --git a/scenes/protopirate_scene_receiver_info.c b/scenes/protopirate_scene_receiver_info.c index 7350fd1..cbda6f0 100644 --- a/scenes/protopirate_scene_receiver_info.c +++ b/scenes/protopirate_scene_receiver_info.c @@ -93,7 +93,6 @@ static void protopirate_receiver_info_build_normal_widget(ProtoPirateApp* app) { if(furi_string_cmp_str(protocol, "PSA") == 0) is_psa = true; if(furi_string_cmp_str(protocol, "Scher-Khan") == 0) is_emu_off = true; else if(furi_string_cmp_str(protocol, "Kia V5") == 0) is_emu_off = true; - else if(furi_string_cmp_str(protocol, "Kia V6") == 0) is_emu_off = true; else is_emu_off = false; } furi_string_free(protocol); diff --git a/scenes/protopirate_scene_saved_info.c b/scenes/protopirate_scene_saved_info.c index 6097737..cb242f8 100644 --- a/scenes/protopirate_scene_saved_info.c +++ b/scenes/protopirate_scene_saved_info.c @@ -119,8 +119,6 @@ void protopirate_scene_saved_info_on_enter(void* context) { is_emu_off = true; } else if(furi_string_cmp_str(temp_str, "Kia V5") == 0) { is_emu_off = true; - } else if(furi_string_cmp_str(temp_str, "Kia V6") == 0) { - is_emu_off = true; } flipper_format_rewind(ff);