This commit is contained in:
KaraZajac
2026-02-17 19:24:03 -05:00
parent b1295a9b86
commit 5944cfa741
49 changed files with 807 additions and 449 deletions
+104 -90
View File
@@ -2,98 +2,112 @@
//! Data is stored as binary to avoid plain-text keys in config.
//! Format: "KATK" magic, n_entries (u16 LE), then per entry: type_id (u32 LE), key (u64 LE), then "VAG " + 64 bytes.
//! Key bytes are little-endian (LSB first); the resulting u64 matches reference hex (MSB-first notation).
//! All manufacture keys from the encrypted keystore list are included for current and future protocols.
//! Type 20 (Star Line) is also included so KeyStore.star_line_mf_key is populated from the same key as SL_A2-A4.
//! Generated from REFERENCES/mf_keys.txt by scripts/build_keystore_from_mf_keys.py
/// Blob: KATK + 41 entries (30 original + 11 Pandora new keys) + VAG 64 bytes.
/// Blob: KATK + 83 entries + VAG 64 bytes.
#[rustfmt::skip]
pub const KEYSTORE_BLOB: &[u8] = &[
b'K', b'A', b'T', b'K',
0x29, 0x00, // 41 entries (30 original + 11 Pandora)
// type 10 KIA
0x0A, 0x00, 0x00, 0x00, 0xDB, 0x5C, 0xAA, 0x8D, 0xFC, 0xDF, 0xF5, 0xA8,
// type 11 KIAV6A
0x0B, 0x00, 0x00, 0x00, 0xFC, 0x23, 0xAA, 0x80, 0xA8, 0x64, 0x86, 0x63,
// type 12 KIAV6B
0x0C, 0x00, 0x00, 0x00, 0xF4, 0x2B, 0xA2, 0x88, 0xA0, 0x6C, 0x8E, 0x6B,
// type 13 KIAV5
0x0D, 0x00, 0x00, 0x00, 0x30, 0x30, 0x45, 0x4B, 0x52, 0x46, 0x54, 0x53,
// type 1 Alligator
0x01, 0x00, 0x00, 0x00, 0x78, 0x55, 0x6C, 0x62, 0xB2, 0xD4, 0xF3, 0xEE,
// type 2 Mongoose
0x02, 0x00, 0x00, 0x00, 0xBA, 0xA0, 0xC1, 0xA7, 0xB2, 0xE7, 0xA5, 0xD5,
// type 1 SL_A6-A9/Tomahawk_9010
0x01, 0x00, 0x00, 0x00, 0x03, 0x12, 0x30, 0x08, 0x57, 0x04, 0x04, 0x55,
// type 1 Pantera
0x01, 0x00, 0x00, 0x00, 0x65, 0x6E, 0x79, 0x64, 0x4E, 0x47, 0x41, 0x4D,
// type 1 SL_A2-A4
0x01, 0x00, 0x00, 0x00, 0xDA, 0x78, 0xFE, 0xF8, 0x9B, 0xF8, 0xF7, 0x9B,
// type 1 Cenmax_St-5
0x01, 0x00, 0x00, 0x00, 0xFC, 0xED, 0xCF, 0xF7, 0xA8, 0xFB, 0xFB, 0xAA,
// type 1 SL_B6,B9_dop
0x01, 0x00, 0x00, 0x00, 0xFE, 0xDC, 0xBA, 0x2A, 0xB3, 0xD4, 0xE5, 0xF6,
// type 1 Harpoon
0x01, 0x00, 0x00, 0x00, 0x64, 0x08, 0x07, 0x64, 0x07, 0x01, 0x87, 0x25,
// type 1 Tomahawk_TZ-9030
0x01, 0x00, 0x00, 0x00, 0x25, 0x87, 0x01, 0x07, 0x64, 0x07, 0x08, 0x64,
// type 1 Tomahawk_Z,X_3-5
0x01, 0x00, 0x00, 0x00, 0xEF, 0xCD, 0xAB, 0x2A, 0x3B, 0x4D, 0x5E, 0x6F,
// type 1 Cenmax_St-7
0x01, 0x00, 0x00, 0x00, 0x2B, 0x75, 0xAC, 0x80, 0xB9, 0x46, 0xB4, 0x66,
// type 1 Sheriff
0x01, 0x00, 0x00, 0x00, 0x78, 0x94, 0x18, 0xA9, 0x94, 0x25, 0x33, 0x12,
// type 1 Pantera_CLK
0x01, 0x00, 0x00, 0x00, 0x14, 0x0A, 0x61, 0x82, 0x0A, 0x85, 0x30, 0x41,
// type 1 Cenmax
0x01, 0x00, 0x00, 0x00, 0x56, 0x34, 0x12, 0x90, 0x78, 0x56, 0x34, 0x12,
// type 1 Alligator_S-275
0x01, 0x00, 0x00, 0x00, 0xF9, 0x84, 0x62, 0x67, 0xB7, 0x5C, 0x36, 0x54,
// type 2 Guard_RF-311A
0x02, 0x00, 0x00, 0x00, 0x47, 0x88, 0x19, 0xEB, 0xC2, 0xDA, 0x61, 0x19,
// type 1 Partisan_RX
0x01, 0x00, 0x00, 0x00, 0x1E, 0xF1, 0x28, 0x70, 0xA1, 0x12, 0x3C, 0xAD,
// type 1 APS-1100_APS-2550
0x01, 0x00, 0x00, 0x00, 0x28, 0x47, 0x33, 0x56, 0x50, 0x2C, 0x73, 0x68,
// type 1 Pantera_XS/Jaguar
0x01, 0x00, 0x00, 0x00, 0x27, 0x46, 0x33, 0x56, 0x50, 0x2C, 0x73, 0x67,
// type 0 Teco
0x00, 0x00, 0x00, 0x00, 0xD7, 0xD1, 0x26, 0xF1, 0xE8, 0x26, 0x15, 0xB5,
// type 0 Leopard
0x00, 0x00, 0x00, 0x00, 0x46, 0x8A, 0xD8, 0xE7, 0x33, 0xDC, 0x67, 0x10,
// type 0 Faraon
0x00, 0x00, 0x00, 0x00, 0xF5, 0x87, 0x22, 0xB6, 0x38, 0x64, 0x32, 0x8A,
// type 0 Reff
0x00, 0x00, 0x00, 0x00, 0x8A, 0x41, 0xC2, 0x7D, 0x55, 0xFA, 0x12, 0x69,
// type 0 ZX-730-750-1055
0x00, 0x00, 0x00, 0x00, 0x96, 0x15, 0x35, 0x27, 0xAB, 0x7A, 0x1A, 0xB5,
// type 20 Star Line (same key as SL_A2-A4, for KeyStore.star_line_mf_key)
0x14, 0x00, 0x00, 0x00, 0xDA, 0x78, 0xFE, 0xF8, 0x9B, 0xF8, 0xF7, 0x9B,
// type 1 Pandora_M101
0x01, 0x00, 0x00, 0x00, 0x99, 0xAE, 0x17, 0x2A, 0x9A, 0x7D, 0x1D, 0x38,
// type 1 Pandora_PRO
0x01, 0x00, 0x00, 0x00, 0xAC, 0x97, 0x15, 0x28, 0x7B, 0x97, 0x36, 0x1B,
// type 1 Pandora_PRO2
0x01, 0x00, 0x00, 0x00, 0xB9, 0x19, 0x2B, 0x39, 0x9A, 0x1E, 0xAF, 0x7E,
// type 1 Pandora_SUBARU
0x01, 0x00, 0x00, 0x00, 0x95, 0x79, 0x22, 0xA4, 0x34, 0xB9, 0x19, 0x35,
// type 1 Pandora_SUZUKI
0x01, 0x00, 0x00, 0x00, 0x73, 0xBD, 0xED, 0xDC, 0x06, 0xEF, 0x52, 0x25,
// type 1 Pandora_DEA
0x01, 0x00, 0x00, 0x00, 0x77, 0xB9, 0xE9, 0xD8, 0x02, 0xEB, 0x56, 0x21,
// type 1 Pandora_GIBIDI
0x01, 0x00, 0x00, 0x00, 0x76, 0xBA, 0xEA, 0xDB, 0x01, 0xEA, 0x55, 0x22,
// type 1 Pandora_MCODE
0x01, 0x00, 0x00, 0x00, 0x75, 0xBB, 0xEB, 0xDA, 0x00, 0xE9, 0x54, 0x23,
// type 1 Pandora_Unknown_1
0x01, 0x00, 0x00, 0x00, 0x72, 0xBC, 0xEC, 0xDD, 0x07, 0xEE, 0x53, 0x24,
// type 1 Pandora_Unknown_2
0x01, 0x00, 0x00, 0x00, 0x70, 0xBE, 0xEE, 0xDF, 0x05, 0xEC, 0x51, 0x26,
// type 1 Pandora_Test_Debug_2
// magic + n_entries
0x4B, 0x41, 0x54, 0x4B, 0x53, 0x00,
// entries 0..
0x06, 0x00, 0x00, 0x00, 0x58, 0x47, 0x30, 0x23, 0x00, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x48, 0x57, 0x20, 0x33, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x40, 0x30, 0x20, 0x10, 0x04, 0x03, 0x02, 0x01,
0x02, 0x00, 0x00, 0x00, 0xEF, 0xCD, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x01,
0x01, 0x00, 0x00, 0x00, 0xEF, 0xCD, 0xAB, 0x89, 0x67, 0x45, 0x32, 0x01,
b'V', b'A', b'G', b' ',
// VAG raw 64 bytes
0x01, 0x37, 0x6C, 0x86, 0xAD, 0xAB, 0xCC, 0x43, 0x07, 0x4D, 0xE8, 0x59, 0xC1, 0x2F, 0x36, 0xAB,
0x02, 0x37, 0x7C, 0x65, 0xCE, 0xDC, 0x42, 0xEA, 0xA4, 0x53, 0xE8, 0x61, 0xD9, 0xB7, 0x20, 0xFC,
0x03, 0x8A, 0xA3, 0x7B, 0x1E, 0x56, 0x1F, 0x83, 0x84, 0xB6, 0x19, 0xC5, 0x2E, 0x0A, 0x3F, 0xD7,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x03, 0x19, 0x98, 0xAA, 0xAB, 0xDA, 0xAE, 0x05,
0x01, 0x00, 0x00, 0x00, 0x03, 0x19, 0x98, 0xAA, 0xAB, 0xDA, 0xAE, 0x08,
0x00, 0x00, 0x00, 0x00, 0x46, 0x8A, 0xD8, 0xE7, 0x33, 0xDC, 0x67, 0x10,
// entries 8..
0x01, 0x00, 0x00, 0x00, 0x78, 0x94, 0x18, 0xA9, 0x94, 0x25, 0x33, 0x12,
0x01, 0x00, 0x00, 0x00, 0x56, 0x34, 0x12, 0x90, 0x78, 0x56, 0x34, 0x12,
0x02, 0x00, 0x00, 0x00, 0x14, 0xDF, 0xB1, 0xA9, 0x44, 0x05, 0x8B, 0x18,
0x02, 0x00, 0x00, 0x00, 0x47, 0x88, 0x19, 0xEB, 0xC2, 0xDA, 0x61, 0x19,
0x01, 0x00, 0x00, 0x00, 0xAC, 0x97, 0x15, 0x28, 0x7B, 0x97, 0x36, 0x1B,
0x02, 0x00, 0x00, 0x00, 0x44, 0x6F, 0x38, 0x9D, 0xE5, 0xBB, 0x7D, 0x20,
0x01, 0x00, 0x00, 0x00, 0x77, 0xB9, 0xE9, 0xD8, 0x02, 0xEB, 0x56, 0x21,
0x01, 0x00, 0x00, 0x00, 0x76, 0xBA, 0xEA, 0xDB, 0x01, 0xEA, 0x55, 0x22,
// entries 16..
0x01, 0x00, 0x00, 0x00, 0x75, 0xBB, 0xEB, 0xDA, 0x00, 0xE9, 0x54, 0x23,
0x01, 0x00, 0x00, 0x00, 0x72, 0xBC, 0xEC, 0xDD, 0x07, 0xEE, 0x53, 0x24,
0x01, 0x00, 0x00, 0x00, 0x73, 0xBD, 0xED, 0xDC, 0x06, 0xEF, 0x52, 0x25,
0x01, 0x00, 0x00, 0x00, 0x64, 0x08, 0x07, 0x64, 0x07, 0x01, 0x87, 0x25,
0x01, 0x00, 0x00, 0x00, 0x02, 0x19, 0x99, 0x02, 0x19, 0x99, 0x26, 0x26,
0x01, 0x00, 0x00, 0x00, 0x70, 0xBE, 0xEE, 0xDF, 0x05, 0xEC, 0x51, 0x26,
0x0B, 0x00, 0x00, 0x00, 0x35, 0x08, 0x7C, 0x11, 0x9B, 0x3A, 0x19, 0x27,
0x01, 0x00, 0x00, 0x00, 0x71, 0x44, 0x79, 0x7D, 0x30, 0x7B, 0x31, 0x30,
// entries 24..
0x01, 0x00, 0x00, 0x00, 0x61, 0x39, 0x4E, 0x30, 0x65, 0x38, 0x4C, 0x31,
0x01, 0x00, 0x00, 0x00, 0x95, 0x79, 0x22, 0xA4, 0x34, 0xB9, 0x19, 0x35,
0x01, 0x00, 0x00, 0x00, 0xE0, 0xDF, 0xF4, 0xA5, 0xAC, 0xAB, 0x2B, 0x35,
0x02, 0x00, 0x00, 0x00, 0x9B, 0xA1, 0x6D, 0xF7, 0xB4, 0x1A, 0x46, 0x3E,
0x01, 0x00, 0x00, 0x00, 0x99, 0xAE, 0x17, 0x2A, 0x9A, 0x7D, 0x1D, 0x38,
0x01, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x10, 0x20, 0x30, 0x40,
0x01, 0x00, 0x00, 0x00, 0x14, 0x0A, 0x61, 0x82, 0x0A, 0x85, 0x30, 0x41,
0x01, 0x00, 0x00, 0x00, 0x30, 0x38, 0x39, 0x31, 0x58, 0x45, 0x4C, 0x41,
// entries 32..
0x02, 0x00, 0x00, 0x00, 0x83, 0x45, 0x13, 0x83, 0x30, 0x90, 0x92, 0x42,
0x01, 0x00, 0x00, 0x00, 0x69, 0x47, 0x73, 0x65, 0x65, 0x73, 0x72, 0x47,
0x01, 0x00, 0x00, 0x00, 0x53, 0x52, 0x54, 0x73, 0x6D, 0x6C, 0x4D, 0x48,
0x03, 0x00, 0x00, 0x00, 0x76, 0x4F, 0x64, 0x55, 0x7A, 0x4D, 0x6D, 0x4C,
0x01, 0x00, 0x00, 0x00, 0x65, 0x6E, 0x79, 0x64, 0x4E, 0x47, 0x41, 0x4D,
0x0D, 0x00, 0x00, 0x00, 0x30, 0x30, 0x45, 0x4B, 0x52, 0x46, 0x54, 0x53,
0x05, 0x00, 0x00, 0x00, 0x14, 0x8C, 0x61, 0x69, 0x76, 0x6C, 0x69, 0x53,
0x01, 0x00, 0x00, 0x00, 0xF9, 0x84, 0x62, 0x67, 0xB7, 0x5C, 0x36, 0x54,
// entries 40..
0x01, 0x00, 0x00, 0x00, 0x4E, 0x49, 0x43, 0x45, 0x53, 0x4D, 0x52, 0x54,
0x01, 0x00, 0x00, 0x00, 0x03, 0x12, 0x30, 0x08, 0x57, 0x04, 0x04, 0x55,
0x02, 0x00, 0x00, 0x00, 0xFF, 0xCA, 0x76, 0x94, 0x22, 0x68, 0x27, 0x57,
0x0B, 0x00, 0x00, 0x00, 0xFC, 0x23, 0xAA, 0x80, 0xA8, 0x64, 0x86, 0x63,
0x01, 0x00, 0x00, 0x00, 0x25, 0x87, 0x01, 0x07, 0x64, 0x07, 0x08, 0x64,
0x01, 0x00, 0x00, 0x00, 0x2B, 0x75, 0xAC, 0x80, 0xB9, 0x46, 0xB4, 0x66,
0x01, 0x00, 0x00, 0x00, 0x27, 0x46, 0x33, 0x56, 0x50, 0x2C, 0x73, 0x67,
0x01, 0x00, 0x00, 0x00, 0x28, 0x47, 0x33, 0x56, 0x50, 0x2C, 0x73, 0x68,
// entries 48..
0x00, 0x00, 0x00, 0x00, 0x8A, 0x41, 0xC2, 0x7D, 0x55, 0xFA, 0x12, 0x69,
0x0C, 0x00, 0x00, 0x00, 0xF4, 0x2B, 0xA2, 0x88, 0xA0, 0x6C, 0x8E, 0x6B,
0x09, 0x00, 0x00, 0x00, 0xD2, 0x3E, 0xC6, 0x15, 0x48, 0xAE, 0xB6, 0x6E,
0x01, 0x00, 0x00, 0x00, 0xEF, 0xCD, 0xAB, 0x2A, 0x3B, 0x4D, 0x5E, 0x6F,
0x02, 0x00, 0x00, 0x00, 0xBF, 0xDC, 0x6E, 0x37, 0xD4, 0xEE, 0xCB, 0x7B,
0x01, 0x00, 0x00, 0x00, 0xB9, 0x19, 0x2B, 0x39, 0x9A, 0x1E, 0xAF, 0x7E,
0x02, 0x00, 0x00, 0x00, 0x23, 0x12, 0x94, 0x84, 0x35, 0xF4, 0x55, 0x84,
0x02, 0x00, 0x00, 0x00, 0x30, 0x4E, 0x39, 0x61, 0x78, 0x42, 0x07, 0x86,
// entries 56..
0x01, 0x00, 0x00, 0x00, 0xB7, 0x03, 0x79, 0x53, 0x59, 0x6E, 0x14, 0x89,
0x00, 0x00, 0x00, 0x00, 0xF5, 0x87, 0x22, 0xB6, 0x38, 0x64, 0x32, 0x8A,
0x01, 0x00, 0x00, 0x00, 0x9B, 0x2C, 0x9C, 0xC9, 0x36, 0x8C, 0x63, 0x96,
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA5, 0x5A, 0x65, 0x04, 0x98,
0x01, 0x00, 0x00, 0x00, 0xDA, 0x78, 0xFE, 0xF8, 0x9B, 0xF8, 0xF7, 0x9B,
0x02, 0x00, 0x00, 0x00, 0x5C, 0xE2, 0xB8, 0x47, 0x3A, 0xFD, 0x61, 0x9C,
0x01, 0x00, 0x00, 0x00, 0xA7, 0x2B, 0x31, 0xCF, 0x53, 0x81, 0xA0, 0x9D,
0x0A, 0x00, 0x00, 0x00, 0xDB, 0x5C, 0xAA, 0x8D, 0xFC, 0xDF, 0xF5, 0xA8,
// entries 64..
0x02, 0x00, 0x00, 0x00, 0x97, 0x52, 0x65, 0xB7, 0x32, 0x85, 0x74, 0xA9,
0x00, 0x00, 0x00, 0x00, 0xA1, 0xB5, 0x89, 0x21, 0xA3, 0xA7, 0x38, 0xAA,
0x01, 0x00, 0x00, 0x00, 0xFC, 0xED, 0xCF, 0xF7, 0xA8, 0xFB, 0xFB, 0xAA,
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x27, 0xBB, 0x35, 0xAC,
0x01, 0x00, 0x00, 0x00, 0x1E, 0xF1, 0x28, 0x70, 0xA1, 0x12, 0x3C, 0xAD,
0x02, 0x00, 0x00, 0x00, 0x39, 0x71, 0xCD, 0x8C, 0x5A, 0x62, 0xE5, 0xB3,
0x00, 0x00, 0x00, 0x00, 0xD7, 0xD1, 0x26, 0xF1, 0xE8, 0x26, 0x15, 0xB5,
0x00, 0x00, 0x00, 0x00, 0x96, 0x15, 0x35, 0x27, 0xAB, 0x7A, 0x1A, 0xB5,
// entries 72..
0x01, 0x00, 0x00, 0x00, 0xCC, 0x7E, 0xF9, 0xAA, 0xDD, 0x9E, 0xEE, 0xBB,
0x04, 0x00, 0x00, 0x00, 0xD2, 0x3E, 0xC6, 0xB5, 0x48, 0xAE, 0xB6, 0xCE,
0x02, 0x00, 0x00, 0x00, 0xBA, 0xA0, 0xC1, 0xA7, 0xB2, 0xE7, 0xA5, 0xD5,
0x01, 0x00, 0x00, 0x00, 0x78, 0x55, 0x6C, 0x62, 0xB2, 0xD4, 0xF3, 0xEE,
0x02, 0x00, 0x00, 0x00, 0x55, 0x7E, 0x64, 0xC8, 0x52, 0xE5, 0xA3, 0xF1,
0x01, 0x00, 0x00, 0x00, 0xFE, 0xDC, 0xBA, 0x2A, 0xB3, 0xD4, 0xE5, 0xF6,
0x02, 0x00, 0x00, 0x00, 0xAC, 0xFA, 0x01, 0x00, 0x60, 0x05, 0xAC, 0xFA,
0x02, 0x00, 0x00, 0x00, 0xAC, 0xFA, 0x02, 0x00, 0x60, 0x05, 0xAC, 0xFA,
// entries 80..
0x01, 0x00, 0x00, 0x00, 0xDA, 0xF0, 0x34, 0xDA, 0xFA, 0x12, 0xDA, 0xFA,
0x01, 0x00, 0x00, 0x00, 0x20, 0x03, 0x37, 0xD5, 0x2C, 0xE2, 0x4D, 0xFC,
0x14, 0x00, 0x00, 0x00, 0xDA, 0x78, 0xFE, 0xF8, 0x9B, 0xF8, 0xF7, 0x9B,
// VAG tag + 64 bytes
0x56, 0x41, 0x47, 0x20, 0x01, 0x37, 0x6C, 0x86, 0xAD, 0xAB, 0xCC, 0x43,
0x07, 0x4D, 0xE8, 0x59, 0xC1, 0x2F, 0x36, 0xAB, 0x02, 0x37, 0x7C, 0x65,
0xCE, 0xDC, 0x42, 0xEA, 0xA4, 0x53, 0xE8, 0x61, 0xD9, 0xB7, 0x20, 0xFC,
0x03, 0x8A, 0xA3, 0x7B, 0x1E, 0x56, 0x1F, 0x83, 0x84, 0xB6, 0x19, 0xC5,
0x2E, 0x0A, 0x3F, 0xD7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
+82 -8
View File
@@ -10,15 +10,89 @@ const KEELOQ_MF_TYPES: &[u32] = &[0, 1, 2, 10, 20];
/// Names for each key entry in the blob (same order as entries before VAG). Used for fallback decode display.
const KEY_ENTRY_NAMES: &[&str] = &[
"KIA", "KIAV6A", "KIAV6B", "KIAV5", "Alligator", "Mongoose",
"SL_A6-A9/Tomahawk_9010", "Pantera", "SL_A2-A4", "Cenmax_St-5", "SL_B6,B9_dop",
"Harpoon", "Tomahawk_TZ-9030", "Tomahawk_Z,X_3-5", "Cenmax_St-7", "Sheriff",
"Pantera_CLK", "Cenmax", "Alligator_S-275", "Guard_RF-311A", "Partisan_RX",
"APS-1100_APS-2550", "Pantera_XS/Jaguar", "Teco", "Leopard", "Faraon", "Reff",
"ZX-730-750-1055", "Star Line",
"Pandora_M101", "Pandora_PRO", "Pandora_PRO2", "Pandora_SUBARU", "Pandora_SUZUKI",
"Pandora_DEA", "Pandora_GIBIDI", "Pandora_MCODE", "Pandora_Unknown_1", "Pandora_Unknown_2",
"KGB/Subaru",
"Magic_2",
"IronLogic",
"Stilmatic",
"Pandora_Test_Debug_2",
"Rosh",
"Dea_Mio",
"Leopard",
"Sheriff",
"Cenmax",
"Centurion",
"Guard_RF-311A",
"Pandora_PRO",
"Cardin_S449",
"Pandora_DEA",
"Pandora_GIBIDI",
"Pandora_MCODE",
"Pandora_Unknown_1",
"Pandora_SUZUKI",
"Harpoon",
"Gibidi",
"Pandora_Unknown_2",
"Jarolift",
"KEY",
"Novoferm",
"Pandora_SUBARU",
"Pecinin",
"Merlin",
"Pandora_M101",
"IL-100(Smart)",
"Pantera_CLK",
"Kingates_Stylo4k",
"Monarch",
"Aprimatic",
"NICE_MHOUSE",
"BFT",
"Pantera",
"KIAV5",
"FAAC_SLH",
"Alligator_S-275",
"NICE_Smilo",
"SL_A6-A9/Tomahawk_9010",
"Motorline",
"KIAV6A",
"Tomahawk_TZ-9030",
"Cenmax_St-7",
"Pantera_XS/Jaguar",
"APS-1100_APS-2550",
"Reff",
"KIAV6B",
"Beninca_ARC",
"Tomahawk_Z,X_3-5",
"Sommer",
"Pandora_PRO2",
"DoorHan",
"EcoStar",
"JCM_Tech",
"Faraon",
"Came_Space",
"Magic_4",
"SL_A2-A4",
"Elmes_Poland",
"Normstahl",
"KIA",
"GSN",
"Mutanco_Mutancode",
"Cenmax_St-5",
"Magic_3",
"Partisan_RX",
"Steelmate",
"Teco",
"ZX-730-750-1055",
"Jolly_Motors",
"Beninca",
"Mongoose",
"Alligator",
"Comunello",
"SL_B6,B9_dop",
"FAAC_RC,XT",
"Genius_Bravo",
"Rossi",
"DTM_Neo",
"Star Line",
];
const MAGIC: &[u8; 4] = b"KATK";
+308 -308
View File
@@ -1,308 +1,308 @@
//! Protocol decoders and encoders for various keyfob systems.
//!
//! Protocols are aligned with the ProtoPirate reference (`REFERENCES/ProtoPirate/protocols/`).
//! Each decoder processes level+duration pairs from the demodulator and optionally supports
//! encoding (replay). Shared pieces: [common], [keeloq_common], [keys], [aut64].
//!
//! **Manchester decoding**: Ford, Fiat, and common each have separate Manchester state machines
//! (FordV0ManchesterState, FiatV0ManchesterState, CommonManchesterState in common.rs). They are
//! not reused across protocols. Event conventions match the reference per protocol (e.g. Kia V5
//! opposite polarity; Fiat/Ford/common use Flipper-style: level ? ShortLow : ShortHigh).
mod common;
pub mod keeloq_common;
mod keeloq_generic;
#[allow(dead_code)]
pub mod aut64;
#[allow(dead_code)]
pub mod keys;
mod kia_v0;
mod kia_v1;
mod kia_v2;
mod kia_v3_v4;
mod kia_v5;
mod kia_v6;
mod subaru;
mod ford_v0;
mod vag;
mod fiat_v0;
mod suzuki;
mod scher_khan;
mod star_line;
mod psa;
pub use common::DecodedSignal;
use crate::capture::Capture;
use crate::radio::demodulator::LevelDuration;
/// Protocol timing constants
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub struct ProtocolTiming {
/// Short pulse duration in µs
pub te_short: u32,
/// Long pulse duration in µs
pub te_long: u32,
/// Tolerance for timing matching in µs
pub te_delta: u32,
/// Minimum bit count for valid decode
pub min_count_bit: usize,
}
/// Trait for protocol decoders
///
/// Each protocol implements a state machine that processes level+duration pairs.
pub trait ProtocolDecoder: Send + Sync {
/// Get the protocol name
fn name(&self) -> &'static str;
/// Get timing constants
#[allow(dead_code)]
fn timing(&self) -> ProtocolTiming;
/// Get supported frequencies in Hz
fn supported_frequencies(&self) -> &[u32];
/// Reset the decoder state machine
fn reset(&mut self);
/// Feed a level+duration pair to the decoder
/// Returns Some(DecodedSignal) when a complete valid signal is decoded
fn feed(&mut self, level: bool, duration_us: u32) -> Option<DecodedSignal>;
/// Check if this protocol supports encoding
fn supports_encoding(&self) -> bool;
/// Encode a signal with the given button command
fn encode(&self, decoded: &DecodedSignal, button: u8) -> Option<Vec<LevelDuration>>;
}
/// Registry of all supported protocols
pub struct ProtocolRegistry {
decoders: Vec<Box<dyn ProtocolDecoder>>,
}
impl ProtocolRegistry {
/// Create a new protocol registry with all built-in protocols
pub fn new() -> Self {
let decoders: Vec<Box<dyn ProtocolDecoder>> = vec![
// Kia protocols
Box::new(kia_v0::KiaV0Decoder::new()),
Box::new(kia_v1::KiaV1Decoder::new()),
Box::new(kia_v2::KiaV2Decoder::new()),
Box::new(kia_v3_v4::KiaV3V4Decoder::new()),
Box::new(kia_v5::KiaV5Decoder::new()),
Box::new(kia_v6::KiaV6Decoder::new()),
// Other protocols (Ford before Subaru so 250/500µs Ford keyfobs decode as Ford)
Box::new(ford_v0::FordV0Decoder::new()),
Box::new(subaru::SubaruDecoder::new()),
Box::new(vag::VagDecoder::new()),
Box::new(fiat_v0::FiatV0Decoder::new()),
Box::new(suzuki::SuzukiDecoder::new()),
Box::new(scher_khan::ScherKhanDecoder::new()),
Box::new(star_line::StarLineDecoder::new()),
Box::new(psa::PsaDecoder::new()),
];
Self { decoders }
}
/// Process level+duration pairs from demodulator
/// Returns decoded signal info if any protocol matches.
/// Tries normal polarity first, then inverted polarity (so OOK captures where
/// carrier-on is recorded as LOW can still decode as Fiat/Ford etc.).
pub fn process_signal(&mut self, pairs: &[LevelDuration], frequency: u32) -> Option<(String, DecodedSignal)> {
// Try normal polarity first
if let Some(result) = self.process_signal_inner(pairs, frequency, false) {
return Some(result);
}
// Try inverted polarity (capture LOW = RF HIGH)
if let Some(result) = self.process_signal_inner(pairs, frequency, true) {
return Some(result);
}
// No known protocol: try KeeLoq generic (uses keeloq_common with every keystore key)
keeloq_generic::try_decode(pairs, frequency)
}
/// ProtoPirate-style streaming decode: feed the whole stream, on each decode record and reset decoders, continue.
/// Returns one entry per decode: (protocol name, decoded signal, pairs that produced it).
/// Tries normal polarity first; if no decodes, runs again with inverted polarity.
pub fn process_signal_stream(
&mut self,
pairs: &[LevelDuration],
frequency: u32,
) -> Vec<(String, DecodedSignal, Vec<LevelDuration>)> {
let with_normal = self.process_signal_stream_inner(pairs, frequency, false);
if !with_normal.is_empty() {
return with_normal;
}
self.process_signal_stream_inner(pairs, frequency, true)
}
/// Inner streaming decode with optional level inversion.
fn process_signal_stream_inner(
&mut self,
pairs: &[LevelDuration],
frequency: u32,
invert_level: bool,
) -> Vec<(String, DecodedSignal, Vec<LevelDuration>)> {
let mut out = Vec::new();
let mut segment_start = 0_usize;
for decoder in &mut self.decoders {
decoder.reset();
}
for (i, pair) in pairs.iter().enumerate() {
let level = if invert_level { !pair.level } else { pair.level };
let duration_us = pair.duration_us;
let mut hit = None;
for decoder in &mut self.decoders {
let freq_supported = decoder
.supported_frequencies()
.iter()
.any(|&f| {
let diff = if f > frequency { f - frequency } else { frequency - f };
diff < (f / 50)
});
if !freq_supported {
continue;
}
if let Some(decoded) = decoder.feed(level, duration_us) {
hit = Some((decoder.name().to_string(), decoded));
break;
}
}
if let Some((name, decoded)) = hit {
let segment: Vec<LevelDuration> = pairs[segment_start..=i]
.iter()
.map(|p| LevelDuration::new(p.level, p.duration_us))
.collect();
out.push((name, decoded, segment));
for d in &mut self.decoders {
d.reset();
}
segment_start = i + 1;
}
}
out
}
/// Inner decode: feed pairs (with optional level flip) to decoders that support this frequency.
fn process_signal_inner(
&mut self,
pairs: &[LevelDuration],
frequency: u32,
invert_level: bool,
) -> Option<(String, DecodedSignal)> {
for decoder in &mut self.decoders {
decoder.reset();
}
for pair in pairs {
let level = if invert_level { !pair.level } else { pair.level };
let duration_us = pair.duration_us;
for decoder in &mut self.decoders {
let freq_supported = decoder
.supported_frequencies()
.iter()
.any(|&f| {
let diff = if f > frequency { f - frequency } else { frequency - f };
diff < (f / 50) // 2% tolerance
});
if !freq_supported {
continue;
}
if let Some(decoded) = decoder.feed(level, duration_us) {
return Some((decoder.name().to_string(), decoded));
}
}
}
None
}
/// Try to decode a capture (for compatibility with old interface)
#[allow(dead_code)]
pub fn try_decode(&mut self, capture: &Capture) -> Option<(String, DecodedSignal)> {
// Convert raw pairs to LevelDuration and process
if capture.raw_pairs.is_empty() {
return None;
}
let pairs: Vec<LevelDuration> = capture.raw_pairs
.iter()
.map(|p| LevelDuration::new(p.level, p.duration_us))
.collect();
self.process_signal(&pairs, capture.frequency)
}
/// Get a decoder by name
pub fn get(&self, name: &str) -> Option<&dyn ProtocolDecoder> {
self.decoders
.iter()
.find(|d| d.name().eq_ignore_ascii_case(name))
.map(|d| d.as_ref())
}
/// List all protocol names
#[allow(dead_code)]
pub fn list_protocols(&self) -> Vec<&'static str> {
self.decoders.iter().map(|d| d.name()).collect()
}
}
impl Default for ProtocolRegistry {
fn default() -> Self {
Self::new()
}
}
/// Helper macro for duration comparison (matches protopirate's DURATION_DIFF)
#[macro_export]
macro_rules! duration_diff {
($actual:expr, $expected:expr) => {
if $actual > $expected {
$actual - $expected
} else {
$expected - $actual
}
};
}
#[cfg(test)]
mod tests {
use super::*;
use crate::export::flipper::import_sub_raw;
use crate::radio::LevelDuration;
use std::path::Path;
#[test]
fn ford_v0_decodes_imports_ford_unlock_sub() {
let path = Path::new("IMPORTS/FORD/3_unlock_ford.sub");
if !path.exists() {
eprintln!("Skip: {:?} not found (run from crate root)", path);
return;
}
let (freq, raw_pairs) = import_sub_raw(path).unwrap();
let pairs: Vec<LevelDuration> = raw_pairs
.iter()
.map(|p| LevelDuration::new(p.level, p.duration_us))
.collect();
let mut reg = ProtocolRegistry::new();
let results = reg.process_signal_stream(&pairs, freq);
let ford_decodes: Vec<_> = results.iter().filter(|(name, _, _)| *name == "Ford V0").collect();
assert!(
!ford_decodes.is_empty(),
"expected at least one Ford V0 decode from 3_unlock_ford.sub, got: {:?}",
results.iter().map(|(n, _, _)| n.as_str()).collect::<Vec<_>>()
);
}
}
//! Protocol decoders and encoders for various keyfob systems.
//!
//! Protocols are aligned with the ProtoPirate reference (`REFERENCES/ProtoPirate/protocols/`).
//! Each decoder processes level+duration pairs from the demodulator and optionally supports
//! encoding (replay). Shared pieces: [common], [keeloq_common], [keys], [aut64].
//!
//! **Manchester decoding**: Ford, Fiat, and common each have separate Manchester state machines
//! (FordV0ManchesterState, FiatV0ManchesterState, CommonManchesterState in common.rs). They are
//! not reused across protocols. Event conventions match the reference per protocol (e.g. Kia V5
//! opposite polarity; Fiat/Ford/common use Flipper-style: level ? ShortLow : ShortHigh).
mod common;
pub mod keeloq_common;
mod keeloq_generic;
#[allow(dead_code)]
pub mod aut64;
#[allow(dead_code)]
pub mod keys;
mod kia_v0;
mod kia_v1;
mod kia_v2;
mod kia_v3_v4;
mod kia_v5;
mod kia_v6;
mod subaru;
mod ford_v0;
mod vag;
mod fiat_v0;
mod suzuki;
mod scher_khan;
mod star_line;
mod psa;
pub use common::DecodedSignal;
use crate::capture::Capture;
use crate::radio::demodulator::LevelDuration;
/// Protocol timing constants
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub struct ProtocolTiming {
/// Short pulse duration in µs
pub te_short: u32,
/// Long pulse duration in µs
pub te_long: u32,
/// Tolerance for timing matching in µs
pub te_delta: u32,
/// Minimum bit count for valid decode
pub min_count_bit: usize,
}
/// Trait for protocol decoders
///
/// Each protocol implements a state machine that processes level+duration pairs.
pub trait ProtocolDecoder: Send + Sync {
/// Get the protocol name
fn name(&self) -> &'static str;
/// Get timing constants
#[allow(dead_code)]
fn timing(&self) -> ProtocolTiming;
/// Get supported frequencies in Hz
fn supported_frequencies(&self) -> &[u32];
/// Reset the decoder state machine
fn reset(&mut self);
/// Feed a level+duration pair to the decoder
/// Returns Some(DecodedSignal) when a complete valid signal is decoded
fn feed(&mut self, level: bool, duration_us: u32) -> Option<DecodedSignal>;
/// Check if this protocol supports encoding
fn supports_encoding(&self) -> bool;
/// Encode a signal with the given button command
fn encode(&self, decoded: &DecodedSignal, button: u8) -> Option<Vec<LevelDuration>>;
}
/// Registry of all supported protocols
pub struct ProtocolRegistry {
decoders: Vec<Box<dyn ProtocolDecoder>>,
}
impl ProtocolRegistry {
/// Create a new protocol registry with all built-in protocols
pub fn new() -> Self {
let decoders: Vec<Box<dyn ProtocolDecoder>> = vec![
// Kia protocols
Box::new(kia_v0::KiaV0Decoder::new()),
Box::new(kia_v1::KiaV1Decoder::new()),
Box::new(kia_v2::KiaV2Decoder::new()),
Box::new(kia_v3_v4::KiaV3V4Decoder::new()),
Box::new(kia_v5::KiaV5Decoder::new()),
Box::new(kia_v6::KiaV6Decoder::new()),
// Other protocols (Ford before Subaru so 250/500µs Ford keyfobs decode as Ford)
Box::new(ford_v0::FordV0Decoder::new()),
Box::new(subaru::SubaruDecoder::new()),
Box::new(vag::VagDecoder::new()),
Box::new(fiat_v0::FiatV0Decoder::new()),
Box::new(suzuki::SuzukiDecoder::new()),
Box::new(scher_khan::ScherKhanDecoder::new()),
Box::new(star_line::StarLineDecoder::new()),
Box::new(psa::PsaDecoder::new()),
];
Self { decoders }
}
/// Process level+duration pairs from demodulator
/// Returns decoded signal info if any protocol matches.
/// Tries normal polarity first, then inverted polarity (so OOK captures where
/// carrier-on is recorded as LOW can still decode as Fiat/Ford etc.).
pub fn process_signal(&mut self, pairs: &[LevelDuration], frequency: u32) -> Option<(String, DecodedSignal)> {
// Try normal polarity first
if let Some(result) = self.process_signal_inner(pairs, frequency, false) {
return Some(result);
}
// Try inverted polarity (capture LOW = RF HIGH)
if let Some(result) = self.process_signal_inner(pairs, frequency, true) {
return Some(result);
}
// No known protocol: try KeeLoq generic (uses keeloq_common with every keystore key)
keeloq_generic::try_decode(pairs, frequency)
}
/// ProtoPirate-style streaming decode: feed the whole stream, on each decode record and reset decoders, continue.
/// Returns one entry per decode: (protocol name, decoded signal, pairs that produced it).
/// Tries normal polarity first; if no decodes, runs again with inverted polarity.
pub fn process_signal_stream(
&mut self,
pairs: &[LevelDuration],
frequency: u32,
) -> Vec<(String, DecodedSignal, Vec<LevelDuration>)> {
let with_normal = self.process_signal_stream_inner(pairs, frequency, false);
if !with_normal.is_empty() {
return with_normal;
}
self.process_signal_stream_inner(pairs, frequency, true)
}
/// Inner streaming decode with optional level inversion.
fn process_signal_stream_inner(
&mut self,
pairs: &[LevelDuration],
frequency: u32,
invert_level: bool,
) -> Vec<(String, DecodedSignal, Vec<LevelDuration>)> {
let mut out = Vec::new();
let mut segment_start = 0_usize;
for decoder in &mut self.decoders {
decoder.reset();
}
for (i, pair) in pairs.iter().enumerate() {
let level = if invert_level { !pair.level } else { pair.level };
let duration_us = pair.duration_us;
let mut hit = None;
for decoder in &mut self.decoders {
let freq_supported = decoder
.supported_frequencies()
.iter()
.any(|&f| {
let diff = if f > frequency { f - frequency } else { frequency - f };
diff < (f / 50)
});
if !freq_supported {
continue;
}
if let Some(decoded) = decoder.feed(level, duration_us) {
hit = Some((decoder.name().to_string(), decoded));
break;
}
}
if let Some((name, decoded)) = hit {
let segment: Vec<LevelDuration> = pairs[segment_start..=i]
.iter()
.map(|p| LevelDuration::new(p.level, p.duration_us))
.collect();
out.push((name, decoded, segment));
for d in &mut self.decoders {
d.reset();
}
segment_start = i + 1;
}
}
out
}
/// Inner decode: feed pairs (with optional level flip) to decoders that support this frequency.
fn process_signal_inner(
&mut self,
pairs: &[LevelDuration],
frequency: u32,
invert_level: bool,
) -> Option<(String, DecodedSignal)> {
for decoder in &mut self.decoders {
decoder.reset();
}
for pair in pairs {
let level = if invert_level { !pair.level } else { pair.level };
let duration_us = pair.duration_us;
for decoder in &mut self.decoders {
let freq_supported = decoder
.supported_frequencies()
.iter()
.any(|&f| {
let diff = if f > frequency { f - frequency } else { frequency - f };
diff < (f / 50) // 2% tolerance
});
if !freq_supported {
continue;
}
if let Some(decoded) = decoder.feed(level, duration_us) {
return Some((decoder.name().to_string(), decoded));
}
}
}
None
}
/// Try to decode a capture (for compatibility with old interface)
#[allow(dead_code)]
pub fn try_decode(&mut self, capture: &Capture) -> Option<(String, DecodedSignal)> {
// Convert raw pairs to LevelDuration and process
if capture.raw_pairs.is_empty() {
return None;
}
let pairs: Vec<LevelDuration> = capture.raw_pairs
.iter()
.map(|p| LevelDuration::new(p.level, p.duration_us))
.collect();
self.process_signal(&pairs, capture.frequency)
}
/// Get a decoder by name
pub fn get(&self, name: &str) -> Option<&dyn ProtocolDecoder> {
self.decoders
.iter()
.find(|d| d.name().eq_ignore_ascii_case(name))
.map(|d| d.as_ref())
}
/// List all protocol names
#[allow(dead_code)]
pub fn list_protocols(&self) -> Vec<&'static str> {
self.decoders.iter().map(|d| d.name()).collect()
}
}
impl Default for ProtocolRegistry {
fn default() -> Self {
Self::new()
}
}
/// Helper macro for duration comparison (matches protopirate's DURATION_DIFF)
#[macro_export]
macro_rules! duration_diff {
($actual:expr, $expected:expr) => {
if $actual > $expected {
$actual - $expected
} else {
$expected - $actual
}
};
}
#[cfg(test)]
mod tests {
use super::*;
use crate::export::flipper::import_sub_raw;
use crate::radio::LevelDuration;
use std::path::Path;
#[test]
fn ford_v0_decodes_imports_ford_unlock_sub() {
let path = Path::new("IMPORTS/FORD/3_unlock_ford.sub");
if !path.exists() {
eprintln!("Skip: {:?} not found (run from crate root)", path);
return;
}
let (freq, raw_pairs) = import_sub_raw(path).unwrap();
let pairs: Vec<LevelDuration> = raw_pairs
.iter()
.map(|p| LevelDuration::new(p.level, p.duration_us))
.collect();
let mut reg = ProtocolRegistry::new();
let results = reg.process_signal_stream(&pairs, freq);
let ford_decodes: Vec<_> = results.iter().filter(|(name, _, _)| *name == "Ford V0").collect();
assert!(
!ford_decodes.is_empty(),
"expected at least one Ford V0 decode from 3_unlock_ford.sub, got: {:?}",
results.iter().map(|(n, _, _)| n.as_str()).collect::<Vec<_>>()
);
}
}
+10 -8
View File
@@ -899,14 +899,14 @@ impl ProtocolDecoder for VagDecoder {
TE_SHORT_12 - duration
};
// Reference: (300-duration)<=79 or (duration-300)<80 -> count pair (check_preamble1_prev)
if te_diff < TE_DELTA_12 {
// Check previous pulse
let prev_diff = if self.te_last > TE_SHORT_12 {
self.te_last - TE_SHORT_12
} else {
TE_SHORT_12 - self.te_last
};
if prev_diff <= TE_DELTA_12 {
if prev_diff <= REF_RESET_DELTA {
self.te_last = duration;
self.header_count += 1;
return None;
@@ -915,12 +915,13 @@ impl ProtocolDecoder for VagDecoder {
return None;
}
// Check for gap (end of preamble): 600µs ±79, te_last 300±79 (ref check_gap1)
// Duration not near 300: ref checks for 600µs gap (Preamble1->Data1), then reset
// ref: set step=Reset; if header_count>=201 then duration=|duration-600|; if duration<=79 and te_last 300±79 -> Data1
if self.header_count >= 201 {
let gap_diff = if duration > TE_LONG_12 {
duration - TE_LONG_12
} else {
let gap_diff = if duration < TE_LONG_12 {
TE_LONG_12 - duration
} else {
duration - TE_LONG_12
};
if gap_diff <= REF_GAP1_DELTA {
let prev_diff = if self.te_last > TE_SHORT_12 {
@@ -952,9 +953,10 @@ impl ProtocolDecoder for VagDecoder {
TE_LONG_12 - duration
};
let event = if short_diff <= TE_DELTA_12 {
// Reference Data1: short 300±79 (221..380), long 600±79 (521..680)
let event = if short_diff <= REF_RESET_DELTA {
Some(if level { ManchesterEvent::ShortLow } else { ManchesterEvent::ShortHigh })
} else if long_diff <= TE_DELTA_12 {
} else if long_diff <= REF_RESET_DELTA {
Some(if level { ManchesterEvent::LongLow } else { ManchesterEvent::LongHigh })
} else {
None