diff --git a/firmware/application/external/cvs_spam/cvs_spam.cpp b/firmware/application/external/cvs_spam/cvs_spam.cpp new file mode 100644 index 00000000..9bdfa90b --- /dev/null +++ b/firmware/application/external/cvs_spam/cvs_spam.cpp @@ -0,0 +1,313 @@ +// CVS Spam app by RocketGod (@rocketgod-git) https://betaskynet.com +// Original .cu8 files by @jimilinuxguy https://github.com/jimilinuxguy/customer-assistance-buttons-sdr +// If you can read this, you're a nerd. :P +// Come join us at https://discord.gg/thepiratesreborn + +#include "cvs_spam.hpp" +#include "io_file.hpp" +#include "metadata_file.hpp" +#include "oversample.hpp" +#include "io_convert.hpp" + +using namespace portapack; + +namespace ui::external_app::cvs_spam { + +void CVSSpamView::file_error(const std::filesystem::path& path, const std::string& error_details) { + nav_.display_modal("Error", + "File error:\n" + path.string() + "\n" + error_details); +} + +void CVSSpamView::refresh_list() { + file_list.clear(); + current_page = page; + uint32_t count = 0; + + for (const auto& entry : std::filesystem::directory_iterator(cvsfiles_dir, u"*")) { + if (std::filesystem::is_regular_file(entry.status())) { + auto filename = entry.path().filename().string(); + auto extension = entry.path().extension().string(); + std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower); + + if (extension == ".c16" || extension == ".cu8") { + if (count >= (page - 1) * 50 && count < page * 50) { + file_list.push_back(entry.path()); + if (file_list.size() == 50) { + page++; + break; + } + } + count++; + } + } + } + + if (file_list.empty()) { + if (page == 1) { + menu_view.hidden(true); + text_empty.hidden(false); + } else { + page = 1; + refresh_list(); + return; + } + } else { + menu_view.hidden(false); + text_empty.hidden(true); + menu_view.clear(); + for (const auto& file : file_list) { + menu_view.add_item({file.filename().string(), + ui::Theme::getInstance()->fg_green->foreground, + nullptr, + [this](KeyEvent key) { + if (key == KeyEvent::Select) { + start_tx(menu_view.highlighted_index()); + } + }}); + } + page_info.set("Page " + std::to_string(current_page)); + menu_view.set_highlighted(0); + } + + if (file_list.size() < 50) { + page = 1; + } +} + +void CVSSpamView::start_tx(const uint32_t id) { + if (is_active()) { + stop_tx(); + return; + } + + const uint32_t sample_rate = 500000; + + current_file = cvsfiles_dir / file_list[id].filename(); + + File capture_file; + auto open_error = capture_file.open(current_file); + if (open_error) { + file_error(current_file, + "Cannot open file.\n" + "Initial file check failed.\n" + "Path: " + + cvsfiles_dir.string() + + "\n" + "Error: " + + std::to_string(static_cast(open_error))); + return; + } + + auto metadata_path = get_metadata_path(current_file); + auto metadata = read_metadata_file(metadata_path); + if (!metadata) { + metadata = capture_metadata{transmitter_model.target_frequency(), sample_rate}; + } + + auto file_size = capture_file.size(); + capture_file.close(); + + replay_thread.reset(); + transmitter_model.disable(); + ready_signal = false; + + baseband::set_sample_rate(metadata->sample_rate, get_oversample_rate(metadata->sample_rate)); + + auto reader = std::make_unique(); + if (auto error = reader->open(current_file)) { + file_error(current_file, + "Cannot read C16 data.\n" + "Check file format/perms.\n" + "Rate: " + + to_string_dec_uint(metadata->sample_rate) + + "\n" + "Size: " + + to_string_dec_uint(file_size) + + "\n" + "Error: " + + std::to_string(static_cast(error))); + return; + } + + progressbar.set_value(0); + + transmitter_model.set_sampling_rate(get_actual_sample_rate(metadata->sample_rate)); + transmitter_model.set_baseband_bandwidth(metadata->sample_rate <= 500000 ? 1750000 : 2500000); + transmitter_model.set_target_frequency(metadata->center_frequency); + transmitter_model.enable(); + + chThdSleepMilliseconds(100); + + replay_thread = std::make_unique( + std::move(reader), + read_size, + buffer_count, + &ready_signal, + [](uint32_t return_code) { + ReplayThreadDoneMessage message{return_code}; + EventDispatcher::send_message(message); + }); +} + +void CVSSpamView::start_chaos_tx() { + if (is_active()) { + stop_tx(); + return; + } + + const std::filesystem::path chaos_file_path = cvsfiles_dir / "chaos.c16"; + const uint32_t sample_rate = 500000; + + File capture_file; + auto open_error = capture_file.open(chaos_file_path); + if (open_error) { + file_error(chaos_file_path, + "Cannot open CHAOS.C16.\n" + "Initial file check failed.\n" + "Path: " + + cvsfiles_dir.string() + + "\n" + "Error: " + + std::to_string(static_cast(open_error))); + return; + } + + auto metadata_path = get_metadata_path(chaos_file_path); + auto metadata = read_metadata_file(metadata_path); + if (!metadata) { + metadata = capture_metadata{transmitter_model.target_frequency(), sample_rate}; + } + + auto file_size = capture_file.size(); + capture_file.close(); + + replay_thread.reset(); + transmitter_model.disable(); + ready_signal = false; + + baseband::set_sample_rate(metadata->sample_rate, get_oversample_rate(metadata->sample_rate)); + + auto reader = std::make_unique(); + if (auto error = reader->open(chaos_file_path)) { + file_error(chaos_file_path, + "Cannot read CHAOS.C16.\n" + "Check file format/perms.\n" + "Rate: " + + to_string_dec_uint(metadata->sample_rate) + + "\n" + "Size: " + + to_string_dec_uint(file_size) + + "\n" + "Error: " + + std::to_string(static_cast(error))); + return; + } + + progressbar.set_value(0); + + transmitter_model.set_sampling_rate(get_actual_sample_rate(metadata->sample_rate)); + transmitter_model.set_baseband_bandwidth(metadata->sample_rate <= 500000 ? 1750000 : 2500000); + transmitter_model.set_target_frequency(metadata->center_frequency); + transmitter_model.enable(); + + chThdSleepMilliseconds(100); + + replay_thread = std::make_unique( + std::move(reader), + read_size, + buffer_count, + &ready_signal, + [](uint32_t return_code) { + ReplayThreadDoneMessage message{return_code}; + EventDispatcher::send_message(message); + }); +} + +void CVSSpamView::on_tx_progress(const uint32_t progress) { + if (is_active()) { + progressbar.set_value(progress % 100); + progressbar.set_style(Theme::getInstance()->fg_red); + } +} + +void CVSSpamView::focus() { + menu_view.focus(); +} + +bool CVSSpamView::is_active() const { + return (bool)replay_thread; +} + +void CVSSpamView::stop_tx() { + replay_thread.reset(); + transmitter_model.disable(); + audio::output::stop(); + ready_signal = false; + thread_sync_complete = false; + progressbar.set_value(0); + chThdSleepMilliseconds(50); +} + +CVSSpamView::CVSSpamView(NavigationView& nav) + : nav_{nav}, + current_file{} { + baseband::run_image(portapack::spi_flash::image_tag_replay); + + add_children({&menu_view, + &text_empty, + &button_prev_page, + &button_next_page, + &page_info, + &button_send, + &button_chaos, + &button_stop, + &progressbar}); + + button_send.set_style(Theme::getInstance()->fg_magenta); + button_chaos.set_style(Theme::getInstance()->fg_yellow); + button_stop.set_style(Theme::getInstance()->fg_red); + + transmitter_model.set_sampling_rate(1536000); + transmitter_model.set_baseband_bandwidth(1750000); + transmitter_model.set_rf_amp(true); + transmitter_model.set_tx_gain(47); + transmitter_model.set_channel_bandwidth(1750000); + + refresh_list(); + + button_prev_page.on_select = [this](Button&) { + if (is_active()) return; + if (current_page == 1) return; + if (current_page == 2) page = 1; + page = current_page - 1; + refresh_list(); + }; + + button_next_page.on_select = [this](Button&) { + if (is_active()) return; + refresh_list(); + }; + + button_send.on_select = [this](Button&) { + if (!file_list.empty()) { + start_tx(menu_view.highlighted_index()); + } + }; + + button_chaos.on_select = [this](Button&) { + start_chaos_tx(); + }; + + button_stop.on_select = [this](Button&) { + if (is_active()) { + stop_tx(); + } + }; +} + +CVSSpamView::~CVSSpamView() { + stop_tx(); + baseband::shutdown(); +} + +} // namespace ui::external_app::cvs_spam \ No newline at end of file diff --git a/firmware/application/external/cvs_spam/cvs_spam.hpp b/firmware/application/external/cvs_spam/cvs_spam.hpp new file mode 100644 index 00000000..d8379ca9 --- /dev/null +++ b/firmware/application/external/cvs_spam/cvs_spam.hpp @@ -0,0 +1,115 @@ +// CVS Spam app by RocketGod (@rocketgod-git) https://betaskynet.com +// Original .cu8 files by @jimilinuxguy https://github.com/jimilinuxguy/customer-assistance-buttons-sdr +// If you can read this, you're a nerd. :P +// Come join us at https://discord.gg/thepiratesreborn + +#pragma once + +#include "ui_widget.hpp" +#include "ui_transmitter.hpp" +#include "replay_thread.hpp" +#include "baseband_api.hpp" +#include "ui_language.hpp" +#include "file_path.hpp" +#include "audio.hpp" + +using namespace portapack; + +namespace ui::external_app::cvs_spam { + +class CVSSpamView : public View { + public: + explicit CVSSpamView(NavigationView& nav); + ~CVSSpamView(); + + void focus() override; + std::string title() const override { return "CVS Spam"; }; + + private: + static constexpr size_t read_size = 0x4000; + static constexpr size_t buffer_count = 3; + + NavigationView& nav_; + std::unique_ptr replay_thread{}; + bool ready_signal{false}; + bool thread_sync_complete{false}; + std::filesystem::path current_file; + + bool is_active() const; + void stop_tx(); + void file_error(const std::filesystem::path& path, const std::string& error_details); + void refresh_list(); + void start_tx(const uint32_t id); + void start_chaos_tx(); + void on_tx_progress(const uint32_t progress); + + uint32_t page = 1; + uint32_t current_page = 1; + std::vector file_list{}; + + MenuView menu_view{ + {0, 0, 240, 180}, + true}; + + Text text_empty{ + {7 * 8, 12 * 8, 16 * 8, 16}, + "Empty directory!"}; + + Button button_prev_page{ + {0, 180, 50, 32}, + "<"}; + + Button button_next_page{ + {190, 180, 50, 32}, + ">"}; + + Text page_info{ + {95, 188, 50, 16}}; + + Button button_send{ + {0, 212, 75, 36}, + "CALL"}; + + Button button_chaos{ + {82, 212, 75, 36}, + "CHAOS"}; + + Button button_stop{ + {165, 212, 75, 36}, + LanguageHelper::currentMessages[LANG_STOP]}; + + ProgressBar progressbar{ + {0, 256, 240, 44}}; + + MessageHandlerRegistration message_handler_fifo_signal{ + Message::ID::RequestSignal, + [this](const Message* const p) { + const auto message = static_cast(p); + if (message->signal == RequestSignalMessage::Signal::FillRequest) { + ready_signal = true; + } + }}; + + MessageHandlerRegistration message_handler_tx_progress{ + Message::ID::TXProgress, + [this](const Message* const p) { + const auto message = *reinterpret_cast(p); + this->on_tx_progress(message.progress); + }}; + + MessageHandlerRegistration message_handler_replay_thread_done{ + Message::ID::ReplayThreadDone, + [this](const Message* const p) { + const auto message = *reinterpret_cast(p); + if (message.return_code == ReplayThread::END_OF_FILE) { + if (is_active()) { + thread_sync_complete = true; + stop_tx(); + } + } else if (message.return_code == ReplayThread::READ_ERROR) { + file_error(file_list[menu_view.highlighted_index()], "Read error during playback"); + } + }}; +}; + +} // namespace ui::external_app::cvs_spam \ No newline at end of file diff --git a/firmware/application/external/cvs_spam/main.cpp b/firmware/application/external/cvs_spam/main.cpp new file mode 100644 index 00000000..9f35de18 --- /dev/null +++ b/firmware/application/external/cvs_spam/main.cpp @@ -0,0 +1,33 @@ +// CVS Spam app by RocketGod (@rocketgod-git) https://betaskynet.com +// Original .cu8 files by @jimilinuxguy https://github.com/jimilinuxguy/customer-assistance-buttons-sdr +// If you can read this, you're a nerd. :P +// Come join us at https://discord.gg/thepiratesreborn + +#include "ui.hpp" +#include "cvs_spam.hpp" +#include "ui_navigation.hpp" +#include "external_app.hpp" + +namespace ui::external_app::cvs_spam { +void initialize_app(NavigationView& nav) { + nav.push(); +} +} // namespace ui::external_app::cvs_spam + +extern "C" { + +__attribute__((section(".external_app.app_cvs_spam.application_information"), used)) application_information_t _application_information_cvs_spam = { + (uint8_t*)0x00000000, + ui::external_app::cvs_spam::initialize_app, + CURRENT_HEADER_VERSION, + VERSION_MD5, + "CVS Spam", + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81}, + ui::Color::red().v, + app_location_t::TX, + {'P', 'R', 'E', 'P'}, + 0x00000000}; +} \ No newline at end of file diff --git a/firmware/application/external/external.cmake b/firmware/application/external/external.cmake index 03eb99fb..03844fcb 100644 --- a/firmware/application/external/external.cmake +++ b/firmware/application/external/external.cmake @@ -110,6 +110,10 @@ set(EXTCPPSRC #shoppingcart_lock external/shoppingcart_lock/main.cpp external/shoppingcart_lock/shoppingcart_lock.cpp + + #cvs_spam + external/cvs_spam/main.cpp + external/cvs_spam/cvs_spam.cpp ) set(EXTAPPLIST @@ -131,6 +135,7 @@ set(EXTAPPLIST foxhunt_rx audio_test wardrivemap + cvs_spam tpmsrx protoview adsbtx diff --git a/firmware/application/external/external.ld b/firmware/application/external/external.ld index 1352b487..362cce89 100644 --- a/firmware/application/external/external.ld +++ b/firmware/application/external/external.ld @@ -49,6 +49,7 @@ MEMORY ram_external_app_random_password(rwx) : org = 0xADC80000, len = 32k ram_external_app_acars_rx(rwx) : org = 0xADC90000, len = 32k ram_external_app_shoppingcart_lock(rwx) : org = 0xADCA0000, len = 32k + ram_external_app_cvs_spam(rwx) : org = 0xADCB0000, len = 32k } SECTIONS @@ -210,6 +211,10 @@ SECTIONS *(*ui*external_app*shoppingcart_lock*); } > ram_external_app_shoppingcart_lock - + .external_app_cvs_spam : ALIGN(4) SUBALIGN(4) + { + KEEP(*(.external_app.app_cvs_spam.application_information)); + *(*ui*external_app*cvs_spam*); + } > ram_external_app_cvs_spam } diff --git a/firmware/application/file_path.cpp b/firmware/application/file_path.cpp index a56e141f..61404895 100644 --- a/firmware/application/file_path.cpp +++ b/firmware/application/file_path.cpp @@ -30,6 +30,7 @@ const std::filesystem::path audio_dir = u"AUDIO"; const std::filesystem::path blerx_dir = u"BLERX"; const std::filesystem::path bletx_dir = u"BLETX"; const std::filesystem::path captures_dir = u"CAPTURES"; +const std::filesystem::path cvsfiles_dir = u"CVSFILES"; const std::filesystem::path debug_dir = u"DEBUG"; const std::filesystem::path firmware_dir = u"FIRMWARE"; const std::filesystem::path freqman_dir = u"FREQMAN"; diff --git a/firmware/application/file_path.hpp b/firmware/application/file_path.hpp index 802ee868..a519e8d7 100644 --- a/firmware/application/file_path.hpp +++ b/firmware/application/file_path.hpp @@ -32,6 +32,7 @@ extern const std::filesystem::path audio_dir; extern const std::filesystem::path blerx_dir; extern const std::filesystem::path bletx_dir; extern const std::filesystem::path captures_dir; +extern const std::filesystem::path cvsfiles_dir; extern const std::filesystem::path debug_dir; extern const std::filesystem::path firmware_dir; extern const std::filesystem::path freqman_dir;