v1.0.0
This commit is contained in:
+44
-2
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +116,7 @@ impl FiatV0Decoder {
|
||||
data,
|
||||
data_count_bit: 71,
|
||||
encoder_capable: true,
|
||||
extra: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -84,6 +84,7 @@ impl KiaV0Decoder {
|
||||
data,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
extra: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +133,7 @@ impl KiaV1Decoder {
|
||||
data,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
extra: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,6 +128,7 @@ impl KiaV2Decoder {
|
||||
data,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
extra: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,6 +154,7 @@ impl KiaV3V4Decoder {
|
||||
data: key_data,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
extra: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,6 +198,7 @@ impl KiaV5Decoder {
|
||||
data: key,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: false, // V5 is decode-only
|
||||
extra: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -538,6 +538,7 @@ impl ProtocolDecoder for KiaV6Decoder {
|
||||
data: key_data,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: false,
|
||||
extra: None,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ impl ScherKhanDecoder {
|
||||
data,
|
||||
data_count_bit: bit_count,
|
||||
encoder_capable: false,
|
||||
extra: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ impl StarLineDecoder {
|
||||
data,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
extra: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +150,7 @@ impl SubaruDecoder {
|
||||
data: key,
|
||||
data_count_bit: 64,
|
||||
encoder_capable: true,
|
||||
extra: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ impl SuzukiDecoder {
|
||||
data,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
extra: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+70
-45
@@ -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>> {
|
||||
|
||||
Reference in New Issue
Block a user