mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-08-16 12:53:25 +00:00
539 lines
16 KiB
C++
539 lines
16 KiB
C++
/*
|
|
* Copyright (C) 2026 HTotoo
|
|
*
|
|
* 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_subcar.hpp"
|
|
#include "audio.hpp"
|
|
#include "baseband_api.hpp"
|
|
#include "string_format.hpp"
|
|
#include "file_path.hpp"
|
|
#include "portapack_persistent_memory.hpp"
|
|
|
|
using namespace portapack;
|
|
using namespace ui;
|
|
|
|
namespace ui::external_app::subcarrx {
|
|
|
|
std::string SubCarRecentEntry::to_csv() {
|
|
std::string csv = ";";
|
|
csv += SubCarView::getSensorTypeName((FPROTO_SUBCAR_SENSOR)sensorType);
|
|
csv += ";" + to_string_dec_uint(bits) + ";";
|
|
csv += to_string_hex(data, 64 / 4) + ";" + to_string_hex(data2, 64 / 4);
|
|
return csv;
|
|
}
|
|
|
|
void SubCarLogger::log_data(SubCarRecentEntry& data) {
|
|
log_file.write_entry(data.to_csv());
|
|
}
|
|
|
|
void SubCarRecentEntryDetailView::update_data() {
|
|
// process protocol data
|
|
parseProtocol();
|
|
// set text elements
|
|
text_type.set(SubCarView::getSensorTypeName((FPROTO_SUBCAR_SENSOR)entry_.sensorType));
|
|
|
|
text_id.set("0x" + to_string_hex(serial));
|
|
if (entry_.bits > 0) console.writeln("Bits: " + to_string_dec_uint(entry_.bits));
|
|
if (!btn.empty()) console.writeln("Btn: " + btn);
|
|
if (cnt != SD_NO_CNT) console.writeln("Cnt: " + to_string_dec_uint(cnt));
|
|
if (entry_.data != 0) console.writeln("Data : " + to_string_hex(entry_.data));
|
|
if (entry_.data2 != 0) console.writeln("Data2: " + to_string_hex(entry_.data2));
|
|
}
|
|
|
|
SubCarRecentEntryDetailView::SubCarRecentEntryDetailView(NavigationView& nav, const SubCarRecentEntry& entry)
|
|
: nav_{nav},
|
|
entry_{entry} {
|
|
add_children({&button_done,
|
|
&text_type,
|
|
&text_id,
|
|
&console,
|
|
&labels});
|
|
|
|
button_done.on_select = [&nav](const ui::Button&) {
|
|
nav.pop();
|
|
};
|
|
update_data();
|
|
}
|
|
|
|
void SubCarRecentEntryDetailView::focus() {
|
|
button_done.focus();
|
|
}
|
|
|
|
void SubCarView::focus() {
|
|
field_frequency.focus();
|
|
}
|
|
|
|
SubCarView::SubCarView(NavigationView& nav)
|
|
: nav_{nav} {
|
|
add_children({&rssi,
|
|
&channel,
|
|
&field_rf_amp,
|
|
&field_lna,
|
|
&field_vga,
|
|
&field_frequency,
|
|
&button_clear_list,
|
|
&check_log,
|
|
&labels,
|
|
&options_mode,
|
|
&recent_entries_view});
|
|
|
|
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
|
|
logger = std::make_unique<SubCarLogger>();
|
|
|
|
button_clear_list.on_select = [this](Button&) {
|
|
recent.clear();
|
|
recent_entries_view.set_dirty();
|
|
};
|
|
field_frequency.set_step(10000);
|
|
check_log.on_select = [this](Checkbox&, bool v) {
|
|
logging = v;
|
|
if (logger && logging) {
|
|
logger->append(logs_dir.string() + "/SubCarLOG_" + to_string_timestamp(rtc_time::now()) + ".CSV");
|
|
logger->write_header();
|
|
}
|
|
};
|
|
check_log.set_value(logging);
|
|
const Rect content_rect{0, header_height, screen_width, screen_height - header_height};
|
|
recent_entries_view.set_parent_rect(content_rect);
|
|
recent_entries_view.on_select = [this](const SubCarRecentEntry& entry) {
|
|
nav_.push<SubCarRecentEntryDetailView>(entry);
|
|
};
|
|
|
|
options_mode.on_change = [this](size_t, int32_t v) {
|
|
modulation = v;
|
|
chThdSleepMilliseconds(100); // wait for the baseband thread to process the previous config, to avoid glitchy output when switching modes
|
|
baseband::set_subghzd_config(modulation, receiver_model.sampling_rate());
|
|
};
|
|
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
|
|
on_tick_second();
|
|
};
|
|
options_mode.set_selected_index(modulation, true);
|
|
receiver_model.enable();
|
|
}
|
|
|
|
void SubCarView::on_tick_second() {
|
|
for (auto& entry : recent) {
|
|
entry.inc_age(1);
|
|
}
|
|
recent_entries_view.set_dirty();
|
|
}
|
|
|
|
void SubCarView::on_data(const SubCarDataMessage* data) {
|
|
SubCarRecentEntry key{data->sensorType, data->data, data->data2, data->bits};
|
|
if (logger && logging) {
|
|
logger->log_data(key);
|
|
}
|
|
auto matching_recent = find(recent, key.key());
|
|
if (matching_recent != std::end(recent)) {
|
|
// Found within. Move to front of list, increment counter.
|
|
(*matching_recent).reset_age();
|
|
recent.push_front(*matching_recent);
|
|
recent.erase(matching_recent);
|
|
} else {
|
|
recent.emplace_front(key);
|
|
truncate_entries(recent, 64);
|
|
}
|
|
recent_entries_view.set_dirty();
|
|
}
|
|
|
|
SubCarView::~SubCarView() {
|
|
rtc_time::signal_tick_second -= signal_token_tick_second;
|
|
receiver_model.disable();
|
|
baseband::shutdown();
|
|
}
|
|
|
|
const char* SubCarView::getSensorTypeName(FPROTO_SUBCAR_SENSOR type) {
|
|
switch (type) {
|
|
case FPC_SUZUKI:
|
|
return "Suzuki";
|
|
case FPC_VW:
|
|
return "VW";
|
|
case FPC_SUBARU:
|
|
return "Subaru";
|
|
case FPC_KIAV5:
|
|
return "Kia V5";
|
|
case FPC_KIAV3V4:
|
|
return "Kia V3/V4";
|
|
case FPC_KIAV2:
|
|
return "Kia V2";
|
|
case FPC_KIAV1:
|
|
return "Kia V1";
|
|
case FPC_KIAV0:
|
|
return "Kia V0";
|
|
case FPC_FORDV0:
|
|
return "Ford V0";
|
|
case FPC_FIATV0:
|
|
return "Fiat V0";
|
|
case FPC_BMWV0:
|
|
return "BMW V0";
|
|
/* case FPC_KIAV6:
|
|
return "Kia V6";*/
|
|
|
|
case FPC_Invalid:
|
|
default:
|
|
return "Unknown";
|
|
}
|
|
}
|
|
|
|
std::string SubCarView::pad_string_with_spaces(int snakes) {
|
|
std::string paddedStr(snakes, ' ');
|
|
return paddedStr;
|
|
}
|
|
|
|
void SubCarView::on_freqchg(int64_t freq) {
|
|
field_frequency.set_value(freq);
|
|
}
|
|
|
|
void subaru_decode_count(const uint8_t* KB, uint16_t* count) {
|
|
uint8_t lo = 0;
|
|
if ((KB[4] & 0x40) == 0)
|
|
lo |= 0x01;
|
|
if ((KB[4] & 0x80) == 0)
|
|
lo |= 0x02;
|
|
if ((KB[5] & 0x01) == 0)
|
|
lo |= 0x04;
|
|
if ((KB[5] & 0x02) == 0)
|
|
lo |= 0x08;
|
|
if ((KB[6] & 0x01) == 0)
|
|
lo |= 0x10;
|
|
if ((KB[6] & 0x02) == 0)
|
|
lo |= 0x20;
|
|
if ((KB[5] & 0x40) == 0)
|
|
lo |= 0x40;
|
|
if ((KB[5] & 0x80) == 0)
|
|
lo |= 0x80;
|
|
|
|
uint8_t REG_SH1 = (KB[7] << 4) & 0xF0;
|
|
if (KB[5] & 0x04)
|
|
REG_SH1 |= 0x04;
|
|
if (KB[5] & 0x08)
|
|
REG_SH1 |= 0x08;
|
|
if (KB[6] & 0x80)
|
|
REG_SH1 |= 0x02;
|
|
if (KB[6] & 0x40)
|
|
REG_SH1 |= 0x01;
|
|
|
|
uint8_t REG_SH2 = ((KB[6] << 2) & 0xF0) | ((KB[7] >> 4) & 0x0F);
|
|
|
|
uint8_t SER0 = KB[3];
|
|
uint8_t SER1 = KB[1];
|
|
uint8_t SER2 = KB[2];
|
|
|
|
uint8_t total_rot = 4 + lo;
|
|
for (uint8_t i = 0; i < total_rot; ++i) {
|
|
uint8_t t_bit = (SER0 >> 7) & 1;
|
|
SER0 = ((SER0 << 1) & 0xFE) | ((SER1 >> 7) & 1);
|
|
SER1 = ((SER1 << 1) & 0xFE) | ((SER2 >> 7) & 1);
|
|
SER2 = ((SER2 << 1) & 0xFE) | t_bit;
|
|
}
|
|
|
|
uint8_t T1 = SER1 ^ REG_SH1;
|
|
uint8_t T2 = SER2 ^ REG_SH2;
|
|
|
|
uint8_t hi = 0;
|
|
if ((T1 & 0x10) == 0)
|
|
hi |= 0x04;
|
|
if ((T1 & 0x20) == 0)
|
|
hi |= 0x08;
|
|
if ((T2 & 0x80) == 0)
|
|
hi |= 0x02;
|
|
if ((T2 & 0x40) == 0)
|
|
hi |= 0x01;
|
|
if ((T1 & 0x01) == 0)
|
|
hi |= 0x40;
|
|
if ((T1 & 0x02) == 0)
|
|
hi |= 0x80;
|
|
if ((T2 & 0x08) == 0)
|
|
hi |= 0x20;
|
|
if ((T2 & 0x04) == 0)
|
|
hi |= 0x10;
|
|
*count = ((hi << 8) | lo) & 0xFFFF;
|
|
}
|
|
|
|
void SubCarRecentEntryDetailView::parseProtocol() {
|
|
btn = "";
|
|
cnt = SD_NO_CNT;
|
|
serial = 0;
|
|
|
|
if (entry_.sensorType == FPC_Invalid) return;
|
|
|
|
if (entry_.sensorType == FPC_SUZUKI) {
|
|
uint32_t data_high = (uint32_t)(entry_.data >> 32);
|
|
uint32_t data_low = (uint32_t)entry_.data;
|
|
serial = ((data_high & 0xFFF) << 16) | (data_low >> 16);
|
|
uint8_t buttonid = (data_low >> 12) & 0xF;
|
|
cnt = (data_high << 4) >> 16;
|
|
btn = to_string_dec_uint(buttonid);
|
|
return;
|
|
}
|
|
if (entry_.sensorType == FPC_VW) {
|
|
// uint32_t key_high = (entry_.data >> 32) & 0xFFFFFFFF;
|
|
uint32_t key_low = entry_.data & 0xFFFFFFFF;
|
|
serial = key_low; // trimmed to 32 bits for VW
|
|
uint8_t check = entry_.data2 & 0xFF;
|
|
uint8_t btnid = (check >> 4) & 0xF;
|
|
switch (btnid) {
|
|
case 0x1:
|
|
btn = "UNLOCK";
|
|
break;
|
|
case 0x2:
|
|
btn = "LOCK";
|
|
break;
|
|
case 0x3:
|
|
btn = "Un+Lk";
|
|
break;
|
|
case 0x4:
|
|
btn = "TRUNK";
|
|
break;
|
|
case 0x5:
|
|
btn = "Un+Tr";
|
|
break;
|
|
case 0x6:
|
|
btn = "Lk+Tr";
|
|
break;
|
|
case 0x7:
|
|
btn = "Un+Lk+Tr";
|
|
break;
|
|
case 0x8:
|
|
btn = "PANIC";
|
|
break;
|
|
default:
|
|
btn = "Unknown";
|
|
break;
|
|
}
|
|
}
|
|
if (entry_.sensorType == FPC_SUBARU) {
|
|
uint8_t* data_bytes = (uint8_t*)entry_.data;
|
|
serial = ((uint32_t)data_bytes[1] << 16) | ((uint32_t)data_bytes[2] << 8) | data_bytes[3];
|
|
uint8_t button = data_bytes[0] & 0x0F;
|
|
btn = to_string_dec_uint(button);
|
|
uint16_t cnttmp = 0;
|
|
subaru_decode_count(data_bytes, &cnttmp);
|
|
cnt = cnttmp;
|
|
}
|
|
|
|
if (entry_.sensorType == FPC_KIAV5) {
|
|
uint64_t yek = 0;
|
|
for (int i = 0; i < 8; i++) {
|
|
uint8_t byte = (entry_.data2 >> (i * 8)) & 0xFF;
|
|
uint8_t reversed = 0;
|
|
for (int b = 0; b < 8; b++) {
|
|
if (byte & (1 << b))
|
|
reversed |= (1 << (7 - b));
|
|
}
|
|
yek |= ((uint64_t)reversed << ((7 - i) * 8));
|
|
}
|
|
serial = (uint32_t)((yek >> 32) & 0x0FFFFFFF);
|
|
uint8_t button = (uint8_t)((yek >> 60) & 0x0F);
|
|
uint32_t encrypted = (uint32_t)(yek & 0xFFFFFFFF);
|
|
btn = to_string_dec_uint(button);
|
|
// decode
|
|
uint8_t keystore_bytes[] = {0x53, 0x54, 0x46, 0x52, 0x4b, 0x45, 0x30, 0x30};
|
|
uint8_t s0 = (encrypted & 0xFF);
|
|
uint8_t s1 = (encrypted >> 8) & 0xFF;
|
|
uint8_t s2 = (encrypted >> 16) & 0xFF;
|
|
uint8_t s3 = (encrypted >> 24) & 0xFF;
|
|
int round_index = 1;
|
|
for (size_t i = 0; i < 18; i++) {
|
|
uint8_t r = keystore_bytes[round_index] & 0xFF;
|
|
int steps = 8;
|
|
while (steps > 0) {
|
|
uint8_t base;
|
|
if ((s3 & 0x40) == 0) {
|
|
base = (s3 & 0x02) == 0 ? 0x74 : 0x2E;
|
|
} else {
|
|
base = (s3 & 0x02) == 0 ? 0x3A : 0x5C;
|
|
}
|
|
|
|
if (s2 & 0x08) {
|
|
base = (((base >> 4) & 0x0F) | ((base & 0x0F) << 4)) & 0xFF;
|
|
}
|
|
if (s1 & 0x01) {
|
|
base = ((base & 0x3F) << 2) & 0xFF;
|
|
}
|
|
if (s0 & 0x01) {
|
|
base = (base << 1) & 0xFF;
|
|
}
|
|
|
|
uint8_t temp = (s3 ^ s1) & 0xFF;
|
|
s3 = ((s3 & 0x7F) << 1) & 0xFF;
|
|
if (s2 & 0x80) {
|
|
s3 |= 0x01;
|
|
}
|
|
s2 = ((s2 & 0x7F) << 1) & 0xFF;
|
|
if (s1 & 0x80) {
|
|
s2 |= 0x01;
|
|
}
|
|
s1 = ((s1 & 0x7F) << 1) & 0xFF;
|
|
if (s0 & 0x80) {
|
|
s1 |= 0x01;
|
|
}
|
|
s0 = ((s0 & 0x7F) << 1) & 0xFF;
|
|
|
|
uint8_t chk = (base ^ (r ^ temp)) & 0xFF;
|
|
if (chk & 0x80) {
|
|
s0 |= 0x01;
|
|
}
|
|
r = ((r & 0x7F) << 1) & 0xFF;
|
|
steps--;
|
|
}
|
|
round_index = (round_index - 1) & 0x7;
|
|
}
|
|
cnt = (s0 + (s1 << 8)) & 0xFFFF;
|
|
}
|
|
|
|
if (entry_.sensorType == FPC_KIAV3V4) {
|
|
// not decrypted!
|
|
serial = (uint32_t)entry_.data;
|
|
// uint8_t button = entry_.data2 & 0xFF;
|
|
btn = "?"; // to_string_dec_uint(button);
|
|
}
|
|
|
|
if (entry_.sensorType == FPC_KIAV2) {
|
|
serial = (uint32_t)((entry_.data >> 20) & 0xFFFFFFFF);
|
|
uint8_t button = (uint8_t)((entry_.data >> 16) & 0x0F);
|
|
uint16_t raw_count = (uint16_t)((entry_.data >> 4) & 0xFFF);
|
|
cnt = ((raw_count >> 4) | (raw_count << 8)) & 0xFFF;
|
|
btn = to_string_dec_uint(button);
|
|
}
|
|
|
|
if (entry_.sensorType == FPC_KIAV1) {
|
|
serial = (uint32_t)((entry_.data >> 24) & 0xFFFFFFFF);
|
|
uint8_t button = (uint8_t)((entry_.data >> 16) & 0xFF);
|
|
cnt = (uint8_t)((entry_.data >> 4) & 0xF) << 8 | ((entry_.data >> 8) & 0xFF);
|
|
btn = to_string_dec_uint(button);
|
|
}
|
|
|
|
if (entry_.sensorType == FPC_KIAV0) {
|
|
serial = (uint32_t)((entry_.data >> 12) & 0x0FFFFFFF);
|
|
uint8_t button = (entry_.data >> 8) & 0x0F;
|
|
cnt = (entry_.data >> 40) & 0xFFFF;
|
|
btn = to_string_dec_uint(button);
|
|
}
|
|
if (entry_.sensorType == FPC_FORDV0) {
|
|
uint8_t buf[13] = {0};
|
|
|
|
for (int i = 0; i < 8; ++i) {
|
|
buf[i] = (uint8_t)(entry_.data >> (56 - i * 8));
|
|
}
|
|
|
|
buf[8] = (uint8_t)(entry_.data2 >> 8);
|
|
buf[9] = (uint8_t)(entry_.data2 & 0xFF);
|
|
uint8_t tmp = buf[8];
|
|
uint8_t parity = 0;
|
|
uint8_t parity_any = (tmp != 0);
|
|
while (tmp) {
|
|
parity ^= (tmp & 1);
|
|
tmp >>= 1;
|
|
}
|
|
buf[11] = parity_any ? parity : 0;
|
|
uint8_t xor_byte;
|
|
uint8_t limit;
|
|
if (buf[11]) {
|
|
xor_byte = buf[7];
|
|
limit = 7;
|
|
} else {
|
|
xor_byte = buf[6];
|
|
limit = 6;
|
|
}
|
|
|
|
for (int idx = 1; idx < limit; ++idx) {
|
|
buf[idx] ^= xor_byte;
|
|
}
|
|
|
|
if (buf[11] == 0) {
|
|
buf[7] ^= xor_byte;
|
|
}
|
|
|
|
uint8_t orig_b7 = buf[7];
|
|
buf[7] = (orig_b7 & 0xAA) | (buf[6] & 0x55);
|
|
uint8_t mixed = (buf[6] & 0xAA) | (orig_b7 & 0x55);
|
|
buf[12] = mixed;
|
|
buf[6] = mixed;
|
|
|
|
uint32_t serial_le = ((uint32_t)buf[1]) |
|
|
((uint32_t)buf[2] << 8) |
|
|
((uint32_t)buf[3] << 16) |
|
|
((uint32_t)buf[4] << 24);
|
|
|
|
serial = ((serial_le & 0xFF) << 24) |
|
|
(((serial_le >> 8) & 0xFF) << 16) |
|
|
(((serial_le >> 16) & 0xFF) << 8) |
|
|
((serial_le >> 24) & 0xFF);
|
|
|
|
uint8_t button = (buf[5] >> 4) & 0x0F;
|
|
|
|
cnt = ((buf[5] & 0x0F) << 16) |
|
|
(buf[6] << 8) |
|
|
buf[7];
|
|
btn = to_string_dec_uint(button);
|
|
}
|
|
|
|
if (entry_.sensorType == FPC_FIATV0) {
|
|
serial = (uint32_t)(entry_.data & 0xFFFFFFFF);
|
|
cnt = (uint32_t)((entry_.data >> 32) & 0xFFFFFFFF);
|
|
uint8_t button = (uint8_t)(entry_.data2 & 0xFF);
|
|
btn = to_string_dec_uint(button);
|
|
}
|
|
|
|
if (entry_.sensorType == FPC_BMWV0) {
|
|
serial = (uint32_t)((entry_.data >> 12) & 0x0FFFFFFF);
|
|
uint8_t button = (entry_.data >> 8) & 0x0F;
|
|
cnt = (entry_.data >> 40) & 0xFFFF;
|
|
btn = to_string_dec_uint(button);
|
|
}
|
|
|
|
/*if (entry_.sensorType == FPC_KIAV6) {
|
|
// not decrypted!
|
|
serial = 0;
|
|
btn = "?";
|
|
cnt = 0;
|
|
}*/
|
|
|
|
return;
|
|
}
|
|
|
|
} // namespace ui::external_app::subcarrx
|
|
|
|
namespace ui {
|
|
|
|
template <>
|
|
void RecentEntriesTable<ui::external_app::subcarrx::SubCarRecentEntries>::draw(
|
|
const Entry& entry,
|
|
const Rect& target_rect,
|
|
Painter& painter,
|
|
const Style& style,
|
|
ui::RecentEntriesColumns& columns) {
|
|
std::string line{};
|
|
line.reserve(30);
|
|
|
|
line = ui::external_app::subcarrx::SubCarView::getSensorTypeName((FPROTO_SUBCAR_SENSOR)entry.sensorType);
|
|
line = line + " " + to_string_hex(entry.data << 32);
|
|
line.resize(columns.at(0).second, ' ');
|
|
std::string ageStr = to_string_dec_uint(entry.age);
|
|
std::string bitsStr = to_string_dec_uint(entry.bits);
|
|
line += ui::external_app::subcarrx::SubCarView::pad_string_with_spaces(5 - bitsStr.length()) + bitsStr;
|
|
line += ui::external_app::subcarrx::SubCarView::pad_string_with_spaces(4 - ageStr.length()) + ageStr;
|
|
|
|
line.resize(target_rect.width() / 8, ' ');
|
|
painter.draw_string(target_rect.location(), style, line);
|
|
}
|
|
|
|
} // namespace ui
|