Jammer improvements (#2674)

* Add new jammer modes
Overview:
This PR enhances the PortaPack Jammer app by introducing eight new signal types, ported from my Flipper Zero RF Jammer app (https://github.com/RocketGod-git/flipper-zero-rf-jammer). These modes expand the app's capability to disrupt a wide range of RF communication protocols, from analog radios to modern digital systems. The implementation preserves the original app structure, resolves namespace conflicts, and ensures compatibility with the Mayhem firmware.

New Modes

The following modes have been added to the options_type in ui_jammer.hpp, with corresponding signal generation in proc_jammer.cpp:

Noise: Generates broadband white noise to interfere with analog and digital signals (e.g., Wi-Fi, Bluetooth, key fobs). Highly effective for overwhelming receivers across a frequency range.

Sine: Produces a continuous, unmodulated sine wave to jam narrowband receivers, ideal for analog FM/AM radios or telemetry systems.

Square: Emits a harmonic-rich square wave, disrupting digital protocols (e.g., OOK, ASK) and systems sensitive to sharp transitions, such as remote keyless entry.

Sawtooth (Experimental): Generates a sawtooth wave with a unique harmonic profile, useful for testing interference against PWM-based or niche analog systems.

Triangle (Experimental): Creates a triangle wave with minimal harmonics, suitable for exploratory jamming of narrowband systems or receiver linearity testing.

Chirp: Outputs a rapid frequency-sweeping chirp signal, effective against frequency-hopping and spread-spectrum systems (e.g., some Wi-Fi, Bluetooth, or military radios).

Gauss: Generates Gaussian noise to mimic natural interference, targeting digital systems like GPS or data links by degrading signal-to-noise ratios.

Brute (Experimental): Transmits a constant maximum-amplitude signal to saturate simple receiver front-ends, useful for brute-force jamming of basic analog devices.

* Fixed and made brutal.

This PR introduces user-focused improvements to the Jammer App in the HackRF PortaPack Mayhem Firmware, enhancing usability and flexibility. The changes address specific user requirements for a more intuitive default configuration, continuous waveform support, and dynamic setting adjustments during transmission.
This commit is contained in:
RocketGod 2025-05-31 13:16:57 -07:00 committed by GitHub
parent f90bd44617
commit fd158e873a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 158 additions and 73 deletions

View file

@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2025 RocketGod - Added modes from my Flipper Zero RF Jammer App - https://betaskynet.com
*
* This file is part of PortaPack.
*
@ -36,7 +37,6 @@ void RangeView::focus() {
}
void RangeView::update_start(rf::Frequency f) {
// Change everything except max
frequency_range.min = f;
button_start.set_text(to_string_short_freq(f));
@ -48,7 +48,6 @@ void RangeView::update_start(rf::Frequency f) {
}
void RangeView::update_stop(rf::Frequency f) {
// Change everything except min
frequency_range.max = f;
button_stop.set_text(to_string_short_freq(f));
@ -60,7 +59,6 @@ void RangeView::update_stop(rf::Frequency f) {
}
void RangeView::update_center(rf::Frequency f) {
// Change min/max/center, keep width
center = f;
button_center.set_text(to_string_short_freq(center));
@ -75,7 +73,6 @@ void RangeView::update_center(rf::Frequency f) {
}
void RangeView::update_width(uint32_t w) {
// Change min/max/width, keep center
width = w;
button_width.set_text(to_string_short_freq(width));
@ -91,7 +88,6 @@ void RangeView::update_width(uint32_t w) {
}
void RangeView::paint(Painter&) {
// Draw lines and arrows
Rect r;
Point p;
Coord c;
@ -162,7 +158,7 @@ RangeView::RangeView(NavigationView& nav) {
auto load_view = nav.push<FrequencyLoadView>();
load_view->on_frequency_loaded = [this](rf::Frequency value) {
update_center(value);
update_width(100000); // 100kHz default jamming bandwidth when loading unique frequency
update_width(100000);
};
load_view->on_range_loaded = [this](rf::Frequency start, rf::Frequency stop) {
update_start(start);
@ -193,10 +189,37 @@ void JammerView::set_jammer_channel(uint32_t i, uint32_t width, uint64_t center,
jammer_channels[i].enabled = true;
jammer_channels[i].width = (width * 0xFFFFFFULL) / 1536000;
jammer_channels[i].center = center;
jammer_channels[i].duration = 30720 * duration;
jammer_channels[i].duration = duration ? 30720 * duration : 0xFFFFFFFF;
}
void JammerView::start_tx() {
if (update_config()) {
jamming = true;
button_transmit.set_style(&style_cancel);
button_transmit.set_text("STOP");
transmitter_model.set_rf_amp(field_amp.value());
transmitter_model.set_tx_gain(field_gain.value());
transmitter_model.set_baseband_bandwidth(28'000'000);
transmitter_model.enable();
baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
mscounter = 0;
}
}
void JammerView::stop_tx() {
button_transmit.set_style(&style_val);
button_transmit.set_text("START");
transmitter_model.disable();
baseband::set_jammer(false, JammerType::TYPE_FSK, 0);
jamming = false;
cooling = false;
seconds = 0;
mscounter = 0;
}
bool JammerView::update_config() {
uint32_t c, i = 0;
size_t num_channels;
rf::Frequency start_freq, range_bw, range_bw_sub, ch_width;
@ -204,24 +227,19 @@ void JammerView::start_tx() {
size_t hop_value = options_hop.selected_index_value();
// Disable all channels by default
for (c = 0; c < JAMMER_MAX_CH; c++)
jammer_channels[c].enabled = false;
// Generate jamming channels with JAMMER_MAX_CH maximum width
// Convert ranges min/max to center/bw
for (size_t r = 0; r < 3; r++) {
if (range_views[r]->frequency_range.enabled) {
range_bw = abs(range_views[r]->frequency_range.max - range_views[r]->frequency_range.min);
// Get lower bound
if (range_views[r]->frequency_range.min < range_views[r]->frequency_range.max)
start_freq = range_views[r]->frequency_range.min;
else
start_freq = range_views[r]->frequency_range.max;
if (range_bw >= JAMMER_CH_WIDTH) {
// Split range in multiple channels
num_channels = 0;
range_bw_sub = range_bw;
@ -241,7 +259,6 @@ void JammerView::start_tx() {
i++;
}
} else {
// Range fits in a single channel
if (i >= JAMMER_MAX_CH) {
out_of_ranges = true;
} else {
@ -254,43 +271,24 @@ void JammerView::start_tx() {
if (!out_of_ranges && i) {
text_range_total.set("/" + to_string_dec_uint(i, 2));
jamming = true;
button_transmit.set_style(&style_cancel);
button_transmit.set_text("STOP");
transmitter_model.set_rf_amp(field_amp.value());
transmitter_model.set_tx_gain(field_gain.value());
transmitter_model.set_baseband_bandwidth(28'000'000); // Although tx is narrowband , let's use Max TX LPF .
transmitter_model.enable();
baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
mscounter = 0; // euquiq: Reset internal ms counter for do_timer()
return true;
} else {
if (out_of_ranges)
nav_.display_modal("Error", "Jamming bandwidth too large.\nMust be less than 24MHz.");
else
nav_.display_modal("Error", "No range enabled.");
return false;
}
}
void JammerView::stop_tx() {
button_transmit.set_style(&style_val);
button_transmit.set_text("START");
transmitter_model.disable();
baseband::set_jammer(false, JammerType::TYPE_FSK, 0);
jamming = false;
cooling = false;
}
// called each 1/60th of second
void JammerView::on_timer() {
if (++mscounter == 60) {
if (++mscounter >= 60) {
mscounter = 0;
if (jamming) {
int32_t timepause = field_timepause.value();
if (cooling) {
if (++seconds >= field_timepause.value()) { // Re-start TX
transmitter_model.set_baseband_bandwidth(28'000'000); // Although tx is narrowband , let's use Max TX LPF .
if (timepause == 0 || ++seconds >= timepause) {
transmitter_model.set_baseband_bandwidth(28'000'000);
transmitter_model.enable();
button_transmit.set_text("STOP");
baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
@ -306,8 +304,7 @@ void JammerView::on_timer() {
seconds = 0;
}
} else {
if (++seconds >= field_timetx.value()) // Start cooling period:
{
if (timepause && ++seconds >= field_timetx.value()) {
transmitter_model.disable();
button_transmit.set_text("PAUSED");
baseband::set_jammer(false, JammerType::TYPE_FSK, 0);
@ -327,11 +324,9 @@ void JammerView::on_timer() {
}
}
JammerView::JammerView(
NavigationView& nav)
JammerView::JammerView(NavigationView& nav)
: nav_{nav} {
Rect view_rect = {0, 3 * 8, screen_width, 80};
// baseband::run_image(portapack::spi_flash::image_tag_jammer);
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
add_children({&tab_view,
@ -355,15 +350,101 @@ JammerView::JammerView(
view_range_b.set_parent_rect(view_rect);
view_range_c.set_parent_rect(view_rect);
options_type.set_selected_index(3); // Rand CW
options_speed.set_selected_index(3); // 10kHz
options_hop.set_selected_index(1); // 50ms
button_transmit.set_style(&style_val);
view_range_a.check_enabled.set_value(true);
view_range_a.frequency_range.enabled = true;
view_range_a.update_center(315'000'000);
view_range_a.update_width(1'000'000);
options_type.set_selected_index(3);
options_speed.set_selected_index(3);
options_hop.set_selected_index(0);
field_timetx.set_value(30);
field_timepause.set_value(1);
field_timepause.set_value(0);
field_jitter.set_value(0);
field_gain.set_value(transmitter_model.tx_gain());
field_amp.set_value(transmitter_model.rf_amp());
button_transmit.set_style(&style_val);
options_type.on_change = [this](size_t, OptionsField::value_t) {
if (jamming) update_config();
if (jamming && !cooling) baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
};
options_speed.on_change = [this](size_t, OptionsField::value_t) {
if (jamming) update_config();
if (jamming && !cooling) baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
};
options_hop.on_change = [this](size_t, OptionsField::value_t) {
if (jamming) update_config();
};
field_timetx.on_change = [this](int32_t) {
if (jamming) update_config();
};
field_timepause.on_change = [this](int32_t) {
if (jamming) update_config();
};
field_jitter.on_change = [this](int32_t) {
if (jamming) update_config();
};
field_gain.on_change = [this](int32_t v) {
if (jamming) transmitter_model.set_tx_gain(v);
};
field_amp.on_change = [this](int32_t v) {
if (jamming) transmitter_model.set_rf_amp(v);
};
for (auto range_view : range_views) {
range_view->check_enabled.on_select = [this](Checkbox&, bool) {
if (jamming) update_config();
};
range_view->button_start.on_select = [this, range_view](Button&) {
auto new_view = nav_.push<FrequencyKeypadView>(range_view->frequency_range.min);
new_view->on_changed = [this, range_view](rf::Frequency f) {
range_view->update_start(f);
if (jamming) update_config();
};
};
range_view->button_stop.on_select = [this, range_view](Button&) {
auto new_view = nav_.push<FrequencyKeypadView>(range_view->frequency_range.max);
new_view->on_changed = [this, range_view](rf::Frequency f) {
range_view->update_stop(f);
if (jamming) update_config();
};
};
range_view->button_center.on_select = [this, range_view](Button&) {
auto new_view = nav_.push<FrequencyKeypadView>(range_view->center);
new_view->on_changed = [this, range_view](rf::Frequency f) {
range_view->update_center(f);
if (jamming) update_config();
};
};
range_view->button_width.on_select = [this, range_view](Button&) {
auto new_view = nav_.push<FrequencyKeypadView>(range_view->width);
new_view->on_changed = [this, range_view](rf::Frequency f) {
range_view->update_width(f);
if (jamming) update_config();
};
};
range_view->button_load_range.on_select = [this, range_view](Button&) {
auto load_view = nav_.push<FrequencyLoadView>();
load_view->on_frequency_loaded = [this, range_view](rf::Frequency value) {
range_view->update_center(value);
range_view->update_width(100000);
if (jamming) update_config();
};
load_view->on_range_loaded = [this, range_view](rf::Frequency start, rf::Frequency stop) {
range_view->update_start(start);
range_view->update_stop(stop);
if (jamming) update_config();
};
};
}
button_transmit.on_select = [this](Button&) {
if (jamming || cooling)