diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt index 082a03d5f..1e0fbcca6 100644 --- a/firmware/application/CMakeLists.txt +++ b/firmware/application/CMakeLists.txt @@ -213,6 +213,7 @@ set(CPPSRC irq_controls.cpp irq_lcd_frame.cpp irq_rtc.cpp + keeloq_common.cpp keeloq_file.cpp keeloq_keystore.cpp log_file.cpp diff --git a/firmware/application/apps/ui_subghzd.cpp b/firmware/application/apps/ui_subghzd.cpp index 379b44762..e482eb77d 100644 --- a/firmware/application/apps/ui_subghzd.cpp +++ b/firmware/application/apps/ui_subghzd.cpp @@ -28,6 +28,7 @@ #include "ui_textentry.hpp" #include "../keeloq_keystore.hpp" #include "../keeloq_file.hpp" +#include "../keeloq_common.hpp" #include "portapack_persistent_memory.hpp" using namespace portapack; @@ -82,7 +83,7 @@ void SubGhzDRecentEntryDetailView::update_data() { KeeloqData params{ mf_name, serial, - cnt, + (uint16_t)cnt, btn}; ensure_directory(keeloq_remotes_dir); @@ -361,28 +362,6 @@ bool SubGhzDRecentEntryDetailView::keeloq_check_decrypt_centurion(uint32_t decry return false; } -uint32_t keeloq_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; -} - -uint64_t keeloq_normal_learning(uint32_t data, const uint64_t key) { - uint32_t k1, k2; - - data &= 0x0FFFFFFF; - data |= 0x20000000; - k1 = keeloq_decrypt(data, key); - - data &= 0x0FFFFFFF; - data |= 0x60000000; - k2 = keeloq_decrypt(data, key); - - return ((uint64_t)k2 << 32) | k1; -} - const uint32_t came_twee_magic_numbers_xor[15] = { 0x0E0E0E00, 0x1D1D1D11, diff --git a/firmware/application/apps/ui_subghzd.hpp b/firmware/application/apps/ui_subghzd.hpp index 77bf5e34d..cbf5f34e5 100644 --- a/firmware/application/apps/ui_subghzd.hpp +++ b/firmware/application/apps/ui_subghzd.hpp @@ -31,12 +31,6 @@ #define SD_NO_FIX 0xFFFFFFFF #define SD_NO_ENCRYPTED 0xFFFFFFFF -#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) - -#define KEELOQ_NLF 0x3A5C742E - #include "ui.hpp" #include "ui_navigation.hpp" #include "ui_receiver.hpp" diff --git a/firmware/application/external/external.cmake b/firmware/application/external/external.cmake index 1114727da..72283c215 100644 --- a/firmware/application/external/external.cmake +++ b/firmware/application/external/external.cmake @@ -293,6 +293,8 @@ set(EXTCPPSRC external/morseradiotx/main.cpp external/morseradiotx/ui_morse_radiotx.cpp + external/keeloqtx/main.cpp + external/keeloqtx/ui_keeloqtx.cpp #rtty_rx external/rtty_rx/main.cpp external/rtty_rx/ui_rtty_rx.cpp @@ -375,6 +377,7 @@ set(EXTAPPLIST siggen morse_radio morseradiotx + keeloqtx rtty_rx rtty_tx ) diff --git a/firmware/application/external/external.ld b/firmware/application/external/external.ld index b66b06d7d..42bfe0fa7 100644 --- a/firmware/application/external/external.ld +++ b/firmware/application/external/external.ld @@ -94,8 +94,9 @@ MEMORY ram_external_app_morse_radio (rwx) : org = 0xADF50000, len = 32k ram_external_app_waterfall_designer (rwx) : org = 0xADF60000, len = 32k ram_external_app_morseradiotx (rwx) : org = 0xADF70000, len = 32k - ram_external_app_rtty_rx (rwx) : org = 0xADF80000, len = 32k - ram_external_app_rtty_tx (rwx) : org = 0xADF90000, len = 32k + ram_external_app_keeloqtx (rwx) : org = 0xADF80000, len = 32k + ram_external_app_rtty_rx (rwx) : org = 0xADF90000, len = 32k + ram_external_app_rtty_tx (rwx) : org = 0xADFA0000, len = 32k } SECTIONS @@ -528,6 +529,11 @@ SECTIONS *(*ui*external_app*morseradiotx*); } > ram_external_app_morseradiotx + .external_app_keeloqtx : ALIGN(4) SUBALIGN(4) + { + KEEP(*(.external_app.app_keeloqtx.application_information)); + *(*ui*external_app*keeloqtx*); + } > ram_external_app_keeloqtx .external_app_rtty_rx : ALIGN(4) SUBALIGN(4) { KEEP(*(.external_app.app_rtty_rx.application_information)); @@ -539,8 +545,5 @@ SECTIONS KEEP(*(.external_app.app_rtty_tx.application_information)); *(*ui*external_app*rtty_tx*); } > ram_external_app_rtty_tx - - - } diff --git a/firmware/application/external/keeloqtx/main.cpp b/firmware/application/external/keeloqtx/main.cpp new file mode 100644 index 000000000..a7252e095 --- /dev/null +++ b/firmware/application/external/keeloqtx/main.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2026 lifegame1lu111 + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "ui.hpp" +#include "ui_navigation.hpp" +#include "ui_keeloqtx.hpp" +#include "external_app.hpp" + +namespace ui::external_app::ui_keeloqtx { +void initialize_app(ui::NavigationView& nav) { + nav.push(); +} +} // namespace ui::external_app::ui_keeloqtx + +extern "C" { + +__attribute__((section(".external_app.app_keeloqtx.application_information"), used)) application_information_t _application_information_keeloqtx = { + /*.memory_location = */ (uint8_t*)0x00000000, + /*.externalAppEntry = */ ui::external_app::ui_keeloqtx::initialize_app, + /*.header_version = */ CURRENT_HEADER_VERSION, + /*.app_version = */ VERSION_MD5, + /*.app_name = */ "KeeLoq TX", + /*.bitmap_data = */ { + 0x20, + 0x00, + 0x20, + 0x00, + 0x20, + 0x00, + 0x20, + 0x00, + 0xE0, + 0x07, + 0xF0, + 0x0F, + 0x30, + 0x0C, + 0x30, + 0x0C, + 0xF0, + 0x0F, + 0xF0, + 0x0F, + 0x70, + 0x0D, + 0xB0, + 0x0E, + 0x70, + 0x0D, + 0xB0, + 0x0E, + 0xF0, + 0x0F, + 0xE0, + 0x07, + }, + /*.icon_color = */ ui::Color::yellow().v, + /*.menu_location = */ app_location_t::TX, + /*.desired_menu_position = */ -1, + /*.m4_app_tag = portapack::spi_flash::image_tag_ook */ {'P', 'O', 'O', 'K'}, + /*.m4_app_offset = */ 0x00000000, // will be filled at compile time +}; +} diff --git a/firmware/application/external/keeloqtx/ui_keeloqtx.cpp b/firmware/application/external/keeloqtx/ui_keeloqtx.cpp new file mode 100644 index 000000000..eb753f15c --- /dev/null +++ b/firmware/application/external/keeloqtx/ui_keeloqtx.cpp @@ -0,0 +1,248 @@ +#include "ui_keeloqtx.hpp" +#include "ui_fileman.hpp" +#include "baseband_api.hpp" +#include "../../keeloq_common.hpp" +#include "../../baseband/fprotos/fprotogeneral.hpp" + +using namespace portapack; +using namespace ui; + +namespace fs = std::filesystem; + +namespace ui::external_app::ui_keeloqtx { + +void KeeloqTXView::focus() { + button_open.focus(); +} + +void KeeloqTXView::update_hop() { + hop = data.btn << 28 | (data.serial & 0x3FF) << 16 | data.counter; + + if (data.mf_name == "Aprimatic") { + uint32_t apri_serial = data.serial; + uint8_t apr1 = 0; + + for (uint16_t i = 1; i != 0b10000000000; i <<= 1) { + if (apri_serial & i) apr1++; + } + + apri_serial &= 0b00001111111111; + + if (apr1 % 2 == 0) { + apri_serial |= 0b110000000000; + } + + hop = data.btn << 28 | (apri_serial & 0xFFF) << 16 | data.counter; + } else if ( + data.mf_name == "DTM_Neo" || data.mf_name == "FAAC_RC,XT" || data.mf_name == "Mutanco_Mutancode" || data.mf_name == "Came_Space" || data.mf_name == "Genius_Bravo" || data.mf_name == "GSN" || data.mf_name == "Rosh" || data.mf_name == "Rossi" || data.mf_name == "Peccinin" || data.mf_name == "Steelmate" || data.mf_name == "Cardin_S449") { + hop = data.btn << 28 | (data.serial & 0xFFF) << 16 | data.counter; + } else if ( + data.mf_name == "NICE_Smilo" || data.mf_name == "NICE_MHOUSE" || data.mf_name == "JCM_Tech") { + hop = data.btn << 28 | (data.serial & 0xFF) << 16 | data.counter; + } else if (data.mf_name == "Merlin") { + hop = data.btn << 28 | (0x000) << 16 | data.counter; + } else if (data.mf_name == "Centurion") { + hop = data.btn << 28 | (0x1CE) << 16 | data.counter; + } else if (data.mf_name == "Monarch") { + hop = data.btn << 28 | (0x100) << 16 | data.counter; + } else if (data.mf_name == "Dea_Mio") { + uint8_t first_disc_num = (data.serial >> 8) & 0xF; + uint8_t result_disc = (0xC + (first_disc_num % 4)); + + uint32_t dea_serial = (data.serial & 0xFF) | (((uint32_t)result_disc) << 8); + + hop = data.btn << 28 | (dea_serial & 0xFFF) << 16 | data.counter; + } + + text_hop.set(to_string_hex(hop)); +} + +void KeeloqTXView::update_payload() { + uint64_t encrypt = 0; + + switch (current_key.type) { + case KEELOQ_SIMPLE_LEARNING: { + encrypt = keeloq_encrypt(hop, current_key.key); + + break; + } + case KEELOQ_NORMAL_LEARNING: { + uint64_t man = keeloq_normal_learning(fix, current_key.key); + + encrypt = keeloq_encrypt(hop, man); + + break; + } + } + + payload = (uint64_t)fix << 32 | encrypt; + + uint64_t preview_payload = FProtoGeneral::subghz_protocol_blocks_reverse_key(payload, 64); + + text_payload.set(to_string_hex(preview_payload)); + encode_data(); +} + +void KeeloqTXView::encode_data() { + std::string fragments{}; + + for (uint32_t i = 0; i < 64; ++i) { + fragments += keeloq_fragments[bit(payload, i)]; + } + + encoded_data = KEELOQ_HEADER + fragments + "1001"; + + if (data.mf_name == "Sommer") { + pause_duration = 28; + } else { + pause_duration = 39; + } +} + +KeeloqTXView::KeeloqTXView(NavigationView& nav) + : nav_{nav} { + baseband::run_image(portapack::spi_flash::image_tag_ook); + + add_children({&labels, + &text_mf_name, + &text_serial, + &text_fix, + &text_hop, + &field_counter, + &field_button, + &field_repeat, + &text_payload, + &button_open, + &text_status, + &progressbar, + &tx_view}); + + field_counter.on_change = [this](uint32_t value) { + data.counter = (uint16_t)value; + + update_hop(); + update_payload(); + }; + + field_button.on_change = [this](uint32_t value) { + data.btn = (uint8_t)value; + + fix &= 0xFFFFFFF; + fix |= data.btn << 28; + + text_fix.set(to_string_hex(fix)); + + update_hop(); + update_payload(); + }; + + field_repeat.on_change = [this](uint32_t value) { + repeat = value; + }; + + field_repeat.set_value(4, true); + + tx_view.on_edit_frequency = [this, &nav]() { + auto new_view = nav.push(transmitter_model.target_frequency()); + new_view->on_changed = [this](rf::Frequency f) { + transmitter_model.set_target_frequency(f); + }; + }; + + tx_view.on_start = [this]() { + start_tx(); + }; + + tx_view.on_stop = [this]() { + baseband::kill_ook(); + stop_tx(); + }; + + button_open.on_select = [this](const Button&) { + auto open_view = nav_.push(".KEELOQ"); + + ensure_directory(keeloq_remotes_dir); + open_view->push_dir(keeloq_remotes_dir); + open_view->on_changed = [this](fs::path path) { + nav_.set_on_pop([this, path]() { + if (!read_keeloq_file(path, data)) { + return; + } + + file_path = path; + + for (const auto& key : keystore.get_keys()) { + if (key.mf_name == data.mf_name) { + current_key = key; + + break; + } + } + + fix = data.btn << 28 | data.serial; + + update_hop(); + + text_mf_name.set(data.mf_name); + text_serial.set(to_string_hex(data.serial)); + text_fix.set(to_string_hex(fix)); + + field_counter.set_value(data.counter, false); + field_button.set_value(data.btn, false); + + update_payload(); + + field_counter.focus(); + }); + }; + }; +} + +void KeeloqTXView::start_tx() { + size_t bitstream_length = encoders::make_bitstream(encoded_data); + + progressbar.set_max(repeat); + tx_view.set_transmitting(true); + + transmitter_model.enable(); + + baseband::set_ook_data( + bitstream_length, + OOK_SAMPLERATE * (400.0 / 1000000.0), + repeat, + pause_duration); +} + +void KeeloqTXView::stop_tx() { + transmitter_model.disable(); + text_status.set("Ready"); + progressbar.set_value(0); + tx_view.set_transmitting(false); + + data.counter += 1; + + field_counter.set_value(data.counter, false); + + update_hop(); + update_payload(); + + write_keeloq_file(file_path, data); +} + +void KeeloqTXView::on_tx_progress(uint32_t progress, bool done) { + if (!done) { + text_status.set(to_string_dec_uint(progress + 1) + "/" + to_string_dec_uint(repeat)); + progressbar.set_value(progress + 1); + } else { + chThdSleepMilliseconds(10); + + stop_tx(); + } +} + +KeeloqTXView::~KeeloqTXView() { + transmitter_model.disable(); + baseband::shutdown(); +} + +} // namespace ui::external_app::ui_keeloqtx diff --git a/firmware/application/external/keeloqtx/ui_keeloqtx.hpp b/firmware/application/external/keeloqtx/ui_keeloqtx.hpp new file mode 100644 index 000000000..9c081f6ce --- /dev/null +++ b/firmware/application/external/keeloqtx/ui_keeloqtx.hpp @@ -0,0 +1,129 @@ +#ifndef __KEELOQTX__ +#define __KEELOQTX__ + +#include "ui.hpp" +#include "ui_transmitter.hpp" +#include "transmitter_model.hpp" +#include "file_path.hpp" +#include "app_settings.hpp" +#include "radio_state.hpp" +#include "encoders.hpp" +#include "string_format.hpp" +#include "../../keeloq_file.hpp" +#include "../../keeloq_keystore.hpp" + +#define KEELOQ_HEADER "101010101010101010101010000000000" + +namespace ui::external_app::ui_keeloqtx { + +class KeeloqTXView : public View { + public: + KeeloqTXView(NavigationView& nav); + ~KeeloqTXView(); + + void focus() override; + + std::string title() const override { + return "KeeLoqTX"; + } + + private: + const std::string keeloq_fragments[2] = {"110", "100"}; + + NavigationView& nav_; + + TxRadioState radio_state_{ + 433920000, + 1750000, + OOK_SAMPLERATE}; + + app_settings::SettingsManager settings_{ + "tx_keeloq", app_settings::Mode::TX}; + + std::filesystem::path file_path{}; + + KeeloqKeystore keystore{}; + KeeloqKey current_key{}; + KeeloqData data{}; + + uint32_t fix = 0; + uint32_t hop = 0; + uint64_t payload = 0; + + void update_hop(); + + Labels labels{ + {{UI_POS_X(1), UI_POS_Y(0)}, "Manufacturer:", Theme::getInstance()->fg_light->foreground}, + {{UI_POS_X(1), UI_POS_Y(1)}, "Serial:", Theme::getInstance()->fg_light->foreground}, + {{UI_POS_X(1), UI_POS_Y(2)}, "Fix:", Theme::getInstance()->fg_light->foreground}, + {{UI_POS_X(1), UI_POS_Y(3)}, "Hop:", Theme::getInstance()->fg_light->foreground}, + {{UI_POS_X(1), UI_POS_Y(4)}, "Counter:", Theme::getInstance()->fg_light->foreground}, + {{UI_POS_X(1), UI_POS_Y(5)}, "Button:", Theme::getInstance()->fg_light->foreground}, + {{UI_POS_X(1), UI_POS_Y(6)}, "Repeat:", Theme::getInstance()->fg_light->foreground}, + {{UI_POS_X(2), UI_POS_Y(8)}, "Payload:", Theme::getInstance()->fg_light->foreground}, + }; + + Text text_mf_name{{UI_POS_X(15), UI_POS_Y(0), 128, 16}, ""}; + Text text_serial{{UI_POS_X(9), UI_POS_Y(1), 64, 16}, "00000000"}; + Text text_fix{{UI_POS_X(6), UI_POS_Y(2), 64, 16}, "00000000"}; + Text text_hop{{UI_POS_X(6), UI_POS_Y(3), 64, 16}, "00000000"}; + + NumberField field_counter{ + {UI_POS_X(10), UI_POS_Y(4)}, + 5, + {0, 65535}, + 1, + ' '}; + NumberField field_button{ + {UI_POS_X(9), UI_POS_Y(5)}, + 2, + {0, 15}, + 1, + ' '}; + NumberField field_repeat{ + {UI_POS_X(10), UI_POS_Y(6)}, + 3, + {0, 100}, + 1, + ' '}; + + Text text_payload{{UI_POS_X(2), UI_POS_Y(9), 128, 16}, "0000000000000000"}; + + void update_payload(); + + Button button_open{{UI_POS_X(0), UI_POS_Y(11), screen_width, 32}, "Open file"}; + + Text text_status{{UI_POS_X(2), UI_POS_Y_BOTTOM(7), 128, 16}, "Ready"}; + + ProgressBar progressbar{ + {UI_POS_X(2), UI_POS_Y_BOTTOM(7) + 20, UI_POS_WIDTH_REMAINING(4), 16}}; + + TransmitterView tx_view{ + (int16_t)UI_POS_Y_BOTTOM(4), + 50000, + 9}; + + std::string encoded_data{}; + + void encode_data(); + + uint32_t repeat = 4; + uint32_t pause_duration = 0; + + void start_tx(); + void stop_tx(); + + void update_progress(); + void on_tx_progress(uint32_t progress, bool done); + + MessageHandlerRegistration message_handler_tx_progress{ + Message::ID::TXProgress, + [this](const Message* const p) { + const auto message = *reinterpret_cast(p); + this->on_tx_progress(message.progress, message.done); + }}; +}; + +} // namespace ui::external_app::ui_keeloqtx + +#endif diff --git a/firmware/application/keeloq_common.cpp b/firmware/application/keeloq_common.cpp new file mode 100644 index 000000000..28c487cd7 --- /dev/null +++ b/firmware/application/keeloq_common.cpp @@ -0,0 +1,36 @@ +#include "keeloq_common.hpp" + +uint32_t keeloq_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; +} + +uint32_t keeloq_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; +} + +uint64_t keeloq_normal_learning(uint32_t data, const uint64_t key) { + uint32_t k1, k2; + + data &= 0x0FFFFFFF; + data |= 0x20000000; + k1 = keeloq_decrypt(data, key); + + data &= 0x0FFFFFFF; + data |= 0x60000000; + k2 = keeloq_decrypt(data, key); + + return ((uint64_t)k2 << 32) | k1; +} diff --git a/firmware/application/keeloq_common.hpp b/firmware/application/keeloq_common.hpp new file mode 100644 index 000000000..04db42ed1 --- /dev/null +++ b/firmware/application/keeloq_common.hpp @@ -0,0 +1,16 @@ +#ifndef __KEELOQ_COMMON__ +#define __KEELOQ_COMMON__ + +#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) + +#define KEELOQ_NLF 0x3A5C742E + +uint32_t keeloq_encrypt(const uint32_t data, const uint64_t key); +uint32_t keeloq_decrypt(const uint32_t data, const uint64_t key); +uint64_t keeloq_normal_learning(uint32_t data, const uint64_t key); + +#endif diff --git a/firmware/application/keeloq_file.cpp b/firmware/application/keeloq_file.cpp index ffb3b613e..0000643f9 100644 --- a/firmware/application/keeloq_file.cpp +++ b/firmware/application/keeloq_file.cpp @@ -43,7 +43,7 @@ bool read_keeloq_file(const fs::path& path, KeeloqData& data) { data.mf_name = std::string{chunks[0]}; data.serial = std::strtoul(chunks[1].data(), NULL, 16); - data.counter = std::atoi(chunks[2].data()); + data.counter = (uint16_t)std::atoi(chunks[2].data()); data.btn = (uint8_t)std::atoi(chunks[3].data()); return true; @@ -60,7 +60,10 @@ bool write_keeloq_file(const fs::path& path, const KeeloqData& data) { return false; } - std::string formatted = data.mf_name + ";" + to_string_hex(data.serial) + ";" + to_string_dec_uint(data.counter) + ";" + to_string_dec_uint(data.btn); + std::string formatted = data.mf_name + ";" + + to_string_hex(data.serial) + ";" + + to_string_dec_uint(data.counter) + ";" + + to_string_dec_uint(data.btn); file.write_line(formatted); file.close(); diff --git a/firmware/application/keeloq_file.hpp b/firmware/application/keeloq_file.hpp index 349511c05..ca0ff1b42 100644 --- a/firmware/application/keeloq_file.hpp +++ b/firmware/application/keeloq_file.hpp @@ -30,7 +30,7 @@ struct KeeloqData { std::string mf_name{}; uint32_t serial = 0; - uint32_t counter = 0; + uint16_t counter = 0; uint8_t btn = 0; }; diff --git a/sdcard/KEELOQKEYS/MFCODES b/sdcard/KEELOQKEYS/MFCODES new file mode 100644 index 000000000..d3a5b26bd --- /dev/null +++ b/sdcard/KEELOQKEYS/MFCODES @@ -0,0 +1 @@ +Stilmatic;0123456789ABCDEF;2 diff --git a/sdcard/KEELOQKEYS/README.md b/sdcard/KEELOQKEYS/README.md new file mode 100644 index 000000000..dd530d519 --- /dev/null +++ b/sdcard/KEELOQKEYS/README.md @@ -0,0 +1,14 @@ +# Keystore File Specification +## Introduction +This directory contains the KeeLoq keystore file needed by SubGhzD to decrypt KeeLoq signals and used by the KeeLoq TX app to emulate KeeLoq transmitters. +## Where to get more KeeLoq keys +To gather more KeeLoq keys you can either extract them directly from the receiver's firmware or you can grab them from Flipper Zero firmwares, such as Unleashed and Momentum. + +To do the latter a Flipper Zero device is required, since the `keeloq_mfcodes` file on the flipper is encrypted using the factory AES-128 keys stored in the its security enclave chip. In order to decrypt them and have them exported for use in Mayhem, you can use [Rocket God's SubGHz toolkit app](https://github.com/RocketGod-git/RocketGods-SubGHz-Toolkit). + +## File structure +Each line in the MFCODES file contains three fields, each separated by a semicolon: Manufacturer, key and key type. For example: +``` +Stilmatic;0123456789ABCDEF;2 +``` +The type field can be either 1 or 2 currently (KeeLoq simple learning and normal learning, respectively) since those are the currently supported learning modes.