Files
mayhem-firmware/firmware/application/external/subcarrx/ui_subcar.cpp
T
2026-02-06 17:20:14 +01:00

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