This commit is contained in:
leviathan
2026-02-13 22:00:42 -05:00
parent b306ff0574
commit 655f019469
35 changed files with 768 additions and 52 deletions
+44 -2
View File
@@ -5,7 +5,7 @@ use std::sync::mpsc::{self, Receiver, Sender};
use crate::capture::{ButtonCommand, Capture};
use crate::protocols::ProtocolRegistry;
use crate::radio::HackRfController;
use crate::radio::{HackRfController, LevelDuration};
use crate::storage::Storage;
/// Input mode for the application
@@ -47,6 +47,7 @@ pub enum ExportFormat {
/// Items available in the signal action menu
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SignalAction {
Replay,
Lock,
Unlock,
Trunk,
@@ -57,7 +58,8 @@ pub enum SignalAction {
}
impl SignalAction {
pub const ALL: [SignalAction; 7] = [
pub const ALL: [SignalAction; 8] = [
SignalAction::Replay,
SignalAction::Lock,
SignalAction::Unlock,
SignalAction::Trunk,
@@ -69,6 +71,7 @@ impl SignalAction {
pub fn label(&self) -> &'static str {
match self {
SignalAction::Replay => "Replay",
SignalAction::Lock => "TX Lock",
SignalAction::Unlock => "TX Unlock",
SignalAction::Trunk => "TX Trunk",
@@ -632,6 +635,7 @@ impl App {
data: capture.data,
data_count_bit: capture.data_count_bit,
encoder_capable: true,
extra: capture.data_extra,
};
// Generate the signal with the new button
@@ -655,6 +659,37 @@ impl App {
Ok(())
}
/// Replay a capture by re-transmitting its raw level/duration pairs (no re-encoding).
pub fn replay_capture(&mut self, id: u32) -> Result<()> {
let capture = match self.captures.iter().find(|c| c.id == id) {
Some(c) => c,
None => {
self.last_error = Some(format!("Capture {} not found", id));
return Ok(());
}
};
if capture.raw_pairs.is_empty() {
self.last_error = Some("No raw signal to replay (capture has no level/duration data)".to_string());
return Ok(());
}
let signal: Vec<LevelDuration> = capture
.raw_pairs
.iter()
.map(|p| LevelDuration::new(p.level, p.duration_us))
.collect();
if let Some(ref mut hackrf) = self.hackrf {
hackrf.transmit(&signal, capture.frequency)?;
self.status_message = Some(format!("Replayed capture {} ({} pairs)", id, signal.len()));
} else {
self.last_error = Some("HackRF not connected".to_string());
}
Ok(())
}
/// Delete the currently selected capture (if any). No-op if none selected or list empty.
pub fn delete_selected_capture(&mut self) -> Result<()> {
let id = match self.selected_capture {
@@ -735,6 +770,7 @@ impl App {
capture.crc_valid = decoded.crc_valid;
capture.data = decoded.data;
capture.data_count_bit = decoded.data_count_bit;
capture.data_extra = decoded.extra;
capture.status = if decoded.encoder_capable {
crate::capture::CaptureStatus::EncoderCapable
} else {
@@ -785,6 +821,9 @@ impl App {
};
match action {
SignalAction::Replay => {
self.replay_capture(capture_id)?;
}
SignalAction::Lock => {
let id_str = capture_id.to_string();
self.transmit_command(Some(&&*id_str.as_str()), ButtonCommand::Lock)?;
@@ -942,6 +981,7 @@ impl App {
capture.crc_valid = decoded.crc_valid;
capture.data = decoded.data;
capture.data_count_bit = decoded.data_count_bit;
capture.data_extra = decoded.extra;
capture.status = if decoded.encoder_capable {
crate::capture::CaptureStatus::EncoderCapable
} else {
@@ -978,6 +1018,7 @@ impl App {
capture.crc_valid = decoded.crc_valid;
capture.data = decoded.data;
capture.data_count_bit = decoded.data_count_bit;
capture.data_extra = decoded.extra;
capture.status = if decoded.encoder_capable {
crate::capture::CaptureStatus::EncoderCapable
} else {
@@ -1149,6 +1190,7 @@ impl App {
crc_valid: true,
data: 0x5A2B3C4D00001234,
data_count_bit: 64,
data_extra: None,
raw_pairs: vec![],
status: crate::capture::CaptureStatus::EncoderCapable,
received_rf: None,
+4
View File
@@ -54,6 +54,9 @@ pub struct Capture {
pub data: u64,
/// Number of valid bits in data
pub data_count_bit: usize,
/// Protocol-specific extra for encoding (e.g. VAG vag_type + key_idx)
#[serde(default)]
pub data_extra: Option<u64>,
/// Raw level+duration pairs
pub raw_pairs: Vec<StoredLevelDuration>,
/// Current status
@@ -131,6 +134,7 @@ impl Capture {
crc_valid: false,
data: 0,
data_count_bit: 0,
data_extra: None,
raw_pairs: pairs,
status: CaptureStatus::Unknown,
received_rf,
+2
View File
@@ -298,6 +298,7 @@ fn import_fob_v2(fob: &FobFile, next_id: u32) -> Result<Capture> {
crc_valid: sig.crc_valid,
data,
data_count_bit: sig.data_bits,
data_extra: None,
raw_pairs,
status,
received_rf: None,
@@ -363,6 +364,7 @@ fn import_fob_v1(fob: &FobFileV1, next_id: u32) -> Result<Capture> {
crc_valid: cap.crc_valid,
data,
data_count_bit: cap.data_bits,
data_extra: None,
raw_pairs,
status,
received_rf: None,
+3
View File
@@ -30,6 +30,8 @@ pub struct DecodedSignal {
pub data_count_bit: usize,
/// Whether encoding is supported
pub encoder_capable: bool,
/// Protocol-specific extra data for encoding (e.g. VAG: vag_type + key_idx)
pub extra: Option<u64>,
}
impl DecodedSignal {
@@ -43,6 +45,7 @@ impl DecodedSignal {
data,
data_count_bit: bit_count,
encoder_capable: false,
extra: None,
}
}
}
+1
View File
@@ -116,6 +116,7 @@ impl FiatV0Decoder {
data,
data_count_bit: 71,
encoder_capable: true,
extra: None,
}
}
}
+1
View File
@@ -608,6 +608,7 @@ impl ProtocolDecoder for FordV0Decoder {
data: self.key1,
data_count_bit: MIN_COUNT_BIT,
encoder_capable: true,
extra: None,
};
self.data_low = 0;
+1
View File
@@ -84,6 +84,7 @@ impl KiaV0Decoder {
data,
data_count_bit: MIN_COUNT_BIT,
encoder_capable: true,
extra: None,
}
}
}
+1
View File
@@ -133,6 +133,7 @@ impl KiaV1Decoder {
data,
data_count_bit: MIN_COUNT_BIT,
encoder_capable: true,
extra: None,
}
}
}
+1
View File
@@ -128,6 +128,7 @@ impl KiaV2Decoder {
data,
data_count_bit: MIN_COUNT_BIT,
encoder_capable: true,
extra: None,
}
}
}
+1
View File
@@ -154,6 +154,7 @@ impl KiaV3V4Decoder {
data: key_data,
data_count_bit: MIN_COUNT_BIT,
encoder_capable: true,
extra: None,
})
}
}
+1
View File
@@ -198,6 +198,7 @@ impl KiaV5Decoder {
data: key,
data_count_bit: MIN_COUNT_BIT,
encoder_capable: false, // V5 is decode-only
extra: None,
})
}
}
+1
View File
@@ -538,6 +538,7 @@ impl ProtocolDecoder for KiaV6Decoder {
data: key_data,
data_count_bit: MIN_COUNT_BIT,
encoder_capable: false,
extra: None,
});
}
+2
View File
@@ -262,6 +262,7 @@ impl PsaDecoder {
data,
data_count_bit: MIN_COUNT_BIT,
encoder_capable: true,
extra: None,
}
} else {
DecodedSignal {
@@ -272,6 +273,7 @@ impl PsaDecoder {
data,
data_count_bit: MIN_COUNT_BIT,
encoder_capable: false,
extra: None,
}
}
}
+1
View File
@@ -70,6 +70,7 @@ impl ScherKhanDecoder {
data,
data_count_bit: bit_count,
encoder_capable: false,
extra: None,
}
}
}
+1
View File
@@ -101,6 +101,7 @@ impl StarLineDecoder {
data,
data_count_bit: MIN_COUNT_BIT,
encoder_capable: true,
extra: None,
}
}
}
+1
View File
@@ -150,6 +150,7 @@ impl SubaruDecoder {
data: key,
data_count_bit: 64,
encoder_capable: true,
extra: None,
})
}
}
+1
View File
@@ -71,6 +71,7 @@ impl SuzukiDecoder {
data,
data_count_bit: MIN_COUNT_BIT,
encoder_capable: true,
extra: None,
}
}
}
+70 -45
View File
@@ -451,41 +451,54 @@ impl VagDecoder {
}
}
/// Build encoder output for the given signal
/// Build encoder output from decoded signal (uses decoded + extra; extra = vag_type | (key_idx<<8))
fn encode_signal(&self, decoded: &DecodedSignal) -> Option<Vec<LevelDuration>> {
if !self.decrypted {
return None;
}
let extra = match decoded.extra {
Some(e) => e,
None => return None,
};
let vag_type_num = (extra & 0xFF) as u8;
let vag_type = match vag_type_num {
1 => VagType::Type1,
2 => VagType::Type2,
3 => VagType::Type3,
4 => VagType::Type4,
_ => return None,
};
let key_idx = ((extra >> 8) & 0xFF) as u8;
match self.vag_type {
VagType::Type1 => self.encode_type1(decoded),
VagType::Type2 => self.encode_type2(decoded),
VagType::Type3 | VagType::Type4 => self.encode_type3_4(decoded),
match vag_type {
VagType::Type1 => Self::encode_type1(decoded, key_idx),
VagType::Type2 => Self::encode_type2(decoded),
VagType::Type3 | VagType::Type4 => Self::encode_type3_4(decoded, vag_type, key_idx),
_ => None,
}
}
/// Encode Type 1 (300µs, AUT64)
fn encode_type1(&self, _decoded: &DecodedSignal) -> Option<Vec<LevelDuration>> {
fn encode_type1(decoded: &DecodedSignal, key_idx: u8) -> Option<Vec<LevelDuration>> {
let mut upload = Vec::with_capacity(700);
let btn_byte = self.btn;
let serial = decoded.serial.unwrap_or(0);
let btn = decoded.button.unwrap_or(0);
let cnt = decoded.counter.unwrap_or(0) as u32;
let type_byte = (decoded.data >> 56) as u8;
let btn_byte = btn;
let dispatch = Self::get_dispatch_byte(btn_byte, 1);
let type_byte = (self.key1_high >> 24) as u8;
// Build plaintext block
let mut block = [0u8; 8];
block[0] = (self.serial >> 24) as u8;
block[1] = (self.serial >> 16) as u8;
block[2] = (self.serial >> 8) as u8;
block[3] = self.serial as u8;
block[4] = self.cnt as u8;
block[5] = (self.cnt >> 8) as u8;
block[6] = (self.cnt >> 16) as u8;
block[0] = (serial >> 24) as u8;
block[1] = (serial >> 16) as u8;
block[2] = (serial >> 8) as u8;
block[3] = serial as u8;
block[4] = cnt as u8;
block[5] = (cnt >> 8) as u8;
block[6] = (cnt >> 16) as u8;
block[7] = btn_byte;
// Encrypt with AUT64
let key_idx = if self.key_idx != 0xFF { self.key_idx as usize } else { 0 };
let key_idx = if key_idx != 0xFF { key_idx as usize } else { 0 };
let store = keys::get_keystore();
if let Some(key) = store.get_vag_key((key_idx + 1) as u8) {
aut64::aut64_encrypt(key, &mut block);
@@ -533,22 +546,25 @@ impl VagDecoder {
}
/// Encode Type 2 (300µs, TEA)
fn encode_type2(&self, _decoded: &DecodedSignal) -> Option<Vec<LevelDuration>> {
fn encode_type2(decoded: &DecodedSignal) -> Option<Vec<LevelDuration>> {
let mut upload = Vec::with_capacity(700);
let btn_byte = Self::btn_to_byte(self.btn, 2);
let serial = decoded.serial.unwrap_or(0);
let btn = decoded.button.unwrap_or(0);
let cnt = decoded.counter.unwrap_or(0) as u32;
let type_byte = (decoded.data >> 56) as u8;
let btn_byte = Self::btn_to_byte(btn, 2);
let dispatch = Self::get_dispatch_byte(btn_byte, 2);
let type_byte = (self.key1_high >> 24) as u8;
// Build plaintext block
let mut block = [0u8; 8];
block[0] = (self.serial >> 24) as u8;
block[1] = (self.serial >> 16) as u8;
block[2] = (self.serial >> 8) as u8;
block[3] = self.serial as u8;
block[4] = self.cnt as u8;
block[5] = (self.cnt >> 8) as u8;
block[6] = (self.cnt >> 16) as u8;
block[0] = (serial >> 24) as u8;
block[1] = (serial >> 16) as u8;
block[2] = (serial >> 8) as u8;
block[3] = serial as u8;
block[4] = cnt as u8;
block[5] = (cnt >> 8) as u8;
block[6] = (cnt >> 16) as u8;
block[7] = btn_byte;
// Encrypt with TEA
@@ -606,27 +622,30 @@ impl VagDecoder {
}
/// Encode Type 3/4 (500µs, AUT64)
fn encode_type3_4(&self, _decoded: &DecodedSignal) -> Option<Vec<LevelDuration>> {
fn encode_type3_4(decoded: &DecodedSignal, vag_type: VagType, key_idx: u8) -> Option<Vec<LevelDuration>> {
let mut upload = Vec::with_capacity(600);
let vag_type_num = self.vag_type as u8;
let vag_type_num = vag_type as u8;
let btn_byte = Self::btn_to_byte(self.btn, vag_type_num);
let serial = decoded.serial.unwrap_or(0);
let btn = decoded.button.unwrap_or(0);
let cnt = decoded.counter.unwrap_or(0) as u32;
let type_byte = (decoded.data >> 56) as u8;
let btn_byte = Self::btn_to_byte(btn, vag_type_num);
let dispatch = Self::get_dispatch_byte(btn_byte, vag_type_num);
let type_byte = (self.key1_high >> 24) as u8;
let mut block = [0u8; 8];
block[0] = (self.serial >> 24) as u8;
block[1] = (self.serial >> 16) as u8;
block[2] = (self.serial >> 8) as u8;
block[3] = self.serial as u8;
block[4] = self.cnt as u8;
block[5] = (self.cnt >> 8) as u8;
block[6] = (self.cnt >> 16) as u8;
block[0] = (serial >> 24) as u8;
block[1] = (serial >> 16) as u8;
block[2] = (serial >> 8) as u8;
block[3] = serial as u8;
block[4] = cnt as u8;
block[5] = (cnt >> 8) as u8;
block[6] = (cnt >> 16) as u8;
block[7] = btn_byte;
let key_idx = if self.key_idx != 0xFF {
self.key_idx as usize
} else if self.vag_type == VagType::Type4 { 2 } else { 1 };
let key_idx = if key_idx != 0xFF {
key_idx as usize
} else if vag_type == VagType::Type4 { 2 } else { 1 };
let store = keys::get_keystore();
if let Some(key) = store.get_vag_key((key_idx + 1) as u8) {
@@ -758,9 +777,14 @@ impl VagDecoder {
}
}
/// Build DecodedSignal from internal state
/// Build DecodedSignal from internal state (sets extra when decrypted for encode-from-capture)
fn build_decoded_signal(&self) -> DecodedSignal {
let key1 = ((self.key1_high as u64) << 32) | (self.key1_low as u64);
let extra = if self.decrypted {
Some((self.vag_type as u8 as u64) | ((self.key_idx as u64) << 8))
} else {
None
};
DecodedSignal {
serial: if self.decrypted { Some(self.serial) } else { None },
@@ -770,6 +794,7 @@ impl VagDecoder {
data: key1,
data_count_bit: self.data_count_bit,
encoder_capable: self.decrypted,
extra,
}
}
}
@@ -1160,7 +1185,7 @@ impl ProtocolDecoder for VagDecoder {
}
fn supports_encoding(&self) -> bool {
self.decrypted
true
}
fn encode(&self, decoded: &DecodedSignal, _button: u8) -> Option<Vec<LevelDuration>> {