Compare commits

...

24 Commits

Author SHA1 Message Date
RocketGod 7b91103118 Made the Space Invaders game. Argh matey! (#2709)
* Made the Space Invaders game. Argh matey!
* Format code, sigh.
2025-06-21 08:35:52 +02:00
Tommaso Ventafridda c0aa4a1738 ADSB database update (tools, db) (#2701)
* enhance make_airlines_db tool
* enhance make_icao24_db tool
* update airlinescode (.txt, .db), aircraftdatabase/icao24 (.csv, .db)
2025-06-19 17:28:49 +02:00
Tommaso Ventafridda e26f77ee77 Filemanager: go to parent directory keep track of the right selected … (#2702)
* Filemanager: go to parent directory keep track of the right selected item and page number
* review: avoid unnecessary copies in get_extension
2025-06-19 17:24:38 +02:00
plomek f53262783a Improved FPV_ANALOG.txt FREQMAN file (#2700)
* Improved FPV_ANALOG.txt FREQMAN file
Removed unused or super rare analog fpv bands: U, O, H, D
Added 1.2GHz -1.3GHz channels sometimes used for long range analog fpv


* Corrected and updated the labels to be more consistent.

Corrected the labels to be more consistent.

I also somehow messed up the correct channels because 1.3GHz FPV is not fully standardized, but these channels seem to be the most common.
It should be all correct now.

Example transmitters using those channels:
https://greenchip.com.ua/0-0-1615-2.html
https://flymod.net/en/item/walksnail_vtx_9ch
https://pl.aliexpress.com/item/1005006505365351.html
2025-06-19 05:26:02 +00:00
Netro b456c18008 Modified Text Editor to handle long presses. (#2698) 2025-06-19 07:21:54 +02:00
Tommaso Ventafridda fa4b74fd6f Add vendor name in bluetooth rx app (#2696)
* add macaddress db, add vendor name in bluetooth rx app

* show "missing macaddress.db" instead of unknown if db not found

* bluetooth rx list with colors based on mac vendor

* bug fix
2025-06-16 10:57:58 -07:00
RocketGod 18bc2cf11c Made the Dino Game (#2697) 2025-06-15 09:25:10 -07:00
Tommaso Ventafridda 20f28c8331 add dark theme (#2695) 2025-06-13 18:34:20 +02:00
Tommaso Ventafridda ea38a0fe48 Add 1ms hop option to hopper app + 0ms (freeze UI) (#2692) 2025-06-12 11:54:37 +02:00
RocketGod fb2e576b34 Super secret dont look (#2690)
* 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.

* Super secret

* You gotta get (Get) that (That) dirt off your shoulder
2025-06-10 13:36:26 -07:00
Tommaso Ventafridda 1d79e30d4b Add all jammer modes in hopper app (#2691)
Added all modes that jammer app supports in hopper app.
2025-06-10 06:54:59 -07:00
Totoo be372e12bc Externalize widget (#2688) 2025-06-09 12:52:40 +02:00
Totoo 00853f526a Gfx widget and Radio (#2685)
* widgetize
* gfx and Radio improvement
* format + handle not wfm visual states
* wf or gf
2025-06-07 11:43:07 +02:00
Petro Danylevskyi 37ca7a601c Rename looking glass preset for clarity and consistency (#2686) 2025-06-07 11:27:25 +02:00
RocketGod b50d18eafc Radio app improvements (#2680) 2025-06-05 21:25:09 +02:00
Totoo 1070d951e6 80mhz jammer range (#2682)
Looks great 😎🤘🚀
2025-06-05 07:08:03 -07:00
Totoo a980fe21ac removed some std stuff only used here (#2681) 2025-06-05 11:32:26 +02:00
Alien 9e96715176 Added ability to enter custom tone values in Morse app (#2679)
* Added ability to enter custom tone values in Morse app

Added the ability to type in a custom tone value in the morse TX app (issue#2582)

*Click on the tone field to open a keyboard for entering in a desired value between 100hz - 9999hz.

*Maintains original step value of 20 when scrolling the rotary wheel.

* Update ui_morse.cpp

Replaced std::to_string with to_string_dec_uint

* Moved tone_input_buffer init to in-class
2025-06-05 05:21:44 +00:00
Brumi-2021 fecfe8b8fc Clean_LCD_beat_in_NOAA_Rx_App (#2678) 2025-06-03 21:10:58 +02:00
zix dfdd52c667 trivial apps folder movement (#2677) 2025-06-03 12:13:44 +02:00
Brumi-2021 695e6d19f4 Adding_BPF_selection_to_the_NOAA_APT_signal (#2675)
* Adding_BPF_selection_to_the_NOAA_APT_signal
* comments, spell mistake .
2025-06-02 14:40:13 +02:00
Totoo 6e9ecf8e7b jammer fix (#2676)
* jammer fix
2025-06-02 19:58:53 +08:00
RocketGod fd158e873a 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.
2025-05-31 22:16:57 +02:00
Totoo f90bd44617 Detector RX ext app (#2673) 2025-05-28 19:19:47 +08:00
68 changed files with 5780 additions and 790 deletions
@@ -143,7 +143,7 @@ class WFMAMAptOptionsView : public View {
};
OptionsField options_config{
{3 * 8, 0 * 16},
16, // Max option char length "80khz (NOAA Apt)" example.
16, // Max option char length "80k-NOAA Apt LPF" , example.
{
// Using common messages from freqman_ui.cpp
}};
+62 -1
View File
@@ -2,6 +2,7 @@
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2023 TJ Baginski
* Copyright (C) 2025 Tommaso Ventafridda
*
* This file is part of PortaPack.
*
@@ -83,6 +84,50 @@ void reverse_byte_array(uint8_t* arr, int length) {
}
}
MAC_VENDOR_STATUS lookup_mac_vendor_status(const uint8_t* mac_address, std::string& vendor_name) {
static bool db_checked = false;
static bool db_exists = false;
if (!db_checked) {
database db;
database::MacAddressDBRecord dummy_record;
int test_result = db.retrieve_macaddress_record(&dummy_record, "000000");
db_exists = (test_result != DATABASE_NOT_FOUND);
db_checked = true;
}
if (!db_exists) {
vendor_name = "macaddress.db not found";
return MAC_DB_NOT_FOUND;
}
database db;
database::MacAddressDBRecord record;
// Convert MAC address to hex string
std::string mac_hex = "";
for (int i = 0; i < 3; i++) {
// Only need first 3 bytes for OUI
mac_hex += to_string_hex(mac_address[i], 2);
}
int result = db.retrieve_macaddress_record(&record, mac_hex);
if (result == DATABASE_RECORD_FOUND) {
vendor_name = std::string(record.vendor_name);
return MAC_VENDOR_FOUND;
} else {
vendor_name = "Unknown";
return MAC_VENDOR_NOT_FOUND;
}
}
std::string lookup_mac_vendor(const uint8_t* mac_address) {
std::string vendor_name;
lookup_mac_vendor_status(mac_address, vendor_name);
return vendor_name;
}
namespace ui {
std::string pdu_type_to_string(ADV_PDU_TYPE type) {
@@ -165,7 +210,10 @@ void RecentEntriesTable<BleRecentEntries>::draw(
line += pad_string_with_spaces(db_spacing) + dbStr;
line.resize(target_rect.width() / 8, ' ');
painter.draw_string(target_rect.location(), style, line);
Style row_style = (entry.vendor_status == MAC_VENDOR_FOUND) ? style : Style{style.font, style.background, Color::grey()};
painter.draw_string(target_rect.location(), row_style, line);
}
BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const BleRecentEntry& entry)
@@ -178,10 +226,14 @@ BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const Bl
&text_mac_address,
&label_pdu_type,
&text_pdu_type,
&label_vendor,
&text_vendor,
&labels});
text_mac_address.set(to_string_mac_address(entry.packetData.macAddress, 6, false));
text_pdu_type.set(pdu_type_to_string(entry.pduType));
std::string vendor_name = lookup_mac_vendor(entry.packetData.macAddress);
text_vendor.set(vendor_name);
button_done.on_select = [&nav](const ui::Button&) {
nav.pop();
@@ -370,6 +422,10 @@ void BleRecentEntryDetailView::paint(Painter& painter) {
void BleRecentEntryDetailView::set_entry(const BleRecentEntry& entry) {
entry_ = entry;
text_mac_address.set(to_string_mac_address(entry.packetData.macAddress, 6, false));
text_pdu_type.set(pdu_type_to_string(entry.pduType));
std::string vendor_name = lookup_mac_vendor(entry.packetData.macAddress);
text_vendor.set(vendor_name);
set_dirty();
}
@@ -952,6 +1008,11 @@ void BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry,
entry.pduType = pdu_type;
entry.channelNumber = channel_number;
if (entry.vendor_status == MAC_VENDOR_UNKNOWN) {
std::string vendor_name;
entry.vendor_status = lookup_mac_vendor_status(entry.packetData.macAddress, vendor_name);
}
// Parse Data Section into buffer to be interpretted later.
for (int i = 0; i < packet->dataLen; i++) {
entry.packetData.data[i] = packet->data[i];
+18
View File
@@ -2,6 +2,7 @@
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2023 TJ Baginski
* Copyright (C) 2025 Tommaso Ventafridda
*
* This file is part of PortaPack.
*
@@ -33,6 +34,7 @@
#include "ui_record_view.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "database.hpp"
#include "log_file.hpp"
#include "utility.hpp"
#include "usb_serial_thread.hpp"
@@ -72,6 +74,13 @@ typedef enum {
RESERVED8 = 15
} ADV_PDU_TYPE;
typedef enum {
MAC_VENDOR_UNKNOWN = 0,
MAC_VENDOR_FOUND = 1,
MAC_VENDOR_NOT_FOUND = 2,
MAC_DB_NOT_FOUND = 3
} MAC_VENDOR_STATUS;
struct BleRecentEntry {
using Key = uint64_t;
@@ -87,6 +96,7 @@ struct BleRecentEntry {
uint16_t numHits;
ADV_PDU_TYPE pduType;
uint8_t channelNumber;
MAC_VENDOR_STATUS vendor_status;
bool entryFound;
BleRecentEntry()
@@ -105,6 +115,7 @@ struct BleRecentEntry {
numHits{},
pduType{},
channelNumber{},
vendor_status{MAC_VENDOR_UNKNOWN},
entryFound{} {
}
@@ -152,6 +163,13 @@ class BleRecentEntryDetailView : public View {
{9 * 8, 1 * 16, 17 * 8, 16},
"-"};
Labels label_vendor{
{{0 * 8, 2 * 16}, "Vendor:", Theme::getInstance()->fg_light->foreground}};
Text text_vendor{
{7 * 8, 2 * 16, 23 * 8, 16},
"-"};
Labels labels{
{{0 * 8, 3 * 16}, "Len", Theme::getInstance()->fg_light->foreground},
{{5 * 8, 3 * 16}, "Type", Theme::getInstance()->fg_light->foreground},
+20 -3
View File
@@ -319,7 +319,8 @@ void BLETxView::on_tx_progress(const bool done) {
BLETxView::BLETxView(NavigationView& nav)
: nav_{nav} {
add_children({&button_open,
add_children({&dataEditView,
&button_open,
&text_filename,
&progressbar,
&check_rand_mac,
@@ -340,7 +341,6 @@ BLETxView::BLETxView(NavigationView& nav)
&label_mac_address,
&text_mac_address,
&label_data_packet,
&dataEditView,
&button_clear_marked,
&button_save_packet,
&button_switch});
@@ -430,6 +430,23 @@ BLETxView::BLETxView(NavigationView& nav)
}
};
dataEditView.on_change = [this](uint8_t value) {
// Reject setting newline at index 29.
if (cursor_pos.col != 29) {
uint16_t dataBytePos = (cursor_pos.line * 29) + cursor_pos.col;
packets[current_packet].advertisementData[dataBytePos] = uint_to_char(value, 16);
update_current_packet(packets[current_packet], current_packet);
}
};
dataEditView.on_cursor_moved = [this]() {
// Save last selected cursor.
cursor_pos.line = dataEditView.line();
cursor_pos.col = dataEditView.col();
};
button_clear_marked.on_select = [this](Button&) {
marked_counter = 0;
markedBytes.clear();
@@ -531,7 +548,6 @@ void BLETxView::update_current_packet(BLETxPacket packet, uint32_t currentIndex)
for (const std::string& str : strings) {
dataFile.write(str.c_str(), str.size());
dataFile.write("\n", 1);
;
}
dataFile.~File();
@@ -546,6 +562,7 @@ void BLETxView::update_current_packet(BLETxPacket packet, uint32_t currentIndex)
dataEditView.set_font_zoom(true);
dataEditView.set_file(*dataFileWrapper);
dataEditView.redraw(true, true);
dataEditView.cursor_set(cursor_pos.line, cursor_pos.col);
}
void BLETxView::set_parent_rect(const Rect new_parent_rect) {
-3
View File
@@ -287,9 +287,6 @@ class BLETxView : public View {
Labels label_data_packet{
{{0 * 8, 9 * 16}, "Packet Data:", Theme::getInstance()->fg_light->foreground}};
Console console{
{0, 9 * 18, screen_width, screen_height - 80}};
TextViewer dataEditView{
{0, 9 * 18, screen_width, screen_height - 80}};
@@ -34,6 +34,7 @@ constexpr std::string_view mayhem_information_list[] = {
"RedFox-Fr,nemanjan00,",
"MichalLeonBorsuk,",
"MatiasFernandez,Giorgiofox",
"TommasoVentafridda",
" ",
"#Havoc:",
"jboone,furrtek,eried,argilo,",
+64 -90
View File
@@ -228,6 +228,8 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
text_current.set(dir_path.empty() ? "(sd root)" : truncate(dir_path, 24));
// Collect all entries first
std::list<fileman_entry> all_entries;
for (const auto& entry : fs::directory_iterator(dir_path, u"*")) {
// Hide files starting with '.' (hidden / tmp).
if (!show_hidden_files && is_hidden_file(entry.path()))
@@ -235,36 +237,40 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
if (fs::is_regular_file(entry.status())) {
if (!filtering || path_iequal(entry.path().extension(), extension_filter) || (cxx_file && is_cxx_capture_file(entry.path())))
insert_sorted(entry_list, {entry.path().string(), (uint32_t)entry.size(), false});
insert_sorted(all_entries, {entry.path().string(), (uint32_t)entry.size(), false});
} else if (fs::is_directory(entry.status())) {
insert_sorted(entry_list, {entry.path().string(), 0, true});
insert_sorted(all_entries, {entry.path().string(), 0, true});
}
}
// paginating
auto list_size = entry_list.size();
nb_pages = 1 + (list_size / items_per_page);
size_t start = pagination * items_per_page;
size_t stop = start + items_per_page;
if (list_size > start) {
if (list_size < stop)
stop = list_size;
entry_list.erase(std::next(entry_list.begin(), stop), entry_list.end());
entry_list.erase(entry_list.begin(), std::next(entry_list.begin(), start));
// Calculate pagination
nb_pages = (all_entries.size() + items_per_page - 1) / items_per_page;
if (nb_pages == 0) nb_pages = 1;
size_t start_idx = pagination * items_per_page;
size_t end_idx = std::min(start_idx + items_per_page, all_entries.size());
// Add "parent" directory if not at the root and on first page
if (!dir_path.empty() && pagination == 0) {
entry_list.push_back({parent_dir_path.string(), 0, true});
}
// Add "parent" directory if not at the root.
if (!dir_path.empty() && pagination == 0)
entry_list.insert(entry_list.begin(), {parent_dir_path.string(), 0, true});
// add next page
if (list_size > start + items_per_page) {
entry_list.push_back({str_next, (uint32_t)pagination + 1, true});
}
// add prev page
// Add prev page navigation if not on first page
if (pagination > 0) {
entry_list.insert(entry_list.begin(), {str_back, (uint32_t)pagination - 1, true});
entry_list.push_back({str_back, (uint32_t)pagination - 1, true});
}
// Add entries for current page
auto it = all_entries.begin();
std::advance(it, start_idx);
for (size_t i = start_idx; i < end_idx && it != all_entries.end(); i++, ++it) {
entry_list.push_back(*it);
}
// Add next page navigation if not on last page
if (end_idx < all_entries.size()) {
entry_list.push_back({str_next, (uint32_t)pagination + 1, true});
}
}
@@ -320,30 +326,49 @@ void FileManBaseView::focus() {
} else {
menu_view.focus();
}
// Set menu to the correct page and select the correct item
menu_view.set_highlighted(prev_highlight % items_per_page);
}
// Push directory - store the global index (page * items_per_page + local_index)
void FileManBaseView::push_dir(const fs::path& path) {
if (path == parent_dir_path) {
pop_dir();
} else {
// Save global index (combines page number and item position)
saved_index_stack.push_back(menu_view.highlighted_index() + (pagination * items_per_page));
current_path /= path;
saved_index_stack.push_back(menu_view.highlighted_index());
menu_view.set_highlighted(0);
reload_current(true);
reload_current(true); // Reset pagination when entering new directory
}
}
void FileManBaseView::pop_dir() {
if (saved_index_stack.empty())
if (current_path.empty()) {
return;
}
// Move to parent directory
current_path = current_path.parent_path();
reload_current(true);
menu_view.set_highlighted(saved_index_stack.back());
saved_index_stack.pop_back();
// Restore the previous global index if available
if (!saved_index_stack.empty()) {
uint32_t global_index = saved_index_stack.back();
saved_index_stack.pop_back();
// Calculate pagination from global index
pagination = global_index / items_per_page;
// Calculate local index within the page
prev_highlight = global_index % items_per_page;
restoring_navigation = true;
}
reload_current(false); // Important: don't reset pagination
}
std::string get_extension(std::string t) {
std::string FileManBaseView::get_extension(const std::string& t) const {
const auto index = t.find_last_of(u'.');
if (index == t.npos) {
return {};
@@ -372,7 +397,9 @@ void FileManBaseView::refresh_list() {
if (on_refresh_widgets)
on_refresh_widgets(false);
prev_highlight = menu_view.highlighted_index();
if (!restoring_navigation) {
prev_highlight = menu_view.highlighted_index();
}
menu_view.clear();
for (const auto& entry : entry_list) {
@@ -411,10 +438,14 @@ void FileManBaseView::refresh_list() {
}
menu_view.set_highlighted(prev_highlight);
restoring_navigation = false;
}
void FileManBaseView::reload_current(bool reset_pagination) {
if (reset_pagination) pagination = 0;
// Only reset pagination if explicitly requested
if (reset_pagination) {
pagination = 0;
}
load_directory_contents(current_path);
refresh_list();
}
@@ -489,63 +520,6 @@ void FileLoadView::refresh_widgets(const bool) {
set_dirty();
}
/* FileSaveView **************************************************************/
/*
FileSaveView::FileSaveView(
NavigationView& nav,
const fs::path& path,
const fs::path& file
) : nav_{ nav },
path_{ path },
file_{ file }
{
add_children({
&text_path,
&button_edit_path,
&text_name,
&button_edit_name,
&button_save,
&button_cancel,
});
button_edit_path.on_select = [this](Button&) {
buffer_ = path_.string();
text_prompt(nav_, buffer_, max_filename_length,ENTER_KEYBOARD_MODE_ALPHA,
[this](std::string&) {
path_ = buffer_;
refresh_widgets();
});
};
button_edit_name.on_select = [this](Button&) {
buffer_ = file_.string();
text_prompt(nav_, buffer_, max_filename_length,ENTER_KEYBOARD_MODE_ALPHA,
[this](std::string&) {
file_ = buffer_;
refresh_widgets();
});
};
button_save.on_select = [this](Button&) {
if (on_save)
on_save(path_ / file_);
else
nav_.pop();
};
button_cancel.on_select = [this](Button&) {
nav_.pop();
};
refresh_widgets();
}
void FileSaveView::refresh_widgets() {
text_path.set(truncate(path_, 30));
text_name.set(truncate(file_, 30));
set_dirty();
}
*/
/* FileManagerView ***********************************************************/
void FileManagerView::refresh_widgets(const bool v) {
+2
View File
@@ -64,6 +64,7 @@ class FileManBaseView : public View {
uint32_t prev_highlight = 0;
uint8_t pagination = 0;
uint8_t nb_pages = 1;
bool restoring_navigation = false;
static constexpr size_t max_filename_length = 20;
static constexpr size_t max_items_loaded = 75; // too memory hungry, so won't sort it
static constexpr size_t items_per_page = 20;
@@ -96,6 +97,7 @@ class FileManBaseView : public View {
void load_directory_contents(const std::filesystem::path& dir_path);
void load_directory_contents_unordered(const std::filesystem::path& dir_path, size_t file_cnt);
const file_assoc_t& get_assoc(const std::filesystem::path& ext) const;
std::string get_extension(const std::string& t) const;
void copy_waterfall(std::filesystem::path path);
NavigationView& nav_;
@@ -954,6 +954,7 @@ class SetThemeView : public View {
{"Aqua", 2},
{"Green", 3},
{"Red", 4},
{"Dark", 5},
},
true};
+97 -19
View File
@@ -26,6 +26,7 @@
#include "log_file.hpp"
#include "string_format.hpp"
#include "irq_controls.hpp"
using namespace portapack;
namespace fs = std::filesystem;
@@ -81,21 +82,75 @@ void TextViewer::paint(Painter& painter) {
paint_cursor(painter);
}
void TextViewer::enable_long_press() {
// Enable long press on "Select".
SwitchesState config;
config[toUType(Switch::Sel)] = true;
set_switches_long_press_config(config);
}
void TextViewer::on_focus() {
enable_long_press();
}
bool TextViewer::on_key(const KeyEvent key) {
int16_t delta_col = 0;
int16_t delta_line = 0;
if (key == KeyEvent::Left)
delta_col = -1;
else if (key == KeyEvent::Right)
delta_col = 1;
else if (key == KeyEvent::Up)
delta_line = -1;
else if (key == KeyEvent::Down)
delta_line = 1;
else if (key == KeyEvent::Select && on_select) {
on_select();
return true;
if (key == KeyEvent::Select) {
// Toggle 'digit' mode with long-press.
if (digit_mode_ || key_is_long_pressed(key)) {
digit_mode_ = !digit_mode_;
set_dirty();
return true;
}
}
if (digit_mode_) {
switch (key) {
case KeyEvent::Left:
delta_col = -1;
break;
case KeyEvent::Right:
delta_col = 1;
break;
case KeyEvent::Up:
set_value(value_ == 0x0F ? 0x00 : value_ + 1);
break;
case KeyEvent::Down:
set_value(value_ == 0x00 ? 0x0F : value_ - 1);
break;
default:
return false;
}
if (delta_col == 0 && delta_line == 0) {
return true;
}
} else {
switch (key) {
case KeyEvent::Left:
delta_col = -1;
break;
case KeyEvent::Right:
delta_col = 1;
break;
case KeyEvent::Up:
delta_line = -1;
break;
case KeyEvent::Down:
delta_line = 1;
break;
case KeyEvent::Select: {
if (on_select) {
on_select();
return true;
}
break;
}
default:
return false;
}
}
// Always allow cursor direction to be updated.
@@ -111,19 +166,42 @@ bool TextViewer::on_key(const KeyEvent key) {
bool TextViewer::on_encoder(EncoderEvent delta) {
bool updated = false;
if (cursor_.dir == ScrollDirection::Horizontal)
updated = apply_scrolling_constraints(0, delta);
else {
delta *= 16;
updated = apply_scrolling_constraints(delta, 0);
if (digit_mode_) {
if (delta > 0) {
set_value(value_ == 0x0F ? 0x00 : value_ + 1);
} else if (delta < 0) {
set_value(value_ == 0x00 ? 0x0F : value_ - 1);
}
updated = true;
} else {
if (cursor_.dir == ScrollDirection::Horizontal) {
updated = apply_scrolling_constraints(0, delta);
} else {
delta *= 16;
updated = apply_scrolling_constraints(delta, 0);
}
if (updated) {
redraw();
}
}
if (updated)
redraw();
return updated;
}
void TextViewer::set_value(uint8_t new_value) {
if (new_value != value_) {
value_ = new_value;
if (on_change) {
on_change(value_);
}
set_dirty();
}
}
void TextViewer::redraw(bool redraw_text, bool redraw_marked) {
paint_state_.redraw_text = redraw_text;
paint_state_.redraw_marked = redraw_marked;
@@ -54,10 +54,13 @@ class TextViewer : public Widget {
std::function<void()> on_select{};
std::function<void()> on_cursor_moved{};
std::function<void(uint8_t)> on_change{};
void paint(Painter& painter) override;
bool on_key(KeyEvent key) override;
bool on_encoder(EncoderEvent delta) override;
void set_value(uint8_t new_value);
void on_focus() override;
void redraw(bool redraw_text = false, bool redraw_marked = false);
@@ -74,6 +77,7 @@ class TextViewer : public Widget {
void cursor_set(uint16_t line, uint16_t col);
void cursor_mark_selected();
void cursor_clear_marked();
void enable_long_press();
typedef std::pair<uint16_t, uint16_t> LineColPair;
std::vector<LineColPair> lineColPair{};
@@ -95,6 +99,9 @@ class TextViewer : public Widget {
int8_t char_height{};
uint8_t max_line{};
uint8_t max_col{};
bool digit_mode_{false};
uint8_t value_{0};
bool allow_digit_mode_{true};
/* Returns true if the cursor was updated. */
bool apply_scrolling_constraints(
+4 -4
View File
@@ -105,10 +105,10 @@ void WFMConfig::apply() const {
void WFMAMConfig::apply() const {
const WFMAMConfigureMessage message{
decim_0, // Fixed 24 taps array : taps_16k0_decim_0
decim_1, // Fixed 32 taps array : taps_84k_wfm_decim_1
taps_64_lp_1875_2166, // Fixed channel audio filter , 64 taps array , to filter DSB AM 2k4 carrier before demod. AM .
17000, // NOAA satellite tx , FM deviation = +-17Khz.
decim_0, // Fixed 24 taps array : taps_16k0_decim_0
decim_1, // Dynamic 32 taps array : taps_80k_wfmam_decim_1, 38k_wfmam
taps_64_lp_bpf, // Dynamic 64 taps array , to filter modulated DSB AM 2k4 carrier before demod. AM .(LPF / BPF)
17000, // NOAA satellite tx , FM deviation = +-17Khz.
apt_audio_12k_notch_2k4_config,
apt_audio_12k_lpf_2000hz_config};
send_message(&message);
+1
View File
@@ -65,6 +65,7 @@ struct WFMConfig {
struct WFMAMConfig {
const fir_taps_real<24> decim_0; // To handle WFM filter BW=40K for NOAA APT
const fir_taps_real<32> decim_1;
const fir_taps_real<64> taps_64_lp_bpf; // to handle dynamically LPF / BPF .
void apply() const;
};
+11
View File
@@ -2,6 +2,7 @@
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2022 Arjan Onwezen
* Copyright (C) 2025 Tommaso Ventafridda
*
* This file is part of PortaPack.
*
@@ -56,6 +57,16 @@ int database::retrieve_aircraft_record(AircraftDBRecord* record, std::string sea
return (result);
}
int database::retrieve_macaddress_record(MacAddressDBRecord* record, std::string search_term) {
file_path = macaddress_dir / u"macaddress.db";
index_item_length = 7;
record_length = 64;
result = retrieve_record(file_path, index_item_length, record_length, record, search_term);
return (result);
}
int database::retrieve_record(std::filesystem::path file_path, int index_item_length, int record_length, void* record, std::string search_term) {
if (search_term.empty())
return DATABASE_RECORD_NOT_FOUND;
+8 -1
View File
@@ -2,6 +2,7 @@
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2022 Arjan Onwezen
* Copyright (C) 2025 Tommaso Ventafridda
*
* This file is part of PortaPack.
*
@@ -60,6 +61,12 @@ class database {
int retrieve_aircraft_record(AircraftDBRecord* record, std::string search_term);
struct MacAddressDBRecord {
char vendor_name[64]; // vendor/manufacturer name
};
int retrieve_macaddress_record(MacAddressDBRecord* record, std::string search_term);
private:
std::filesystem::path file_path = ""; // path including filename
int index_item_length = 0; // length of index item
@@ -77,4 +84,4 @@ class database {
int retrieve_record(std::filesystem::path file_path, int index_item_length, int record_length, void* record, std::string search_term);
};
#endif /*__DATABASE_H__*/
#endif /*__DATABASE_H__*/
+84
View File
@@ -0,0 +1,84 @@
/*
* Copyright (C) 2023 Bernd Herzog
*
* 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.hpp"
#include "ui_detector_rx.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::detector_rx {
void initialize_app(ui::NavigationView& nav) {
nav.push<DetectorRxView>();
}
} // namespace ui::external_app::detector_rx
extern "C" {
__attribute__((section(".external_app.app_detector_rx.application_information"), used)) application_information_t _application_information_detector_rx = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::detector_rx::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "Detector",
/*.bitmap_data = */
{
0x00,
0x00,
0x00,
0x00,
0x04,
0x20,
0x12,
0x48,
0x8A,
0x51,
0xCA,
0x53,
0xCA,
0x53,
0x8A,
0x51,
0x12,
0x48,
0x84,
0x21,
0xC0,
0x03,
0x40,
0x02,
0x60,
0x06,
0x20,
0x04,
0x30,
0x0C,
0xF0,
0x0F},
/*.icon_color = */ ui::Color::orange().v,
/*.menu_location = */ app_location_t::RX,
/*.desired_menu_position = */ -1,
// this has to be the biggest baseband used by the app. SPEC
/*.m4_app_tag = portapack::spi_flash::image_tag_nfm */ {'P', 'W', 'F', 'M'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
@@ -0,0 +1,177 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2018 Furrtek
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
*
* 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_detector_rx.hpp"
#include "ui_fileman.hpp"
#include "ui_freqman.hpp"
#include "baseband_api.hpp"
#include "file.hpp"
#include "oversample.hpp"
#include "ui_font_fixed_8x16.hpp"
using namespace portapack;
using namespace tonekey;
using portapack::memory::map::backup_ram;
namespace ui::external_app::detector_rx {
// Function to map the value from one range to another
int32_t DetectorRxView::map(int32_t value, int32_t fromLow, int32_t fromHigh, int32_t toLow, int32_t toHigh) {
return toLow + (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow);
}
void DetectorRxView::focus() {
field_lna.focus();
}
DetectorRxView::~DetectorRxView() {
// reset performance counters request to default
shared_memory.request_m4_performance_counter = 0;
receiver_model.disable();
audio::output::stop();
baseband::shutdown();
}
void DetectorRxView::on_timer() {
freq_index++;
if (freq_mode == 0 && freq_index >= tetra_uplink_monitoring_frequencies_hz.size()) freq_index = 0; // TETRA UP
if (freq_mode == 1 && freq_index >= lora_monitoring_frequencies_hz.size()) freq_index = 0; // Lora
if (freq_mode == 2 && freq_index >= remotes_monitoring_frequencies_hz.size()) freq_index = 0; // Remotes
if (freq_mode == 2)
freq_ = remotes_monitoring_frequencies_hz[freq_index];
else if (freq_mode == 1)
freq_ = lora_monitoring_frequencies_hz[freq_index];
else
freq_ = tetra_uplink_monitoring_frequencies_hz[freq_index];
receiver_model.set_target_frequency(freq_);
}
DetectorRxView::DetectorRxView(NavigationView& nav)
: nav_{nav} {
add_children({
&labels,
&field_lna,
&field_vga,
&field_rf_amp,
&field_volume,
&text_frequency,
&freq_stats_rssi,
&freq_stats_db,
&text_beep_squelch,
&field_beep_squelch,
&rssi,
&rssi_graph,
&field_mode,
});
// activate vertical bar mode
rssi.set_vertical_rssi(true);
freq_ = receiver_model.target_frequency();
field_beep_squelch.set_value(beep_squelch);
field_beep_squelch.on_change = [this](int32_t v) {
beep_squelch = v;
};
rssi_graph.set_nb_columns(256);
change_mode();
rssi.set_peak(true, 3000);
// FILL STEP OPTIONS
freq_stats_rssi.set_style(Theme::getInstance()->bg_darkest);
freq_stats_db.set_style(Theme::getInstance()->bg_darkest);
field_mode.on_change = [this](size_t, int32_t value) {
freq_mode = value;
freq_index = 0;
switch (value) {
case 1: // Lora
text_frequency.set(" 433, 868, 915 Mhz");
break;
case 2: // Remotes
text_frequency.set(" 433, 315 Mhz");
break;
default:
case 0: // TETRA UP
text_frequency.set(" 380-390 Mhz");
break;
}
};
}
void DetectorRxView::on_statistics_update(const ChannelStatistics& statistics) {
static int16_t last_max_db = 0;
static uint8_t last_min_rssi = 0;
static uint8_t last_avg_rssi = 0;
static uint8_t last_max_rssi = 0;
rssi_graph.add_values(rssi.get_min(), rssi.get_avg(), rssi.get_max(), statistics.max_db);
// refresh db
if (last_max_db != statistics.max_db) {
last_max_db = statistics.max_db;
freq_stats_db.set("Power: " + to_string_dec_int(statistics.max_db) + " db");
rssi.set_db(statistics.max_db);
}
// refresh rssi
if (last_min_rssi != rssi_graph.get_graph_min() || last_avg_rssi != rssi_graph.get_graph_avg() || last_max_rssi != rssi_graph.get_graph_max()) {
last_min_rssi = rssi_graph.get_graph_min();
last_avg_rssi = rssi_graph.get_graph_avg();
last_max_rssi = rssi_graph.get_graph_max();
freq_stats_rssi.set("RSSI: " + to_string_dec_uint(last_min_rssi) + "/" + to_string_dec_uint(last_avg_rssi) + "/" + to_string_dec_uint(last_max_rssi));
}
if (statistics.max_db > beep_squelch) {
baseband::request_audio_beep(map(statistics.max_db, -100, 20, 400, 2600), 24000, 150);
}
} /* on_statistic_updates */
size_t DetectorRxView::change_mode() {
audio::output::stop();
receiver_model.disable();
baseband::shutdown();
audio_sampling_rate = audio::Rate::Hz_24000;
baseband::run_image(portapack::spi_flash::image_tag_capture);
receiver_model.set_modulation(ReceiverModel::Mode::Capture);
baseband::set_sample_rate(DETECTOR_BW, get_oversample_rate(DETECTOR_BW));
// The radio needs to know the effective sampling rate.
auto actual_sampling_rate = get_actual_sample_rate(DETECTOR_BW);
receiver_model.set_sampling_rate(actual_sampling_rate);
receiver_model.set_baseband_bandwidth(filter_bandwidth_for_sampling_rate(actual_sampling_rate));
audio::set_rate(audio_sampling_rate);
audio::output::start();
receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // WM8731 hack.
receiver_model.enable();
return 0;
}
} // namespace ui::external_app::detector_rx
@@ -0,0 +1,201 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2018 Furrtek
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
*
* 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.
*/
#ifndef _UI_DETECTOR_RX
#define _UI_DETECTOR_RX
#include "analog_audio_app.hpp"
#include "app_settings.hpp"
#include "audio.hpp"
#include "baseband_api.hpp"
#include "file.hpp"
#include "freqman_db.hpp"
#include "portapack_persistent_memory.hpp"
#include "radio_state.hpp"
#include "receiver_model.hpp"
#include "string_format.hpp"
#include "ui.hpp"
#include "ui_mictx.hpp"
#include "ui_receiver.hpp"
#include "ui_spectrum.hpp"
namespace ui::external_app::detector_rx {
#define DETECTOR_BW 750000
class DetectorRxView : public View {
public:
DetectorRxView(NavigationView& nav);
~DetectorRxView();
void focus() override;
std::string title() const override { return "Detector RX"; };
private:
NavigationView& nav_;
RxRadioState radio_state_{};
int32_t map(int32_t value, int32_t fromLow, int32_t fromHigh, int32_t toLow, int32_t toHigh);
size_t change_mode();
void on_statistics_update(const ChannelStatistics& statistics);
void set_display_freq(int64_t freq);
void on_timer();
uint8_t freq_index = 0;
rf::Frequency freq_ = {433920000};
int32_t beep_squelch = 0;
audio::Rate audio_sampling_rate = audio::Rate::Hz_48000;
uint8_t freq_mode = 0;
app_settings::SettingsManager settings_{
"rx_detector",
app_settings::Mode::RX,
{
{"beep_squelch"sv, &beep_squelch},
}};
Labels labels{
{{UI_POS_X(0), UI_POS_Y(0)}, "LNA: VGA: AMP: VOL: ", Theme::getInstance()->fg_light->foreground}};
LNAGainField field_lna{
{UI_POS_X(4), UI_POS_Y(0)}};
VGAGainField field_vga{
{UI_POS_X(11), UI_POS_Y(0)}};
RFAmpField field_rf_amp{
{UI_POS_X(18), UI_POS_Y(0)}};
AudioVolumeField field_volume{
{UI_POS_X(24), UI_POS_Y(0)}};
OptionsField field_mode{
{UI_POS_X(0), UI_POS_Y(1)},
9,
{
{"TETRA UP", 0},
{"Lora", 1},
{"Remotes", 2},
}};
Text text_frequency{
{UI_POS_X_RIGHT(20), UI_POS_Y(1), UI_POS_WIDTH(20), UI_POS_DEFAULT_HEIGHT},
""};
Text text_beep_squelch{
{UI_POS_X_RIGHT(9), UI_POS_Y(2), UI_POS_WIDTH(4), UI_POS_DEFAULT_HEIGHT},
"Bip>"};
NumberField field_beep_squelch{
{UI_POS_X_RIGHT(5), UI_POS_Y(2)},
4,
{-100, 20},
1,
' ',
};
// RSSI: XX/XX/XXX
Text freq_stats_rssi{
{UI_POS_X(0), UI_POS_Y(2), UI_POS_WIDTH(15), UI_POS_DEFAULT_HEIGHT},
};
// Power: -XXX db
Text freq_stats_db{
{UI_POS_X(0), UI_POS_Y(3), UI_POS_WIDTH(15), UI_POS_DEFAULT_HEIGHT},
};
RSSIGraph rssi_graph{
{UI_POS_X(0), UI_POS_Y(5), UI_POS_WIDTH_REMAINING(5), UI_POS_HEIGHT_REMAINING(6)},
};
RSSI rssi{
{UI_POS_X_RIGHT(5), UI_POS_Y(5), UI_POS_WIDTH(5), UI_POS_HEIGHT_REMAINING(6)},
};
MessageHandlerRegistration message_handler_stats{
Message::ID::ChannelStatistics,
[this](const Message* const p) {
this->on_statistics_update(static_cast<const ChannelStatisticsMessage*>(p)->statistics);
}};
MessageHandlerRegistration message_handler_frame_sync{
Message::ID::DisplayFrameSync,
[this](const Message* const) {
this->on_timer();
}};
const std::vector<uint32_t> remotes_monitoring_frequencies_hz = {
// Around 315 MHz (common for older remotes, key fobs in some regions)
// Window centered on 315 MHz, covers 314.625 - 315.375 MHz
315000000,
// Around 433.92 MHz (very common for remotes, sensors, key fobs globally)
// Window centered on 433.92 MHz, covers 433.545 - 434.295 MHz
433920000,
};
const std::vector<uint32_t> lora_monitoring_frequencies_hz = {
// EU433 Band (Europe, typically 433.05 MHz to 434.79 MHz)
// Scanning the approximate range 433.0 MHz to 434.8 MHz with 750kHz steps
433375000, // Covers 433.000 - 433.750 MHz
434125000, // Covers 433.750 - 434.500 MHz (includes 433.92 MHz)
434875000, // Covers 434.500 - 435.250 MHz (covers up to 434.79 MHz)
// EU868 Band (Europe, typically 863 MHz to 870 MHz, specific channels around 868 MHz)
// Targeting common LoRaWAN channel groups (approx 867.0 - 868.6 MHz) with 750kHz steps
867375000, // Covers 867.000 - 867.750 MHz
868125000, // Covers 867.750 - 868.500 MHz
868875000, // Covers 868.500 - 869.250 MHz (covers up to 868.6 MHz)
// US915 Band (North America, typically 902 MHz to 928 MHz, specific channels around 915 MHz)
// Providing a few sample windows around the 915 MHz area with 750kHz steps.
// This band is wide; a full scan would require many more frequencies.
914250000, // Covers 913.875 - 914.625 MHz
915000000, // Covers 914.625 - 915.375 MHz (Centered on 915 MHz)
915750000, // Covers 915.375 - 916.125 MHz
};
const std::vector<uint32_t> tetra_uplink_monitoring_frequencies_hz = {
// Band starts at 380,000,000 Hz, ends at 390,000,000 Hz.
// First center: 380,000,000 + 375,000 = 380,375,000 Hz
// Last center: 380,375,000 + 13 * 750,000 = 390,125,000 Hz (14 frequencies total for this band)
380375000, // Covers 380.000 - 380.750 MHz
381125000, // Covers 380.750 - 381.500 MHz
381875000, // Covers 381.500 - 382.250 MHz
382625000, // Covers 382.250 - 383.000 MHz
383375000, // Covers 383.000 - 383.750 MHz
384125000, // Covers 383.750 - 384.500 MHz
384875000, // Covers 384.500 - 385.250 MHz
385625000, // Covers 385.250 - 386.000 MHz
386375000, // Covers 386.000 - 386.750 MHz
387125000, // Covers 386.750 - 387.500 MHz
387875000, // Covers 387.500 - 388.250 MHz
388625000, // Covers 388.250 - 389.000 MHz
389375000, // Covers 389.000 - 389.750 MHz
390125000, // Covers 389.750 - 390.500 MHz
};
};
} // namespace ui::external_app::detector_rx
#endif
+75
View File
@@ -0,0 +1,75 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
/*
* Chrome Dino Game for Portapack Mayhem
* Based on the original DinoGame by various contributors
*/
#include "ui.hpp"
#include "ui_dinogame.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::dinogame {
void initialize_app(ui::NavigationView& nav) {
nav.push<DinoGameView>();
}
} // namespace ui::external_app::dinogame
extern "C" {
__attribute__((section(".external_app.app_dinogame.application_information"), used)) application_information_t _application_information_dinogame = {
(uint8_t*)0x00000000,
ui::external_app::dinogame::initialize_app,
CURRENT_HEADER_VERSION,
VERSION_MD5,
"Dino Game",
{
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0xF0,
0xF8,
0xFC,
0xFE,
0xFF,
0xFF,
0x7F,
0x7F,
0x3F,
0x3F,
0x3E,
0x3C,
0x38,
0x30,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
},
ui::Color::green().v,
app_location_t::GAMES,
-1,
{0, 0, 0, 0},
0x00000000,
};
}
+447
View File
@@ -0,0 +1,447 @@
/*
standing - 34x36
ducking - 46x24
*/
const uint16_t dino_default[] PROGMEM = {
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0030 (48)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0040 (64)
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0050 (80)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0060 (96)
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0070 (112)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0080 (128)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0090 (144)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x00A0 (160)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00B0 (176)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00C0 (192)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00D0 (208)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x00E0 (224)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00F0 (240)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0100 (256)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x0110 (272)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0120 (288)
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0130 (304)
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0140 (320)
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0150 (336)
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0160 (352)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0170 (368)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0180 (384)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0190 (400)
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01A0 (416)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01C0 (448)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x01E0 (480)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01F0 (496)
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0200 (512)
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0220 (544)
0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0240 (576)
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0250 (592)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0260 (608)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0270 (624)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0280 (640)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0290 (656)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02B0 (688)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x02C0 (704)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02D0 (720)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02E0 (736)
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x02F0 (752)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0300 (768)
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0310 (784)
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0320 (800)
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0340 (832)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0360 (864)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0380 (896)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0390 (912)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x03A0 (928)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03B0 (944)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03C0 (960)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03D0 (976)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03E0 (992)
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03F0 (1008)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0400 (1024)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0410 (1040)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0420 (1056)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0430 (1072)
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0440 (1088)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0450 (1104)
0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0460 (1120)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0470 (1136)
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0480 (1152)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0490 (1168)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04A0 (1184)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04B0 (1200)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04C0 (1216)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
const uint16_t dino_leftstep[] PROGMEM = {
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0030 (48)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0040 (64)
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0050 (80)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0060 (96)
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0070 (112)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0080 (128)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0090 (144)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x00A0 (160)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00B0 (176)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00C0 (192)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00D0 (208)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x00E0 (224)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00F0 (240)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0100 (256)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x0110 (272)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0120 (288)
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0130 (304)
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0140 (320)
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0150 (336)
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0160 (352)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0170 (368)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0180 (384)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0190 (400)
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01A0 (416)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01C0 (448)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x01E0 (480)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01F0 (496)
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0200 (512)
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0220 (544)
0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0240 (576)
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0250 (592)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0260 (608)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0270 (624)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0280 (640)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0290 (656)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02B0 (688)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x02C0 (704)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02D0 (720)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02E0 (736)
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x02F0 (752)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0300 (768)
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0310 (784)
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0320 (800)
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0340 (832)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0360 (864)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0380 (896)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0390 (912)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x03A0 (928)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03B0 (944)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03C0 (960)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03D0 (976)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03E0 (992)
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03F0 (1008)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0400 (1024)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0410 (1040)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0420 (1056)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0430 (1072)
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0440 (1088)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0450 (1104)
0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0460 (1120)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0470 (1136)
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0480 (1152)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0490 (1168)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04A0 (1184)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04B0 (1200)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04C0 (1216)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
const uint16_t dino_rightstep[] PROGMEM = {
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0030 (48)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0040 (64)
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0050 (80)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0060 (96)
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0070 (112)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0080 (128)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0090 (144)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x00A0 (160)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00B0 (176)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00C0 (192)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00D0 (208)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x00E0 (224)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00F0 (240)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0100 (256)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x0110 (272)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0120 (288)
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0130 (304)
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0140 (320)
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0150 (336)
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0160 (352)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0170 (368)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0180 (384)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0190 (400)
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01A0 (416)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01C0 (448)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x01E0 (480)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01F0 (496)
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0200 (512)
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0220 (544)
0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0240 (576)
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0250 (592)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0260 (608)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0270 (624)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0280 (640)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0290 (656)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02B0 (688)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x02C0 (704)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02D0 (720)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02E0 (736)
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x02F0 (752)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0300 (768)
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0310 (784)
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0320 (800)
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0340 (832)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0360 (864)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0380 (896)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0390 (912)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x03A0 (928)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03B0 (944)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03C0 (960)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03D0 (976)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03E0 (992)
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03F0 (1008)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0400 (1024)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0410 (1040)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0420 (1056)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0430 (1072)
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0440 (1088)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0450 (1104)
0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0460 (1120)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0470 (1136)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0480 (1152)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0490 (1168)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04A0 (1184)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04B0 (1200)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04C0 (1216)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
const uint16_t dino_ducking_leftstep[] PROGMEM = {
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0030 (48)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0040 (64)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0050 (80)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x0060 (96)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0070 (112)
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0080 (128)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0090 (144)
0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00A0 (160)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00B0 (176)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00C0 (192)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00D0 (208)
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00E0 (224)
0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00F0 (240)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0100 (256)
0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0110 (272)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0120 (288)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0130 (304)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x0140 (320)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0150 (336)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0160 (352)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0170 (368)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0180 (384)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0190 (400)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01A0 (416)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01C0 (448)
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01E0 (480)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01F0 (496)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0200 (512)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0220 (544)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0240 (576)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0250 (592)
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0260 (608)
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0270 (624)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0280 (640)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0290 (656)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x02B0 (688)
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02C0 (704)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02D0 (720)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x02E0 (736)
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02F0 (752)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0300 (768)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0310 (784)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0320 (800)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0340 (832)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0360 (864)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0380 (896)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0390 (912)
0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03A0 (928)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03B0 (944)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x03C0 (960)
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03D0 (976)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
const uint16_t dino_ducking_rightstep[] PROGMEM = {
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0030 (48)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0040 (64)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0050 (80)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x0060 (96)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0070 (112)
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0080 (128)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0090 (144)
0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00A0 (160)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00B0 (176)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00C0 (192)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00D0 (208)
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00E0 (224)
0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00F0 (240)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0100 (256)
0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0110 (272)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0120 (288)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0130 (304)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x0140 (320)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0150 (336)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0160 (352)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0170 (368)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0180 (384)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0190 (400)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01A0 (416)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01C0 (448)
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01E0 (480)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01F0 (496)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0200 (512)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0220 (544)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0240 (576)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0250 (592)
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0260 (608)
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0270 (624)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0280 (640)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0290 (656)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02B0 (688)
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02C0 (704)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02D0 (720)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x02E0 (736)
0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02F0 (752)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0300 (768)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x0310 (784)
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0320 (800)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0340 (832)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0360 (864)
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0380 (896)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0390 (912)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03A0 (928)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03B0 (944)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03C0 (960)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03D0 (976)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
const uint16_t dino_gameover[] PROGMEM = {
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0030 (48)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0040 (64)
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0050 (80)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0060 (96)
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0070 (112)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0080 (128)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0090 (144)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0xFFFF, 0x528A, // 0x00A0 (160)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00B0 (176)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x00C0 (192)
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00D0 (208)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x00E0 (224)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00F0 (240)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0100 (256)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x0110 (272)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0120 (288)
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0130 (304)
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0140 (320)
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0150 (336)
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0160 (352)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0170 (368)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0180 (384)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0190 (400)
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01A0 (416)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01C0 (448)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x01E0 (480)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01F0 (496)
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0200 (512)
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0220 (544)
0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0240 (576)
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0250 (592)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0260 (608)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0270 (624)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0280 (640)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0290 (656)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02B0 (688)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x02C0 (704)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02D0 (720)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02E0 (736)
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x02F0 (752)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0300 (768)
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0310 (784)
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0320 (800)
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0340 (832)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0360 (864)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0380 (896)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0390 (912)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x03A0 (928)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03B0 (944)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03C0 (960)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03D0 (976)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03E0 (992)
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03F0 (1008)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0400 (1024)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0410 (1040)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0420 (1056)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0430 (1072)
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0440 (1088)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0450 (1104)
0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0460 (1120)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0470 (1136)
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0480 (1152)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0490 (1168)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04A0 (1184)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04B0 (1200)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04C0 (1216)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
@@ -0,0 +1,121 @@
// 34x27
const uint16_t pterodactyl_upflop[] PROGMEM = {
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0010 (16)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0030 (48)
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0040 (64)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0050 (80)
0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0060 (96)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0070 (112)
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0080 (128)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x0090 (144)
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00A0 (160)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x00B0 (176)
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00C0 (192)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x00D0 (208)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x00E0 (224)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00F0 (240)
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0100 (256)
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0110 (272)
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0120 (288)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0130 (304)
0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0140 (320)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0150 (336)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0160 (352)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0170 (368)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0180 (384)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0190 (400)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01A0 (416)
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01C0 (448)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01E0 (480)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01F0 (496)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0200 (512)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0220 (544)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0240 (576)
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0250 (592)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0260 (608)
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0270 (624)
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0280 (640)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0290 (656)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02B0 (688)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02C0 (704)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02D0 (720)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02E0 (736)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02F0 (752)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0300 (768)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0310 (784)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0320 (800)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0340 (832)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0360 (864)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0380 (896)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0390 (912)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
const uint16_t pterodactyl_downflop[] PROGMEM = {
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0030 (48)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0040 (64)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0050 (80)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0060 (96)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0070 (112)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0080 (128)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x0090 (144)
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00A0 (160)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x00B0 (176)
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00C0 (192)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x00D0 (208)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00E0 (224)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00F0 (240)
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0100 (256)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0110 (272)
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0120 (288)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0130 (304)
0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0140 (320)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0150 (336)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0160 (352)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0170 (368)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0180 (384)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0190 (400)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01A0 (416)
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01C0 (448)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01E0 (480)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01F0 (496)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0200 (512)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0220 (544)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0240 (576)
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0250 (592)
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0260 (608)
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0270 (624)
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0280 (640)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0290 (656)
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02B0 (688)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02C0 (704)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02D0 (720)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02E0 (736)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02F0 (752)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0300 (768)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0310 (784)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0320 (800)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0340 (832)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0360 (864)
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0380 (896)
0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0390 (912)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
+808
View File
@@ -0,0 +1,808 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
/*
* Chrome Dino Game for Portapack Mayhem
* Based on the original DinoGame by various contributors
*/
#include "ui_dinogame.hpp"
namespace ui::external_app::dinogame {
#define PROGMEM
#include "sprites/dino.h"
#include "sprites/pterodactyl.h"
#undef PROGMEM
// Global variables
Ticker game_timer;
Painter painter;
static Callback game_update_callback = nullptr;
static uint32_t game_update_timeout = 0;
static uint32_t game_update_counter = 0;
static DinoGameView* current_instance = nullptr;
const Color pp_colors[] = {
Color::white(),
Color::blue(),
Color::yellow(),
Color::purple(),
Color::green(),
Color::red(),
Color::magenta(),
Color::orange(),
Color::black(),
};
// Drawing functions
void cls() {
painter.fill_rectangle({0, 0, portapack::display.width(), portapack::display.height()}, Color::black());
}
void fillrect(int x1, int y1, int x2, int y2, int color) {
painter.fill_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
}
void rect(int x1, int y1, int x2, int y2, int color) {
painter.draw_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
}
// Timer implementation
void check_game_timer() {
if (game_update_callback) {
if (++game_update_counter >= game_update_timeout) {
game_update_counter = 0;
game_update_callback();
}
}
}
void Ticker::attach(Callback func, double delay_sec) {
game_update_callback = func;
game_update_timeout = delay_sec * 60;
}
void Ticker::detach() {
game_update_callback = nullptr;
}
// String helper
std::string DinoGameView::score_to_string(uint32_t score) {
std::string score_s = std::to_string(score);
if (score_s.length() < 5) {
std::string temp = "";
for (uint8_t i = 0; i < 5 - score_s.length(); ++i) temp += "0";
temp += score_s;
score_s = temp;
}
return score_s;
}
// Game timer callback
void game_timer_check() {
if (current_instance) {
if (current_instance->game_state == GameState::PLAYING) {
current_instance->game_loop();
} else if (current_instance->game_state == GameState::MENU) {
current_instance->show_menu();
}
}
}
DinoGameView::DinoGameView(NavigationView& nav)
: nav_{nav}, bird_info{}, game_timer{} {
add_children({&dummy, &button_difficulty});
current_instance = this;
game_timer.attach(&game_timer_check, 1.0 / 60.0);
button_difficulty.on_select = [this](Button&) {
easy_mode = !easy_mode;
button_difficulty.set_text(easy_mode ? "Mode: EASY" : "Mode: HARD");
};
}
void DinoGameView::on_show() {
}
void DinoGameView::paint(Painter& painter) {
(void)painter;
if (!initialized) {
initialized = true;
std::srand(LPC_RTC->CTIME0);
init_game();
}
}
void DinoGameView::frame_sync() {
check_game_timer();
set_dirty();
}
void DinoGameView::init_game() {
game_state = GameState::MENU;
menu_initialized = false;
blink_state = true;
blink_counter = 0;
new_game();
}
void DinoGameView::new_game() {
steps = 0;
score = 0;
collided = false;
ducking = false;
duck_timer = 0;
displayedGameOver = false;
bird_info.inGame = false;
bird_info.x_offset = 320;
bird_info.y_offset = 0;
bird_info.y_velocity = 0;
jumping = false;
falling = false;
jumpHeight = 0;
last_dino_y = DINO_Y;
last_bird_x = -1;
last_bird_y = -1;
last_ducking = false;
last_runstate = false;
ground_offset = 0;
speed_modifier = 0;
runstate = false;
score_drawn = false;
last_score = 999999;
obstacle_spawn_timer = 100; // Initial delay before first obstacle
// Initialize obstacles
for (int i = 0; i < MAX_OBSTACLES; i++) {
obstacles[i].active = false;
obstacles[i].x = -100;
obstacles[i].last_x = -100;
}
cls();
}
void DinoGameView::show_menu() {
if (!menu_initialized) {
cls();
auto style = *ui::Theme::getInstance()->fg_medium;
auto style_title = *ui::Theme::getInstance()->fg_light;
// Draw title
painter.draw_string({80, 60}, style_title, "DINO GAME");
// Draw instructions
painter.draw_string({45, 130}, style, "SELECT: Jump/Start");
painter.draw_string({65, 150}, style, "DOWN: Duck");
painter.draw_string({50, 170}, style, "Avoid obstacles!");
// Draw high score
draw_high_score();
menu_initialized = true;
}
// Show difficulty button
button_difficulty.hidden(false);
// Animate the menu dino
bool menu_run_frame = (blink_counter / 15) % 2;
// Clear previous dino position
fillrect(103, 90, 103 + DINO_WIDTH, 90 + DINO_HEIGHT, Black);
// Draw animated dino
draw_dino_at(103, 90, false, menu_run_frame);
// Blinking start prompt
auto style_prompt = *ui::Theme::getInstance()->fg_light;
if (++blink_counter >= 30) {
blink_counter = 0;
blink_state = !blink_state;
painter.fill_rectangle({55, 258, 130, 20}, Color::black());
if (blink_state) {
painter.draw_string({55, 260}, style_prompt, "* PRESS SELECT *");
}
}
}
void DinoGameView::show_game_over() {
if (!displayedGameOver) {
displayedGameOver = true;
// Clear the last normal dino position
if (last_ducking) {
fillrect(DINO_X, last_dino_y, DINO_X + DINO_DUCK_WIDTH, last_dino_y + DINO_DUCK_HEIGHT, Black);
} else {
fillrect(DINO_X, last_dino_y, DINO_X + DINO_WIDTH, last_dino_y + DINO_HEIGHT, Black);
}
// Draw the game over dino sprite
draw_dino_sprite(DINO_X, DINO_Y, dino_gameover);
auto style = *ui::Theme::getInstance()->fg_light;
auto style_score = *ui::Theme::getInstance()->fg_medium;
// Game over text
painter.draw_string({85, 70}, style, "GAME OVER");
// Show final score
std::string score_text = "SCORE: " + score_to_string(score);
int score_x = (240 - score_text.length() * 8) / 2;
painter.draw_string({score_x, 90}, style_score, score_text);
painter.draw_string({65, 110}, style, "SELECT TO RETRY");
// Update high score
if (score > highScore) {
highScore = score;
painter.draw_string({55, 130}, style, "NEW HIGH SCORE!");
}
}
}
void DinoGameView::game_loop() {
button_difficulty.hidden(true);
if (collided && game_state == GameState::PLAYING) {
game_state = GameState::GAME_OVER;
show_game_over();
return;
}
// Update ground animation
ground_offset = (ground_offset + GAME_SPEED_BASE + speed_modifier) % 20;
// Clear only the game area (not the whole screen to reduce flicker)
draw_ground();
// Update and draw obstacles
update_obstacles();
// Manage bird
manage_bird();
// Handle jumping
if (jumping) {
if (!falling) jumpHeight += JUMP_SPEED + speed_modifier;
if (jumpHeight > JUMP_MAX_HEIGHT && !falling) falling = true;
if (falling) jumpHeight -= JUMP_SPEED + speed_modifier;
if (jumpHeight < 0) {
falling = false;
jumping = false;
jumpHeight = 0;
}
}
// Handle auto-stand from duck
if (ducking && duck_timer > 0) {
duck_timer--;
if (duck_timer == 0) {
stand();
}
}
// Update run animation
if (get_steps() % (10 - speed_modifier) == 0) runstate = !runstate;
// Draw dino with minimal redraw
int current_dino_y = jumping ? (DINO_Y - jumpHeight) : (ducking ? DINO_DUCK_Y : DINO_Y);
// Clear old dino position more precisely
if (current_dino_y != last_dino_y || runstate != last_runstate || ducking != last_ducking) {
// Clear based on last state
if (last_ducking) {
fillrect(DINO_X, last_dino_y, DINO_X + DINO_DUCK_WIDTH, last_dino_y + DINO_DUCK_HEIGHT, Black);
} else {
fillrect(DINO_X, last_dino_y, DINO_X + DINO_WIDTH, last_dino_y + DINO_HEIGHT, Black);
}
}
// Draw dino at new position
if (jumping) {
draw_dino_sprite(DINO_X, current_dino_y, dino_default);
} else if (ducking) {
if (runstate)
draw_dino_sprite(DINO_X, current_dino_y, dino_ducking_leftstep);
else
draw_dino_sprite(DINO_X, current_dino_y, dino_ducking_rightstep);
} else {
if (runstate)
draw_dino_sprite(DINO_X, current_dino_y, dino_leftstep);
else
draw_dino_sprite(DINO_X, current_dino_y, dino_rightstep);
}
// Update last state
last_dino_y = current_dino_y;
last_ducking = ducking;
last_runstate = runstate;
// Check collisions
check_collision();
// Update score
score = get_steps() / 10;
if (score % 100 == 0 && score > 0 && get_steps() % 1000 == 0) {
if (speed_modifier < 5) speed_modifier++;
}
step();
draw_current_score();
draw_high_score();
}
void DinoGameView::draw_ground() {
int ground_y = GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT;
// Clear ground area
fillrect(0, ground_y, 320, ground_y + GROUND_HEIGHT, Black);
// Draw ground line
painter.draw_hline({0, ground_y}, 320, Color::white());
painter.draw_hline({0, ground_y + 1}, 320, Color::dark_grey());
// Draw ground texture
for (int x = -ground_offset; x < 320; x += 20) {
painter.draw_hline({x, ground_y + 3}, 10, Color::dark_grey());
painter.draw_hline({x + 5, ground_y + 5}, 5, Color::dark_grey());
}
}
void DinoGameView::update_obstacles() {
// Move obstacles
for (int i = 0; i < MAX_OBSTACLES; i++) {
if (obstacles[i].active) {
// Clear old position
if (obstacles[i].last_x != obstacles[i].x && obstacles[i].last_x < 320) {
clear_obstacle_area(obstacles[i].last_x, obstacles[i].width + 10, obstacles[i].height + 10);
}
obstacles[i].last_x = obstacles[i].x;
obstacles[i].x -= GAME_SPEED_BASE + speed_modifier;
// Remove off-screen obstacles
if (obstacles[i].x < -50) {
obstacles[i].active = false;
clear_obstacle_area(obstacles[i].last_x, obstacles[i].width + 10, obstacles[i].height + 10);
} else {
// Draw obstacle at new position
draw_obstacle(obstacles[i]);
}
}
}
// Decrement spawn timer
if (obstacle_spawn_timer > 0) {
obstacle_spawn_timer -= GAME_SPEED_BASE + speed_modifier;
}
// Check if we should spawn a new obstacle
if (obstacle_spawn_timer <= 0) {
// Try to spawn an obstacle
bool spawned = false;
for (int i = 0; i < MAX_OBSTACLES; i++) {
if (!obstacles[i].active) {
obstacles[i].active = true;
obstacles[i].x = 320;
obstacles[i].last_x = 320;
obstacles[i].type = rand() % 4;
// Set obstacle dimensions based on type
switch (obstacles[i].type) {
case 0: // Small cactus
obstacles[i].width = 15;
obstacles[i].height = 25;
break;
case 1: // Large cactus
obstacles[i].width = 20;
obstacles[i].height = 35;
break;
case 2: // Double cactus
obstacles[i].width = 35;
obstacles[i].height = 30;
break;
case 3: // Triple cactus
obstacles[i].width = 45;
obstacles[i].height = 28;
break;
}
spawned = true;
break;
}
}
if (spawned) {
// Set timer for next obstacle with random variation
obstacle_spawn_timer = MIN_OBSTACLE_DISTANCE + (rand() % (MAX_OBSTACLE_DISTANCE - MIN_OBSTACLE_DISTANCE));
}
}
}
void DinoGameView::clear_obstacle_area(int x, int width, int height) {
int ground_y = GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT;
// Only clear the obstacle area, not the entire vertical space
fillrect(x - 5, ground_y - height - 5, x + width + 5, ground_y, Black);
}
void DinoGameView::draw_obstacle(const SimpleObstacle& obstacle) {
if (!obstacle.active) return;
int ground_y = GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT;
int y = ground_y - obstacle.height;
// Draw cactus with green color
switch (obstacle.type) {
case 0: // Small single cactus
fillrect(obstacle.x + 5, y, obstacle.x + 10, ground_y, Green);
fillrect(obstacle.x, y + 8, obstacle.x + 5, y + 15, Green);
fillrect(obstacle.x + 10, y + 12, obstacle.x + 15, y + 19, Green);
// Add darker edges for definition
painter.draw_vline({obstacle.x + 4, y + 8}, 7, Color::dark_green());
painter.draw_vline({obstacle.x + 10, y + 12}, 7, Color::dark_green());
break;
case 1: // Large single cactus
fillrect(obstacle.x + 7, y, obstacle.x + 13, ground_y, Green);
fillrect(obstacle.x, y + 10, obstacle.x + 7, y + 20, Green);
fillrect(obstacle.x + 13, y + 15, obstacle.x + 20, y + 25, Green);
fillrect(obstacle.x + 5, y + 5, obstacle.x + 7, y + 12, Green);
fillrect(obstacle.x + 13, y + 8, obstacle.x + 15, y + 15, Green);
// Add darker edges
painter.draw_vline({obstacle.x + 6, y}, ground_y - y, Color::dark_green());
painter.draw_vline({obstacle.x + 13, y}, ground_y - y, Color::dark_green());
break;
case 2: // Double cactus
fillrect(obstacle.x + 5, y + 5, obstacle.x + 10, ground_y, Green);
fillrect(obstacle.x + 20, y, obstacle.x + 25, ground_y, Green);
fillrect(obstacle.x, y + 12, obstacle.x + 5, y + 18, Green);
fillrect(obstacle.x + 10, y + 15, obstacle.x + 15, y + 22, Green);
fillrect(obstacle.x + 25, y + 10, obstacle.x + 30, y + 17, Green);
// Darker outlines
painter.draw_vline({obstacle.x + 4, y + 5}, ground_y - y - 5, Color::dark_green());
painter.draw_vline({obstacle.x + 19, y}, ground_y - y, Color::dark_green());
break;
case 3: // Triple cactus
fillrect(obstacle.x + 5, y + 8, obstacle.x + 10, ground_y, Green);
fillrect(obstacle.x + 20, y, obstacle.x + 25, ground_y, Green);
fillrect(obstacle.x + 35, y + 5, obstacle.x + 40, ground_y, Green);
fillrect(obstacle.x, y + 15, obstacle.x + 5, y + 20, Green);
fillrect(obstacle.x + 25, y + 8, obstacle.x + 30, y + 15, Green);
fillrect(obstacle.x + 40, y + 12, obstacle.x + 45, y + 18, Green);
// Darker outlines
painter.draw_vline({obstacle.x + 4, y + 8}, ground_y - y - 8, Color::dark_green());
painter.draw_vline({obstacle.x + 19, y}, ground_y - y, Color::dark_green());
painter.draw_vline({obstacle.x + 34, y + 5}, ground_y - y - 5, Color::dark_green());
break;
}
}
void DinoGameView::manage_bird() {
if (!bird_info.inGame) {
// Only spawn bird if no obstacles are too close
bool obstacle_nearby = false;
for (int i = 0; i < MAX_OBSTACLES; i++) {
if (obstacles[i].active && obstacles[i].x > 200) {
obstacle_nearby = true;
break;
}
}
// Randomly spawn bird if no nearby obstacles
if (!obstacle_nearby && rand() % 400 == 0 && get_steps() > 100) {
bird_info.inGame = true;
bird_info.x_offset = 320;
bird_info.y_offset = 0;
bird_info.y_velocity = easy_mode ? 0 : (rand() % 3) - 1; // No vertical movement in easy mode
bird_info.y_position = BirdPosition::DOWN; // Always spawn at standing height in easy mode
}
} else {
// Calculate bird Y position
int base_y = (bird_info.y_position == BirdPosition::UP) ? BIRD_Y_UP : BIRD_Y_DOWN;
int current_y = base_y + bird_info.y_offset;
// Clear previous bird position
if (last_bird_x < 320 && last_bird_x >= -BIRD_WIDTH) {
int clear_start_x = last_bird_x;
int clear_end_x = last_bird_x + BIRD_WIDTH;
if (clear_start_x < 0) clear_start_x = 0;
if (clear_end_x > 320) clear_end_x = 320;
if (clear_end_x > clear_start_x) {
fillrect(clear_start_x, last_bird_y, clear_end_x, last_bird_y + BIRD_HEIGHT, Black);
}
}
// Move bird
bird_info.x_offset -= GAME_SPEED_BASE + speed_modifier + 1;
// Update vertical position only in hard mode
if (!easy_mode) {
bird_info.y_offset += bird_info.y_velocity;
if (rand() % 30 == 0) {
bird_info.y_velocity = (rand() % 3) - 1;
}
// Keep bird within bounds
if (current_y < GAME_AREA_TOP + 10) {
current_y = GAME_AREA_TOP + 10;
bird_info.y_offset = current_y - base_y;
bird_info.y_velocity = 1;
} else if (current_y > GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT - BIRD_HEIGHT - 10) {
current_y = GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT - BIRD_HEIGHT - 10;
bird_info.y_offset = current_y - base_y;
bird_info.y_velocity = -1;
}
} else {
// In easy mode, keep bird at perfect ducking height
// Bird should hit standing dino but miss ducking dino
current_y = DINO_Y - 10; // Position bird just above ducking height
}
// Animate wings
if (get_steps() % 15 == 0) bird_info.flop = !bird_info.flop;
// Draw bird only if any part is visible
if (bird_info.x_offset > -BIRD_WIDTH && bird_info.x_offset < 320) {
if (bird_info.flop) {
draw_bird_sprite(bird_info.x_offset, current_y, pterodactyl_upflop);
} else {
draw_bird_sprite(bird_info.x_offset, current_y, pterodactyl_downflop);
}
}
// Update last position
last_bird_x = bird_info.x_offset;
last_bird_y = current_y;
// Remove bird only when completely off screen
if (bird_info.x_offset < -BIRD_WIDTH - 5) {
bird_info.inGame = false;
last_bird_x = -1;
last_bird_y = -1;
}
}
}
void DinoGameView::draw_dino_sprite(int x, int y, const uint16_t* sprite) {
int height, width;
// Determine dimensions based on which sprite we're drawing
if (sprite == dino_ducking_leftstep || sprite == dino_ducking_rightstep) {
height = DINO_DUCK_HEIGHT;
width = DINO_DUCK_WIDTH;
} else {
height = DINO_HEIGHT;
width = DINO_WIDTH;
}
for (int dy = 0; dy < height; dy++) {
int run_start = -1;
uint16_t run_color = 0;
for (int dx = 0; dx < width; dx++) {
uint16_t pixel = sprite[dy * width + dx];
if (pixel != TRANSPARENT_COLOR) {
if (run_start == -1 || pixel != run_color) {
// Draw previous run if any
if (run_start != -1) {
painter.fill_rectangle({x + run_start, y + dy, dx - run_start, 1}, Color(run_color));
}
run_start = dx;
run_color = pixel;
}
} else {
// Draw previous run if any
if (run_start != -1) {
painter.fill_rectangle({x + run_start, y + dy, dx - run_start, 1}, Color(run_color));
run_start = -1;
}
}
}
// Draw final run if any
if (run_start != -1) {
painter.fill_rectangle({x + run_start, y + dy, width - run_start, 1}, Color(run_color));
}
}
}
void DinoGameView::draw_bird_sprite(int x, int y, const uint16_t* sprite) {
for (int dy = 0; dy < BIRD_HEIGHT; dy++) {
int run_start = -1;
uint16_t run_color = 0;
for (int dx = 0; dx < BIRD_WIDTH; dx++) {
// Skip pixels that would be off-screen
if (x + dx < 0 || x + dx >= 320) {
// End any current run
if (run_start != -1) {
painter.fill_rectangle({x + run_start, y + dy, dx - run_start, 1}, Color(run_color));
run_start = -1;
}
continue;
}
uint16_t pixel = sprite[dy * BIRD_WIDTH + dx];
if (pixel != TRANSPARENT_COLOR) {
if (pixel == SPRITE_COLOR) {
pixel = Color::red().v;
}
if (run_start == -1 || pixel != run_color) {
if (run_start != -1) {
painter.fill_rectangle({x + run_start, y + dy, dx - run_start, 1}, Color(run_color));
}
run_start = dx;
run_color = pixel;
}
} else {
if (run_start != -1) {
painter.fill_rectangle({x + run_start, y + dy, dx - run_start, 1}, Color(run_color));
run_start = -1;
}
}
}
if (run_start != -1 && x + run_start < 320) {
int width = BIRD_WIDTH - run_start;
if (x + run_start + width > 320) {
width = 320 - (x + run_start);
}
if (width > 0) {
painter.fill_rectangle({x + run_start, y + dy, width, 1}, Color(run_color));
}
}
}
}
void DinoGameView::draw_dino_at(int x, int y, bool is_ducking, bool run_frame) {
if (is_ducking) {
if (run_frame) {
draw_dino_sprite(x, y, dino_ducking_leftstep);
} else {
draw_dino_sprite(x, y, dino_ducking_rightstep);
}
} else {
if (run_frame) {
draw_dino_sprite(x, y, dino_leftstep);
} else {
draw_dino_sprite(x, y, dino_default);
}
}
}
void DinoGameView::check_collision() {
int dino_left = DINO_X;
int dino_right = DINO_X + (ducking ? DINO_DUCK_WIDTH : DINO_WIDTH);
int dino_top = ducking ? DINO_DUCK_Y : (DINO_Y - jumpHeight);
int dino_bottom = dino_top + (ducking ? DINO_DUCK_HEIGHT : DINO_HEIGHT);
// Give a small hitbox reduction for fairness
dino_left += 5;
dino_right -= 5;
dino_top += 5;
dino_bottom -= 5;
// Check cactus collisions
for (int i = 0; i < MAX_OBSTACLES; i++) {
if (obstacles[i].active) {
int ground_y = GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT;
int obs_top = ground_y - obstacles[i].height;
int obs_bottom = ground_y;
if (dino_right > obstacles[i].x &&
dino_left < obstacles[i].x + obstacles[i].width &&
dino_bottom > obs_top &&
dino_top < obs_bottom) {
collided = true;
return;
}
}
}
// Check bird collision
if (bird_info.inGame) {
int base_y = (bird_info.y_position == BirdPosition::UP) ? BIRD_Y_UP : BIRD_Y_DOWN;
int bird_y = base_y + bird_info.y_offset;
if (dino_right > bird_info.x_offset + 5 &&
dino_left < bird_info.x_offset + BIRD_WIDTH - 5 &&
dino_bottom > bird_y + 5 &&
dino_top < bird_y + BIRD_HEIGHT - 5) {
collided = true;
}
}
}
void DinoGameView::draw_current_score() {
auto style = *ui::Theme::getInstance()->fg_medium;
if (last_score != score) {
painter.fill_rectangle({10, 28, 60, 14}, Color::black());
painter.draw_string({10, 30}, style, score_to_string(score));
last_score = score;
}
}
void DinoGameView::draw_high_score() {
auto style = *ui::Theme::getInstance()->fg_light;
painter.fill_rectangle({152, 28, 100, 14}, Color::black());
painter.draw_string({152, 30}, style, "HI " + score_to_string(highScore));
}
void DinoGameView::jump() {
if (!jumping) {
jumping = true;
// If we're ducking when we jump, stand up
if (ducking) {
stand();
}
}
}
void DinoGameView::duck() {
if (!jumping) {
ducking = true;
duck_timer = 60; // 1 second at 60 FPS
}
}
void DinoGameView::stand() {
ducking = false;
duck_timer = 0;
}
void DinoGameView::step() {
++steps;
}
bool DinoGameView::on_key(const KeyEvent key) {
if (key == KeyEvent::Select) {
if (game_state == GameState::MENU) {
game_state = GameState::PLAYING;
new_game();
draw_high_score();
} else if (game_state == GameState::PLAYING && !collided) {
jump();
} else if (game_state == GameState::GAME_OVER || collided) {
game_state = GameState::PLAYING; // Restart immediately
new_game();
draw_high_score();
}
} else if (key == KeyEvent::Down) {
if (game_state == GameState::PLAYING && !collided) {
duck();
}
} else if (key == KeyEvent::Up) {
if (game_state == GameState::PLAYING && !collided) {
stand();
}
}
set_dirty();
return true;
}
bool DinoGameView::on_encoder(const EncoderEvent delta) {
(void)delta;
return true;
}
} // namespace ui::external_app::dinogame
+254
View File
@@ -0,0 +1,254 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
/*
* Chrome Dino Game for Portapack Mayhem
* Based on the original DinoGame by various contributors
*/
#ifndef __UI_DINOGAME_H__
#define __UI_DINOGAME_H__
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "event_m0.hpp"
#include "message.hpp"
#include "irq_controls.hpp"
#include "random.hpp"
#include "lpc43xx_cpp.hpp"
#include "ui_widget.hpp"
#include "file.hpp"
#include <string>
#include "app_settings.hpp"
namespace ui::external_app::dinogame {
// Color definitions
enum {
White,
Blue,
Yellow,
Purple,
Green,
Red,
Maroon,
Orange,
Black,
};
// Game constants
#define DINO_WIDTH 34
#define DINO_HEIGHT 36
#define DINO_DUCK_WIDTH 45
#define DINO_DUCK_HEIGHT 22
#define BIRD_WIDTH 34
#define BIRD_HEIGHT 27
#define GROUND_HEIGHT 10
#define GAME_AREA_TOP 78
#define GAME_AREA_HEIGHT 160
#define DINO_X 30
#define DINO_Y (GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT - DINO_HEIGHT)
#define DINO_DUCK_Y (GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT - DINO_DUCK_HEIGHT)
#define BIRD_Y_UP (GAME_AREA_TOP + 20)
#define BIRD_Y_DOWN (GAME_AREA_TOP + 60)
#define JUMP_MAX_HEIGHT 70
#define JUMP_SPEED 3
#define GAME_SPEED_BASE 3
#define SPRITE_COLOR 0x528A
#define TRANSPARENT_COLOR 0xFFFF
#define MIN_OBSTACLE_DISTANCE 300
#define MAX_OBSTACLE_DISTANCE 600
// Game states
enum class GameState {
MENU,
PLAYING,
GAME_OVER
};
// Walk styles
enum class WalkStyle {
WALKING,
DUCKING
};
// Bird position
enum class BirdPosition {
UP,
DOWN
};
// Structures
struct Pterodactyl {
bool inGame = false;
BirdPosition y_position = BirdPosition::UP;
int16_t x_offset = 320;
int16_t y_offset = 0;
int8_t y_velocity = 0;
bool flop = false;
};
// Simple obstacle structure with proper initialization
struct SimpleObstacle {
bool active;
int16_t x;
int16_t last_x;
uint8_t width;
uint8_t height;
uint8_t type; // 0=small, 1=large, 2=double, 3=triple
SimpleObstacle()
: active(false), x(0), last_x(0), width(0), height(0), type(0) {}
};
// External references to sprite data
extern const uint16_t dino_default[];
extern const uint16_t dino_leftstep[];
extern const uint16_t dino_rightstep[];
extern const uint16_t dino_ducking_leftstep[];
extern const uint16_t dino_ducking_rightstep[];
extern const uint16_t dino_gameover[];
extern const uint16_t pterodactyl_upflop[];
extern const uint16_t pterodactyl_downflop[];
// Global painter
extern Painter painter;
// Function declarations
void cls();
void fillrect(int x1, int y1, int x2, int y2, int color);
void rect(int x1, int y1, int x2, int y2, int color);
void check_game_timer();
void game_timer_check();
// Ticker class
using Callback = void (*)(void);
class Ticker {
public:
Ticker() = default;
void attach(Callback func, double delay_sec);
void detach();
};
// Main view class
class DinoGameView : public View {
public:
DinoGameView(NavigationView& nav);
void on_show() override;
std::string title() const override { return "Dino Game"; }
void focus() override { dummy.focus(); }
void paint(Painter& painter) override;
void frame_sync();
bool on_encoder(const EncoderEvent event) override;
bool on_key(KeyEvent key) override;
// Public for timer callback
GameState game_state = GameState::MENU;
void game_loop();
void show_menu();
private:
bool initialized = false;
NavigationView& nav_;
// Game variables
static constexpr uint8_t MAX_OBSTACLES = 1;
SimpleObstacle obstacles[MAX_OBSTACLES];
Pterodactyl bird_info;
int16_t jumpHeight = 0;
uint8_t ground_offset = 0;
uint8_t speed_modifier = 0;
bool runstate = false;
bool jumping = false;
bool falling = false;
bool collided = false;
bool ducking = false;
bool displayedGameOver = false;
bool last_ducking = false;
bool last_runstate = false;
uint32_t steps = 0;
uint32_t score = 0;
uint32_t highScore = 0;
uint32_t last_score = 999999; // Initialize to impossible value
int32_t next_obstacle_distance = 100;
int16_t last_obstacle_x = 320; // Track position of last spawned obstacle
int32_t obstacle_spawn_timer = 0;
// Position tracking for minimal redraw
int16_t last_dino_y = DINO_Y;
int16_t last_bird_x = -1;
int16_t last_bird_y = -1;
uint8_t duck_timer = 0;
// Menu animation
bool menu_initialized = false;
bool blink_state = true;
uint32_t blink_counter = 0;
// Current Score
bool score_drawn = false;
// Game timer
Ticker game_timer;
// Private methods
void init_game();
void new_game();
void update_obstacles();
void spawn_obstacle();
void draw_screen();
void draw_ground();
void draw_obstacle(const SimpleObstacle& obstacle);
void clear_obstacle_area(int x, int width, int height);
void draw_dino_sprite(int x, int y, const uint16_t* sprite);
void draw_bird_sprite(int x, int y, const uint16_t* sprite);
void draw_dino_at(int x, int y, bool is_ducking, bool run_frame);
void manage_bird();
void draw_score();
void draw_high_score();
void draw_current_score();
void show_game_over();
void jump();
void duck();
void stand();
void check_collision();
void step();
uint32_t get_steps() { return steps; }
std::string score_to_string(uint32_t score);
bool easy_mode = false;
Button button_difficulty{
{70, 195, 100, 20},
"Mode: HARD"};
app_settings::SettingsManager settings_{
"dinogame",
app_settings::Mode::NO_RF,
{{"highscore"sv, &highScore},
{"easy_mode"sv, &easy_mode}}};
Button dummy{
{screen_width, 0, 0, 0},
""};
MessageHandlerRegistration message_handler_frame_sync{
Message::ID::DisplayFrameSync,
[this](const Message* const) {
this->frame_sync();
}};
};
} // namespace ui::external_app::dinogame
#endif /* __UI_DINOGAME_H__ */
+15
View File
@@ -193,6 +193,10 @@ set(EXTCPPSRC
external/breakout/main.cpp
external/breakout/ui_breakout.cpp
#dinogame
external/dinogame/main.cpp
external/dinogame/ui_dinogame.cpp
#doom
external/doom/main.cpp
external/doom/ui_doom.cpp
@@ -212,6 +216,14 @@ set(EXTCPPSRC
#gfxEQ
external/gfxeq/main.cpp
external/gfxeq/ui_gfxeq.cpp
#detector_rx
external/detector_rx/main.cpp
external/detector_rx/ui_detector_rx.cpp
#space_invaders
external/spaceinv/main.cpp
external/spaceinv/ui_spaceinv.cpp
)
set(EXTAPPLIST
@@ -261,9 +273,12 @@ set(EXTAPPLIST
snake
stopwatch
breakout
dinogame
doom
debug_pmem
scanner
level
gfxeq
detector_rx
spaceinv
)
+23
View File
@@ -74,6 +74,9 @@ MEMORY
ram_external_app_level (rwx) : org = 0xADE10000, len = 32k
ram_external_app_gfxeq (rwx) : org = 0xADE20000, len = 32k
ram_external_app_noaaapt_rx (rwx) : org = 0xADE30000, len = 32k
ram_external_app_detector_rx (rwx) : org = 0xADE40000, len = 32k
ram_external_app_dinogame (rwx) : org = 0xADE50000, len = 32k
ram_external_app_spaceinv (rwx) : org = 0xADE60000, len = 32k
}
SECTIONS
@@ -383,4 +386,24 @@ SECTIONS
KEEP(*(.external_app.app_gfxeq.application_information));
*(*ui*external_app*gfxeq*);
} > ram_external_app_gfxeq
.external_app_detector_rx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_detector_rx.application_information));
*(*ui*external_app*detector_rx*);
} > ram_external_app_detector_rx
.external_app_dinogame : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_dinogame.application_information));
*(*ui*external_app*dinogame*);
} > ram_external_app_dinogame
.external_app_spaceinv : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_spaceinv.application_information));
*(*ui*external_app*spaceinv*);
} > ram_external_app_spaceinv
}
+1 -1
View File
@@ -38,7 +38,7 @@ __attribute__((section(".external_app.app_fmradio.application_information"), use
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "FM Radio",
/*.app_name = */ "Radio",
/*.bitmap_data = */ {
0x00,
0x00,
+168 -54
View File
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2024 HTotoo
* Copyright (C) 2025 RocketGod
*
* This file is part of PortaPack.
*
@@ -26,6 +27,7 @@
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "portapack_persistent_memory.hpp"
#include "oversample.hpp"
using namespace portapack;
using namespace modems;
@@ -33,20 +35,118 @@ using namespace ui;
namespace ui::external_app::fmradio {
#include "external/ui_grapheq.cpi"
void FmRadioView::focus() {
field_frequency.focus();
}
void FmRadioView::show_hide_gfx(bool show) {
gr.hidden(!show);
gr.set_paused(!show);
waveform.set_paused(show);
btn_fav_0.hidden(show);
btn_fav_1.hidden(show);
btn_fav_2.hidden(show);
btn_fav_3.hidden(show);
btn_fav_4.hidden(show);
btn_fav_5.hidden(show);
btn_fav_6.hidden(show);
btn_fav_7.hidden(show);
btn_fav_8.hidden(show);
btn_fav_9.hidden(show);
txt_save_help.hidden(show);
btn_fav_save.hidden(show);
field_bw.hidden(show);
field_modulation.hidden(show);
text_mode_label.hidden(show);
set_dirty();
}
void FmRadioView::change_mode(int32_t mod) {
field_bw.on_change = [this](size_t n, OptionsField::value_t) { (void)n; };
audio::output::stop();
receiver_model.disable();
baseband::shutdown();
audio_spectrum_update = false; // Reset spectrum update flag
std::fill(audio_spectrum, audio_spectrum + 128, 0); // Clear spectrum buffer
waveform.set_dirty();
receiver_mode = static_cast<ReceiverModel::Mode>(mod);
bool is_ssb = (mod == static_cast<int32_t>(ReceiverModel::Mode::AMAudio) &&
(field_modulation.selected_index() == 3 || field_modulation.selected_index() == 4));
switch (mod) {
case static_cast<int32_t>(ReceiverModel::Mode::AMAudio):
audio_sampling_rate = audio::Rate::Hz_24000; // Increased to 24 kHz for better AM/SSB audio
freqman_set_bandwidth_option(0, field_bw); // AM_MODULATION
baseband::run_image(portapack::spi_flash::image_tag_am_audio);
receiver_mode = ReceiverModel::Mode::AMAudio;
field_bw.set_by_value(0); // DSB default
receiver_model.set_modulation(receiver_mode);
if (is_ssb) {
receiver_model.set_am_configuration(field_modulation.selected_index() == 3 ? 1 : 2); // 1=USB, 2=LSB
} else {
receiver_model.set_am_configuration(0); // DSB
}
field_bw.on_change = [this](size_t index, OptionsField::value_t n) {
radio_bw = index;
receiver_model.set_am_configuration(n);
};
show_hide_gfx(false);
break;
case static_cast<int32_t>(ReceiverModel::Mode::NarrowbandFMAudio):
audio_sampling_rate = audio::Rate::Hz_24000;
freqman_set_bandwidth_option(1, field_bw); // NFM_MODULATION
baseband::run_image(portapack::spi_flash::image_tag_nfm_audio);
receiver_mode = ReceiverModel::Mode::NarrowbandFMAudio;
field_bw.set_by_value(2); // 16k default
receiver_model.set_nbfm_configuration(field_bw.selected_index_value());
field_bw.on_change = [this](size_t index, OptionsField::value_t n) {
radio_bw = index;
receiver_model.set_nbfm_configuration(n);
};
show_hide_gfx(false);
break;
case static_cast<int32_t>(ReceiverModel::Mode::WidebandFMAudio):
audio_sampling_rate = audio::Rate::Hz_48000;
freqman_set_bandwidth_option(2, field_bw); // WFM_MODULATION
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
receiver_mode = ReceiverModel::Mode::WidebandFMAudio;
field_bw.set_by_value(0); // 200k default
receiver_model.set_wfm_configuration(field_bw.selected_index_value());
field_bw.on_change = [this](size_t index, OptionsField::value_t n) {
radio_bw = index;
receiver_model.set_wfm_configuration(n);
};
break;
default:
break;
}
receiver_model.set_modulation(receiver_mode);
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
audio::set_rate(audio_sampling_rate);
audio::output::start();
receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // WM8731 hack
receiver_model.enable();
}
FmRadioView::FmRadioView(NavigationView& nav)
: nav_{nav} {
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
add_children({&rssi,
&field_rf_amp,
add_children({&field_rf_amp,
&field_lna,
&field_vga,
&field_volume,
&field_frequency,
&field_bw,
&text_mode_label,
&field_modulation,
&btn_fav_save,
&txt_save_help,
&btn_fav_0,
@@ -60,12 +160,16 @@ FmRadioView::FmRadioView(NavigationView& nav)
&btn_fav_8,
&btn_fav_9,
&audio,
&waveform});
&waveform,
&rssi,
&gr});
txt_save_help.set_focusable(false);
txt_save_help.visible(false);
for (uint8_t i = 0; i < 12; ++i) {
if (freq_fav_list[i] == 0) {
freq_fav_list[i] = 87000000;
if (freq_fav_list[i].frequency == 0) {
freq_fav_list[i].frequency = 87000000;
freq_fav_list[i].modulation = static_cast<int32_t>(ReceiverModel::Mode::WidebandFMAudio);
}
}
@@ -73,42 +177,20 @@ FmRadioView::FmRadioView(NavigationView& nav)
field_frequency.set_value(87000000);
}
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
field_frequency.set_step(25000);
receiver_model.enable();
audio::output::start();
change_mode(static_cast<int32_t>(ReceiverModel::Mode::WidebandFMAudio));
field_modulation.set_by_value(static_cast<int32_t>(ReceiverModel::Mode::WidebandFMAudio));
btn_fav_0.on_select = [this](Button&) {
on_btn_clicked(0);
};
btn_fav_1.on_select = [this](Button&) {
on_btn_clicked(1);
};
btn_fav_2.on_select = [this](Button&) {
on_btn_clicked(2);
};
btn_fav_3.on_select = [this](Button&) {
on_btn_clicked(3);
};
btn_fav_4.on_select = [this](Button&) {
on_btn_clicked(4);
};
btn_fav_5.on_select = [this](Button&) {
on_btn_clicked(5);
};
btn_fav_6.on_select = [this](Button&) {
on_btn_clicked(6);
};
btn_fav_7.on_select = [this](Button&) {
on_btn_clicked(7);
};
btn_fav_8.on_select = [this](Button&) {
on_btn_clicked(8);
};
btn_fav_9.on_select = [this](Button&) {
on_btn_clicked(9);
};
btn_fav_0.on_select = [this](Button&) { on_btn_clicked(0); };
btn_fav_1.on_select = [this](Button&) { on_btn_clicked(1); };
btn_fav_2.on_select = [this](Button&) { on_btn_clicked(2); };
btn_fav_3.on_select = [this](Button&) { on_btn_clicked(3); };
btn_fav_4.on_select = [this](Button&) { on_btn_clicked(4); };
btn_fav_5.on_select = [this](Button&) { on_btn_clicked(5); };
btn_fav_6.on_select = [this](Button&) { on_btn_clicked(6); };
btn_fav_7.on_select = [this](Button&) { on_btn_clicked(7); };
btn_fav_8.on_select = [this](Button&) { on_btn_clicked(8); };
btn_fav_9.on_select = [this](Button&) { on_btn_clicked(9); };
btn_fav_save.on_select = [this](Button&) {
save_fav = !save_fav;
@@ -117,20 +199,44 @@ FmRadioView::FmRadioView(NavigationView& nav)
txt_save_help.set_dirty();
};
field_modulation.on_change = [this](size_t index, int32_t mod) {
change_mode(mod);
if (index == 3 || index == 4) { // USB or LSB
receiver_model.set_am_configuration(index == 3 ? 1 : 2); // 1=USB, 2=LSB
}
};
waveform.on_select = [this](Waveform&) {
if (receiver_mode != ReceiverModel::Mode::WidebandFMAudio) { // only there is spectrum message
return;
}
show_hide_gfx(!btn_fav_0.hidden());
};
gr.set_theme(themes[current_theme].base_color, themes[current_theme].peak_color);
gr.on_select = [this](GraphEq&) {
current_theme = (current_theme + 1) % themes.size();
gr.set_theme(themes[current_theme].base_color, themes[current_theme].peak_color);
gr.set_paused(false);
};
update_fav_btn_texts();
show_hide_gfx(false);
}
void FmRadioView::on_btn_clicked(uint8_t i) {
if (save_fav) {
save_fav = false;
freq_fav_list[i] = field_frequency.value();
freq_fav_list[i].frequency = field_frequency.value();
freq_fav_list[i].modulation = field_modulation.selected_index_value();
freq_fav_list[i].bandwidth = radio_bw;
update_fav_btn_texts();
txt_save_help.visible(save_fav);
txt_save_help.set_text("");
txt_save_help.set_dirty();
return;
}
field_frequency.set_value(freq_fav_list[i]);
field_frequency.set_value(freq_fav_list[i].frequency);
field_modulation.set_by_value(freq_fav_list[i].modulation);
change_mode(freq_fav_list[i].modulation);
}
std::string FmRadioView::to_nice_freq(rf::Frequency freq) {
@@ -141,16 +247,16 @@ std::string FmRadioView::to_nice_freq(rf::Frequency freq) {
}
void FmRadioView::update_fav_btn_texts() {
btn_fav_0.set_text(to_nice_freq(freq_fav_list[0]));
btn_fav_1.set_text(to_nice_freq(freq_fav_list[1]));
btn_fav_2.set_text(to_nice_freq(freq_fav_list[2]));
btn_fav_3.set_text(to_nice_freq(freq_fav_list[3]));
btn_fav_4.set_text(to_nice_freq(freq_fav_list[4]));
btn_fav_5.set_text(to_nice_freq(freq_fav_list[5]));
btn_fav_6.set_text(to_nice_freq(freq_fav_list[6]));
btn_fav_7.set_text(to_nice_freq(freq_fav_list[7]));
btn_fav_8.set_text(to_nice_freq(freq_fav_list[8]));
btn_fav_9.set_text(to_nice_freq(freq_fav_list[9]));
btn_fav_0.set_text(to_nice_freq(freq_fav_list[0].frequency));
btn_fav_1.set_text(to_nice_freq(freq_fav_list[1].frequency));
btn_fav_2.set_text(to_nice_freq(freq_fav_list[2].frequency));
btn_fav_3.set_text(to_nice_freq(freq_fav_list[3].frequency));
btn_fav_4.set_text(to_nice_freq(freq_fav_list[4].frequency));
btn_fav_5.set_text(to_nice_freq(freq_fav_list[5].frequency));
btn_fav_6.set_text(to_nice_freq(freq_fav_list[6].frequency));
btn_fav_7.set_text(to_nice_freq(freq_fav_list[7].frequency));
btn_fav_8.set_text(to_nice_freq(freq_fav_list[8].frequency));
btn_fav_9.set_text(to_nice_freq(freq_fav_list[9].frequency));
}
FmRadioView::~FmRadioView() {
@@ -160,9 +266,17 @@ FmRadioView::~FmRadioView() {
}
void FmRadioView::on_audio_spectrum() {
for (size_t i = 0; i < audio_spectrum_data->db.size(); i++)
audio_spectrum[i] = ((int16_t)audio_spectrum_data->db[i] - 127) * 256;
waveform.set_dirty();
if (gr.visible() && audio_spectrum_data) gr.update_audio_spectrum(*audio_spectrum_data);
if (audio_spectrum_data && audio_spectrum_data->db.size() <= 128) {
for (size_t i = 0; i < audio_spectrum_data->db.size(); ++i) {
audio_spectrum[i] = ((int16_t)audio_spectrum_data->db[i] - 127) * 256;
}
waveform.set_dirty();
} else {
// Fallback: Clear waveform if no valid data
std::fill(audio_spectrum, audio_spectrum + 128, 0);
waveform.set_dirty();
}
}
} // namespace ui::external_app::fmradio
+107 -16
View File
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2024 HTotoo
* Copyright (C) 2025 RocketGod
*
* This file is part of PortaPack.
*
@@ -38,11 +39,16 @@
#include "radio_state.hpp"
#include "log_file.hpp"
#include "utility.hpp"
#include "audio.hpp"
#include "freqman_db.hpp"
#include "ui_freqman.hpp"
using namespace ui;
namespace ui::external_app::fmradio {
#include "external/ui_grapheq.hpp"
#define FMR_BTNGRID_TOP 60
class FmRadioView : public View {
@@ -54,31 +60,65 @@ class FmRadioView : public View {
void focus() override;
std::string title() const override { return "FM radio"; };
std::string title() const override { return "Radio"; };
private:
NavigationView& nav_;
RxRadioState radio_state_{};
int16_t audio_spectrum[128]{0};
bool audio_spectrum_update = false;
ReceiverModel::Mode receiver_mode = ReceiverModel::Mode::WidebandFMAudio;
AudioSpectrum* audio_spectrum_data{nullptr};
rf::Frequency freq_fav_list[12] = {0};
struct Favorite {
rf::Frequency frequency = 0;
int32_t modulation = static_cast<int32_t>(ReceiverModel::Mode::WidebandFMAudio);
uint8_t bandwidth = 0;
};
Favorite freq_fav_list[12];
audio::Rate audio_sampling_rate = audio::Rate::Hz_48000;
uint8_t radio_bw = 0;
uint32_t current_theme{0};
app_settings::SettingsManager settings_{
"rx_fmradio",
app_settings::Mode::RX,
{{"favlist0"sv, &freq_fav_list[0]},
{"favlist1"sv, &freq_fav_list[1]},
{"favlist2"sv, &freq_fav_list[2]},
{"favlist3"sv, &freq_fav_list[3]},
{"favlist4"sv, &freq_fav_list[4]},
{"favlist5"sv, &freq_fav_list[5]},
{"favlist6"sv, &freq_fav_list[6]},
{"favlist7"sv, &freq_fav_list[7]},
{"favlist8"sv, &freq_fav_list[8]},
{"favlist9"sv, &freq_fav_list[9]},
{"favlist10"sv, &freq_fav_list[10]},
{"favlist11"sv, &freq_fav_list[11]}}};
{{"favlist0_freq"sv, &freq_fav_list[0].frequency},
{"favlist1_freq"sv, &freq_fav_list[1].frequency},
{"favlist2_freq"sv, &freq_fav_list[2].frequency},
{"favlist3_freq"sv, &freq_fav_list[3].frequency},
{"favlist4_freq"sv, &freq_fav_list[4].frequency},
{"favlist5_freq"sv, &freq_fav_list[5].frequency},
{"favlist6_freq"sv, &freq_fav_list[6].frequency},
{"favlist7_freq"sv, &freq_fav_list[7].frequency},
{"favlist8_freq"sv, &freq_fav_list[8].frequency},
{"favlist9_freq"sv, &freq_fav_list[9].frequency},
{"favlist10_freq"sv, &freq_fav_list[10].frequency},
{"favlist11_freq"sv, &freq_fav_list[11].frequency},
{"favlist0_mod"sv, &freq_fav_list[0].modulation},
{"favlist1_mod"sv, &freq_fav_list[1].modulation},
{"favlist2_mod"sv, &freq_fav_list[2].modulation},
{"favlist3_mod"sv, &freq_fav_list[3].modulation},
{"favlist4_mod"sv, &freq_fav_list[4].modulation},
{"favlist5_mod"sv, &freq_fav_list[5].modulation},
{"favlist6_mod"sv, &freq_fav_list[6].modulation},
{"favlist7_mod"sv, &freq_fav_list[7].modulation},
{"favlist8_mod"sv, &freq_fav_list[8].modulation},
{"favlist9_mod"sv, &freq_fav_list[9].modulation},
{"favlist10_mod"sv, &freq_fav_list[10].modulation},
{"favlist11_mod"sv, &freq_fav_list[11].modulation},
{"favlist0_bw"sv, &freq_fav_list[0].bandwidth},
{"favlist1_bw"sv, &freq_fav_list[1].bandwidth},
{"favlist2_bw"sv, &freq_fav_list[2].bandwidth},
{"favlist3_bw"sv, &freq_fav_list[3].bandwidth},
{"favlist4_bw"sv, &freq_fav_list[4].bandwidth},
{"favlist5_bw"sv, &freq_fav_list[5].bandwidth},
{"favlist6_bw"sv, &freq_fav_list[6].bandwidth},
{"favlist7_bw"sv, &freq_fav_list[7].bandwidth},
{"favlist8_bw"sv, &freq_fav_list[8].bandwidth},
{"favlist9_bw"sv, &freq_fav_list[9].bandwidth},
{"favlist10_bw"sv, &freq_fav_list[10].bandwidth},
{"favlist11_bw"sv, &freq_fav_list[11].bandwidth},
{"radio_bw"sv, &radio_bw},
{"theme"sv, &current_theme}}};
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
@@ -95,6 +135,24 @@ class FmRadioView : public View {
{0 * 8, 0 * 16},
nav_};
OptionsField field_bw{
{10 * 8, FMR_BTNGRID_TOP + 6 * 34},
6,
{}};
Text text_mode_label{
{20 * 8, FMR_BTNGRID_TOP + 6 * 34, 5 * 8, 1 * 28},
"MODE:"};
OptionsField field_modulation{
{26 * 8, FMR_BTNGRID_TOP + 6 * 34},
4,
{{"AM", static_cast<int32_t>(ReceiverModel::Mode::AMAudio)},
{"NFM", static_cast<int32_t>(ReceiverModel::Mode::NarrowbandFMAudio)},
{"WFM", static_cast<int32_t>(ReceiverModel::Mode::WidebandFMAudio)},
{"USB", static_cast<int32_t>(ReceiverModel::Mode::AMAudio)},
{"LSB", static_cast<int32_t>(ReceiverModel::Mode::AMAudio)}}};
TextField txt_save_help{
{2, FMR_BTNGRID_TOP + 6 * 34 - 20, 12 * 8, 16},
" "};
@@ -103,7 +161,7 @@ class FmRadioView : public View {
{21 * 8, 10, 6 * 8, 4}};
Waveform waveform{
{0, 20, screen_width, 2 * 16},
{0, 20, UI_POS_MAXWIDTH, 2 * 16},
audio_spectrum,
128,
0,
@@ -111,6 +169,8 @@ class FmRadioView : public View {
Theme::getInstance()->bg_darkest->foreground,
true};
GraphEq gr{{2, FMR_BTNGRID_TOP, UI_POS_MAXWIDTH - 4, UI_POS_MAXHEIGHT - FMR_BTNGRID_TOP}, true};
Button btn_fav_0{{2, FMR_BTNGRID_TOP + 0 * 34, 10 * 8, 28}, "---"};
Button btn_fav_1{{2 + 15 * 8, FMR_BTNGRID_TOP + 0 * 34, 10 * 8, 28}, "---"};
Button btn_fav_2{{2, FMR_BTNGRID_TOP + 1 * 34, 10 * 8, 28}, "---"};
@@ -124,10 +184,41 @@ class FmRadioView : public View {
Button btn_fav_save{{2, FMR_BTNGRID_TOP + 6 * 34, 7 * 8, 1 * 28}, "Save"};
bool save_fav = false;
void on_btn_clicked(uint8_t i);
void update_fav_btn_texts();
std::string to_nice_freq(rf::Frequency freq);
void on_audio_spectrum();
void change_mode(int32_t mod);
void show_hide_gfx(bool show);
struct ColorTheme {
Color base_color;
Color peak_color;
};
const std::array<ColorTheme, 20> themes{
ColorTheme{Color(255, 0, 255), Color(255, 255, 255)},
ColorTheme{Color(0, 255, 0), Color(255, 0, 0)},
ColorTheme{Color(0, 0, 255), Color(255, 255, 0)},
ColorTheme{Color(255, 128, 0), Color(255, 0, 128)},
ColorTheme{Color(128, 0, 255), Color(0, 255, 255)},
ColorTheme{Color(255, 255, 0), Color(0, 255, 128)},
ColorTheme{Color(255, 0, 0), Color(0, 128, 255)},
ColorTheme{Color(0, 255, 128), Color(255, 128, 255)},
ColorTheme{Color(128, 128, 128), Color(255, 255, 255)},
ColorTheme{Color(255, 64, 0), Color(0, 255, 64)},
ColorTheme{Color(0, 128, 128), Color(255, 192, 0)},
ColorTheme{Color(0, 255, 0), Color(0, 128, 0)},
ColorTheme{Color(32, 64, 32), Color(0, 255, 0)},
ColorTheme{Color(64, 0, 128), Color(255, 0, 255)},
ColorTheme{Color(0, 64, 0), Color(0, 255, 128)},
ColorTheme{Color(255, 255, 255), Color(0, 0, 255)},
ColorTheme{Color(128, 0, 0), Color(255, 128, 0)},
ColorTheme{Color(0, 128, 255), Color(255, 255, 128)},
ColorTheme{Color(64, 64, 64), Color(255, 0, 0)},
ColorTheme{Color(255, 192, 0), Color(0, 64, 128)}};
MessageHandlerRegistration message_handler_audio_spectrum{
Message::ID::AudioSpectrum,
+6 -104
View File
@@ -20,10 +20,12 @@ using namespace portapack;
namespace ui::external_app::gfxeq {
#include "external/ui_grapheq.cpi"
gfxEQView::gfxEQView(NavigationView& nav)
: nav_{nav}, bar_heights(NUM_BARS, 0), prev_bar_heights(NUM_BARS, 0) {
: nav_{nav} {
add_children({&button_frequency, &field_rf_amp, &field_lna, &field_vga,
&button_mood, &field_volume});
&button_mood, &field_volume, &gr});
audio::output::stop();
receiver_model.disable();
@@ -69,6 +71,7 @@ gfxEQView::gfxEQView(NavigationView& nav)
};
button_mood.on_select = [this](Button&) { this->cycle_theme(); };
gr.set_theme(themes[current_theme].base_color, themes[current_theme].peak_color);
}
// needed to answer usb serial frequency set
@@ -87,110 +90,9 @@ void gfxEQView::focus() {
button_frequency.focus();
}
void gfxEQView::on_show() {
needs_background_redraw = true;
}
void gfxEQView::on_hide() {
needs_background_redraw = true;
}
void gfxEQView::update_audio_spectrum(const AudioSpectrum& spectrum) {
const float bin_frequency_size = 48000.0f / 128;
for (int bar = 0; bar < NUM_BARS; bar++) {
float start_freq = FREQUENCY_BANDS[bar];
float end_freq = FREQUENCY_BANDS[bar + 1];
int start_bin = std::max(1, (int)(start_freq / bin_frequency_size));
int end_bin = std::min(127, (int)(end_freq / bin_frequency_size));
if (start_bin >= end_bin) {
end_bin = start_bin + 1;
}
float total_energy = 0;
int bin_count = 0;
for (int bin = start_bin; bin <= end_bin; bin++) {
total_energy += spectrum.db[bin];
bin_count++;
}
float avg_db = bin_count > 0 ? (total_energy / bin_count) : 0;
// Manually boost highs for better visual balance
float treble_boost = 1.0f;
if (bar == 10)
treble_boost = 1.7f;
else if (bar >= 9)
treble_boost = 1.3f;
else if (bar >= 7)
treble_boost = 1.3f;
// Mid emphasis for a V-shape effect
float mid_boost = 1.0f;
if (bar == 4 || bar == 5 || bar == 6) mid_boost = 1.2f;
float amplified_db = avg_db * treble_boost * mid_boost;
if (amplified_db > 255) amplified_db = 255;
float band_scale = 1.0f;
int target_height = (amplified_db * RENDER_HEIGHT * band_scale) / 255;
if (target_height > RENDER_HEIGHT) {
target_height = RENDER_HEIGHT;
}
// Adjusted to look nice to my eyes
float rise_speed = 0.8f;
float fall_speed = 1.0f;
if (target_height > bar_heights[bar]) {
bar_heights[bar] = bar_heights[bar] * (1.0f - rise_speed) + target_height * rise_speed;
} else {
bar_heights[bar] = bar_heights[bar] * (1.0f - fall_speed) + target_height * fall_speed;
}
}
}
void gfxEQView::render_equalizer(Painter& painter) {
const int num_segments = RENDER_HEIGHT / SEGMENT_HEIGHT;
const ColorTheme& theme = themes[current_theme];
for (int bar = 0; bar < NUM_BARS; bar++) {
int x = HORIZONTAL_OFFSET + bar * (BAR_WIDTH + BAR_SPACING);
int active_segments = (bar_heights[bar] * num_segments) / RENDER_HEIGHT;
if (prev_bar_heights[bar] > active_segments) {
int clear_height = (prev_bar_heights[bar] - active_segments) * SEGMENT_HEIGHT;
int clear_y = screen_height - prev_bar_heights[bar] * SEGMENT_HEIGHT;
painter.fill_rectangle({x, clear_y, BAR_WIDTH, clear_height}, Color(0, 0, 0));
}
for (int seg = 0; seg < active_segments; seg++) {
int y = screen_height - (seg + 1) * SEGMENT_HEIGHT;
if (y < header_height) break;
Color segment_color = (seg >= active_segments - 2 && seg < active_segments) ? theme.peak_color : theme.base_color;
painter.fill_rectangle({x, y, BAR_WIDTH, SEGMENT_HEIGHT - 1}, segment_color);
}
prev_bar_heights[bar] = active_segments;
}
}
void gfxEQView::paint(Painter& painter) {
if (needs_background_redraw) {
painter.fill_rectangle({0, header_height, screen_width, RENDER_HEIGHT}, Color(0, 0, 0));
needs_background_redraw = false;
}
render_equalizer(painter);
}
void gfxEQView::cycle_theme() {
current_theme = (current_theme + 1) % themes.size();
gr.set_theme(themes[current_theme].base_color, themes[current_theme].peak_color);
}
} // namespace ui::external_app::gfxeq
+5 -33
View File
@@ -22,6 +22,8 @@
namespace ui::external_app::gfxeq {
#include "external/ui_grapheq.hpp"
class gfxEQView : public View {
public:
gfxEQView(NavigationView& nav);
@@ -32,45 +34,17 @@ class gfxEQView : public View {
void focus() override;
std::string title() const override { return "gfxEQ"; }
void on_show() override;
void on_hide() override;
void paint(Painter& painter) override;
void on_freqchg(int64_t freq);
private:
static constexpr ui::Dim header_height = 2 * 16;
static constexpr int RENDER_HEIGHT = 288;
static constexpr int NUM_BARS = 11;
static constexpr int BAR_SPACING = 2;
int BAR_WIDTH = (screen_width - (BAR_SPACING * (NUM_BARS - 1))) / NUM_BARS;
static constexpr int HORIZONTAL_OFFSET = 2;
static constexpr int SEGMENT_HEIGHT = 10;
static constexpr std::array<int, NUM_BARS + 1> FREQUENCY_BANDS = {
375, // Bass warmth and low rumble (e.g., deep basslines, kick drum body)
750, // Upper bass punch (e.g., bass guitar punch, kick drum attack)
1500, // Lower midrange fullness (e.g., warmth in vocals, guitar body)
2250, // Midrange clarity (e.g., vocal presence, snare crack)
3375, // Upper midrange bite (e.g., instrument definition, vocal articulation)
4875, // Presence and edge (e.g., guitar bite, vocal sibilance start)
6750, // Lower brilliance (e.g., cymbal shimmer, vocal clarity)
9375, // Brilliance and air (e.g., hi-hat crispness, breathy vocals)
13125, // High treble sparkle (e.g., subtle overtones, synth shimmer)
16875, // Upper treble airiness (e.g., faint harmonics, room ambiance)
20625, // Top-end sheen (e.g., ultra-high harmonics, noise floor)
24375 // Extreme treble limit (e.g., inaudible overtones, signal cutoff, static)
};
struct ColorTheme {
Color base_color;
Color peak_color;
};
NavigationView& nav_;
bool needs_background_redraw{false};
std::vector<int> bar_heights;
std::vector<int> prev_bar_heights;
uint32_t current_theme{0};
const std::array<ColorTheme, 20> themes{
ColorTheme{Color(255, 0, 255), Color(255, 255, 255)},
@@ -100,6 +74,7 @@ class gfxEQView : public View {
VGAGainField field_vga{{18 * 8, 0 * 16}};
Button button_mood{{21 * 8, 0, 6 * 8, 16}, "MOOD"};
AudioVolumeField field_volume{{screen_width - 2 * 8, 0 * 16}};
GraphEq gr{{2, UI_POS_DEFAULT_HEIGHT, UI_POS_MAXWIDTH - 4, UI_POS_HEIGHT_REMAINING(2)}, false};
rf::Frequency frequency_value{93100000};
@@ -111,16 +86,13 @@ class gfxEQView : public View {
{{"theme", &current_theme},
{"frequency", &frequency_value}}};
void update_audio_spectrum(const AudioSpectrum& spectrum);
void render_equalizer(Painter& painter);
void cycle_theme();
MessageHandlerRegistration message_handler_audio_spectrum{
Message::ID::AudioSpectrum,
[this](const Message* const p) {
const auto message = *reinterpret_cast<const AudioSpectrumMessage*>(p);
this->update_audio_spectrum(*message.data);
this->set_dirty();
this->gr.update_audio_spectrum(*message.data);
}};
MessageHandlerRegistration message_handler_freqchg{
+20 -5
View File
@@ -78,7 +78,7 @@ void HopperView::set_hopper_channel(uint32_t i, uint32_t width, uint64_t center,
hopper_channels[i].enabled = true;
hopper_channels[i].width = (width * 0xFFFFFFULL) / 1536000;
hopper_channels[i].center = center;
hopper_channels[i].duration = 30720 * duration;
hopper_channels[i].duration = duration ? 3072 * duration : 10;
}
void HopperView::start_tx() {
@@ -274,7 +274,7 @@ HopperView::HopperView(
options_type.set_selected_index(3); // Rand CW
options_speed.set_selected_index(3); // 10kHz
options_hop.set_selected_index(1); // 50ms
options_hop.set_selected_index(3); // 50ms
button_transmit.set_style(&style_val);
field_timetx.set_value(30);
@@ -327,10 +327,25 @@ HopperView::HopperView(
};
button_transmit.on_select = [this](Button&) {
if (jamming || cooling)
if (jamming || cooling) {
stop_tx();
else
start_tx();
} else {
// if hop speed is 0, alert the user that this will cause a freeze on UI
if (options_hop.selected_index_value() == 0) {
nav_.display_modal(
"Warning", "Hopping set to 0ms (fastest).\n\nTHIS WILL FREEZE THE HACKRF,\npress RESET button to stop\n\nAre you sure?", YESNO, [this](bool choice) {
if (choice) {
// Wait for UI update before the freeze
chThdSleepMilliseconds(50);
start_tx();
}
},
TRUE);
} else {
// if hop speed is not 0, just start the transmission
start_tx();
}
}
};
menu_freq_list.on_left = [this]() {
+17 -8
View File
@@ -143,6 +143,13 @@ class HopperView : public View {
{"FM tone", 1},
{"CW sweep", 2},
{"Rand CW", 3},
{"Sine", 4},
{"Square", 5},
{"Sawtooth", 6},
{"Triangle", 7},
{"Chirp", 8},
{"Gauss", 9},
{"Brute", 10},
}};
Text text_range_number{
@@ -163,14 +170,16 @@ class HopperView : public View {
OptionsField options_hop{
{7 * 8, 27 * 8},
5,
{{"10ms ", 1},
{"50ms ", 5},
{"100ms", 10},
{"1s ", 100},
{"2s ", 200},
{"5s ", 500},
{"10s ", 1000}}};
6,
{{"0ms !!", 0},
{"1ms ", 1},
{"10ms ", 10},
{"50ms ", 50},
{"100ms", 100},
{"1s ", 1000},
{"2s ", 2000},
{"5s ", 5000},
{"10s ", 10000}}};
NumberField field_timetx{
{7 * 8, 29 * 8},
+129 -48
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 : 3000;
}
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)
+29 -25
View File
@@ -41,27 +41,12 @@ class RangeView : public View {
RangeView(NavigationView& nav);
void focus() override;
void paint(Painter&) override;
void paint(Painter& painter) override;
jammer_range_t frequency_range{false, 0, 0};
private:
void update_start(rf::Frequency f);
void update_stop(rf::Frequency f);
void update_center(rf::Frequency f);
void update_width(uint32_t w);
uint32_t width{};
rf::Frequency center{};
const Style& style_info = *Theme::getInstance()->fg_medium;
Labels labels{
{{2 * 8, 8 * 8 + 4}, LanguageHelper::currentMessages[LANG_START], Theme::getInstance()->fg_light->foreground},
{{23 * 8, 8 * 8 + 4}, LanguageHelper::currentMessages[LANG_STOP], Theme::getInstance()->fg_light->foreground},
{{12 * 8, 5 * 8 - 4}, "Center", Theme::getInstance()->fg_light->foreground},
{{12 * 8 + 4, 13 * 8}, "Width", Theme::getInstance()->fg_light->foreground}};
Checkbox check_enabled{
{1 * 8, 4},
12,
@@ -74,15 +59,32 @@ class RangeView : public View {
Button button_start{
{0 * 8, 11 * 8, 11 * 8, 28},
""};
Button button_stop{
{19 * 8, 11 * 8, 11 * 8, 28},
""};
Button button_center{
{76, 4 * 15 - 4, 11 * 8, 28},
""};
Button button_width{
{76, 8 * 15, 11 * 8, 28},
""};
void update_start(rf::Frequency f);
void update_stop(rf::Frequency f);
void update_center(rf::Frequency f);
void update_width(uint32_t w);
private:
const Style& style_info = *Theme::getInstance()->fg_medium;
Labels labels{
{{2 * 8, 8 * 8 + 4}, LanguageHelper::currentMessages[LANG_START], Theme::getInstance()->fg_light->foreground},
{{23 * 8, 8 * 8 + 4}, LanguageHelper::currentMessages[LANG_STOP], Theme::getInstance()->fg_light->foreground},
{{12 * 8, 5 * 8 - 4}, "Center", Theme::getInstance()->fg_light->foreground},
{{12 * 8 + 4, 13 * 8}, "Width", Theme::getInstance()->fg_light->foreground}};
};
class JammerView : public View {
@@ -110,15 +112,16 @@ class JammerView : public View {
void start_tx();
void on_timer();
void stop_tx();
bool update_config();
void set_jammer_channel(uint32_t i, uint32_t width, uint64_t center, uint32_t duration);
void on_retune(const rf::Frequency freq, const uint32_t range);
JammerChannel* jammer_channels = (JammerChannel*)shared_memory.bb_data.data;
bool jamming{false};
bool cooling{false}; // euquiq: Indicates jammer in cooldown
uint16_t seconds = 0; // euquiq: seconds counter for toggling tx / cooldown
int16_t mscounter = 0; // euquiq: Internal ms counter for do_timer()
lfsr_word_t lfsr_v = 1; // euquiq: Used to generate "random" Jitter
bool cooling{false};
uint16_t seconds{0};
int16_t mscounter{0};
lfsr_word_t lfsr_v{1};
const Style& style_val = *Theme::getInstance()->fg_green;
const Style& style_cancel = *Theme::getInstance()->fg_red;
@@ -140,8 +143,8 @@ class JammerView : public View {
{{1 * 8, 25 * 8}, "Speed:", Theme::getInstance()->fg_light->foreground},
{{3 * 8, 27 * 8}, "Hop:", Theme::getInstance()->fg_light->foreground},
{{4 * 8, 29 * 8}, "TX:", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 31 * 8}, "Sle3p:", Theme::getInstance()->fg_light->foreground}, // euquiq: Token of appreciation to TheSle3p, which made this ehnancement a reality with his bounty.
{{0 * 8, 33 * 8}, "Jitter:", Theme::getInstance()->fg_light->foreground}, // Maybe the repository curator can keep the "mystype" for some versions.
{{1 * 8, 31 * 8}, "Sleep:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 33 * 8}, "Jitter:", Theme::getInstance()->fg_light->foreground},
{{11 * 8, 29 * 8}, "Secs.", Theme::getInstance()->fg_light->foreground},
{{11 * 8, 31 * 8}, "Secs.", Theme::getInstance()->fg_light->foreground},
{{11 * 8, 33 * 8}, "/60", Theme::getInstance()->fg_light->foreground},
@@ -182,7 +185,8 @@ class JammerView : public View {
OptionsField options_hop{
{7 * 8, 27 * 8},
5,
{{"10ms ", 1},
{{"Off ", 0},
{"10ms ", 1},
{"50ms ", 5},
{"100ms", 10},
{"1s ", 100},
@@ -201,7 +205,7 @@ class JammerView : public View {
NumberField field_timepause{
{8 * 8, 31 * 8},
2,
{1, 60},
{0, 60},
1,
' ',
};
@@ -209,7 +213,7 @@ class JammerView : public View {
NumberField field_jitter{
{8 * 8, 33 * 8},
2,
{1, 60},
{0, 60},
1,
' ',
};
+29
View File
@@ -98,6 +98,31 @@ static msg_t loopthread_fn(void* arg) {
return 0;
}
void MorseView::on_set_tone(NavigationView& nav) {
tone_input_buffer = to_string_dec_uint(tone);
text_prompt(nav, tone_input_buffer, 4, ENTER_KEYBOARD_MODE_DIGITS, [this](std::string& buffer) {
bool is_digit_only = true;
for (size_t i = 0; i < tone_input_buffer.size(); ++i) {
if (tone_input_buffer[i] < '0' || tone_input_buffer[i] > '9') {
is_digit_only = false;
break;
}
}
if (!buffer.empty() && is_digit_only) {
int new_tone = atoi(buffer.c_str());
if (new_tone >= 100 && new_tone <= 9999) {
tone = new_tone;
field_tone.set_value(tone);
update_tx_duration();
} else {
nav_.display_modal("Out of range", "Tone must be between 100 and 9999 Hz");
}
} else {
nav_.display_modal("Invalid input", "Please enter digits only");
}
});
}
void MorseView::on_set_text(NavigationView& nav) {
text_prompt(nav, buffer, 28, ENTER_KEYBOARD_MODE_ALPHA);
}
@@ -250,6 +275,10 @@ MorseView::MorseView(
tone = value;
};
field_tone.on_select = [this, &nav](NumberField&) {
this->on_set_tone(nav);
};
button_message.on_select = [this, &nav](Button&) {
this->on_set_text(nav);
};
+2
View File
@@ -72,6 +72,7 @@ class MorseView : public View {
NavigationView& nav_;
std::string message{};
uint32_t time_units{0};
std::string tone_input_buffer{}; // Holds the tone value while the text prompt is open
TxRadioState radio_state_{
0 /* frequency */,
@@ -100,6 +101,7 @@ class MorseView : public View {
bool start_tx();
void update_tx_duration();
void on_set_tone(NavigationView& nav);
void on_set_text(NavigationView& nav);
void set_foxhunt(size_t i);
+1 -1
View File
@@ -74,7 +74,7 @@ __attribute__((section(".external_app.app_random_password.application_informatio
0x01,
},
/*.icon_color = */ ui::Color::yellow().v,
/*.menu_location = */ app_location_t::RX,
/*.menu_location = */ app_location_t::UTILITIES,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_afsk_rx */ {'P', 'A', 'F', 'R'},
+69
View File
@@ -0,0 +1,69 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
#include "ui.hpp"
#include "ui_spaceinv.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::spaceinv {
void initialize_app(ui::NavigationView& nav) {
nav.push<SpaceInvadersView>();
}
} // namespace ui::external_app::spaceinv
extern "C" {
__attribute__((section(".external_app.app_spaceinv.application_information"), used)) application_information_t _application_information_spaceinv = {
(uint8_t*)0x00000000,
ui::external_app::spaceinv::initialize_app,
CURRENT_HEADER_VERSION,
VERSION_MD5,
"Space Invaders",
{
0x18,
0x3C,
0x7E,
0xDB,
0xFF,
0xFF,
0x24,
0x66,
0x42,
0x00,
0x24,
0x18,
0x24,
0x42,
0x81,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
},
ui::Color::yellow().v,
app_location_t::GAMES,
-1,
{0, 0, 0, 0},
0x00000000,
};
} // namespace ui::external_app::spaceinv
+879
View File
@@ -0,0 +1,879 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
#include "ui_spaceinv.hpp"
namespace ui::external_app::spaceinv {
// Game constants
#define PLAYER_WIDTH 26
#define PLAYER_HEIGHT 16
#define PLAYER_Y 280
#define BULLET_WIDTH 3
#define BULLET_HEIGHT 10
#define BULLET_SPEED 8
#define MAX_BULLETS 3
#define INVADER_WIDTH 20
#define INVADER_HEIGHT 16
#define INVADER_ROWS 5 // Classic 5 rows
#define INVADER_COLS 6 // Reduced for more movement space on narrow PP screen
#define INVADER_GAP_X 10
#define INVADER_GAP_Y 8 // Gap between invaders
#define MAX_ENEMY_BULLETS 3
#define ENEMY_BULLET_SPEED 3
// Game state
static int game_state = 0; // 0=menu, 1=playing, 2=game over, 3=wave_complete
static bool initialized = false;
static int player_x = 120;
static uint32_t score = 0;
static int lives = 3;
static uint32_t wave = 1;
static uint32_t speed_bonus = 0;
static uint32_t wave_complete_timer = 0;
// Cannon Bullets
static int bullet_x[MAX_BULLETS] = {0, 0, 0};
static int bullet_y[MAX_BULLETS] = {0, 0, 0};
static bool bullet_active[MAX_BULLETS] = {false, false, false};
// Enemy bullets
static int enemy_bullet_x[MAX_ENEMY_BULLETS] = {0, 0, 0};
static int enemy_bullet_y[MAX_ENEMY_BULLETS] = {0, 0, 0};
static bool enemy_bullet_active[MAX_ENEMY_BULLETS] = {false, false, false};
static uint32_t enemy_fire_counter = 0;
// Invaders
static bool invaders[INVADER_ROWS][INVADER_COLS];
static int invaders_x = 40;
static int invaders_y = 60;
static int invader_direction = 1;
static uint32_t invader_move_counter = 0;
static uint32_t invader_move_delay = 30;
static bool invader_animation_frame = false;
// Menu state
static bool menu_initialized = false;
static bool game_over_initialized = false;
static bool blink_state = true;
static uint32_t blink_counter = 0;
static int16_t prompt_x = 0;
// Timer
static Ticker game_timer;
static Callback game_update_callback = nullptr;
static uint32_t game_update_timeout = 0;
static uint32_t game_update_counter = 0;
// Painter
static Painter painter;
// For high score access
static SpaceInvadersView* current_instance = nullptr;
void check_game_timer() {
if (game_update_callback) {
if (++game_update_counter >= game_update_timeout) {
game_update_counter = 0;
game_update_callback();
}
}
}
void Ticker::attach(Callback func, double delay_sec) {
game_update_callback = func;
game_update_timeout = delay_sec * 60;
}
void Ticker::detach() {
game_update_callback = nullptr;
}
static void init_invaders() {
// Initialize invader array
for (int row = 0; row < INVADER_ROWS; row++) {
for (int col = 0; col < INVADER_COLS; col++) {
invaders[row][col] = true;
}
}
invaders_x = 40;
invaders_y = 60;
invader_direction = 1;
invader_move_counter = 0;
// Clear any active enemy bullets
for (int i = 0; i < MAX_ENEMY_BULLETS; i++) {
enemy_bullet_active[i] = false;
}
// Clear any active player bullets
for (int i = 0; i < MAX_BULLETS; i++) {
bullet_active[i] = false;
}
}
static void save_high_score() {
if (current_instance && score > current_instance->highScore) {
current_instance->highScore = score;
// Settings are automatically saved when the view is destroyed
}
}
static void init_menu() {
painter.fill_rectangle({0, 0, 240, 320}, Color::black());
auto style_green = *ui::Theme::getInstance()->fg_green;
auto style_yellow = *ui::Theme::getInstance()->fg_yellow;
auto style_cyan = *ui::Theme::getInstance()->fg_cyan;
int16_t screen_width = 240;
int16_t title_x = (screen_width - 15 * 8) / 2;
int16_t divider_width = 24 * 8;
int16_t divider_x = (screen_width - divider_width) / 2;
int16_t instruction_width = 22 * 8;
int16_t instruction_x = (screen_width - instruction_width) / 2;
int16_t prompt_width = 12 * 8;
prompt_x = (screen_width - prompt_width) / 2;
painter.fill_rectangle({0, 30, screen_width, 30}, Color::black());
painter.draw_string({title_x, 42}, style_green, "SPACE INVADERS");
painter.draw_string({divider_x, 70}, style_yellow, "========================");
painter.fill_rectangle({instruction_x - 5, 100, instruction_width + 10, 80}, Color::black());
painter.draw_rectangle({instruction_x - 5, 100, instruction_width + 10, 80}, Color::white());
painter.draw_string({instruction_x, 110}, style_cyan, " ROTARY: MOVE SHIP");
painter.draw_string({instruction_x, 130}, style_cyan, " SELECT: FIRE");
painter.draw_string({instruction_x, 150}, style_cyan, " DEFEND EARTH!");
// Draw point values
auto style_purple = *ui::Theme::getInstance()->fg_magenta;
painter.draw_string({50, 190}, style_purple, "TOP ROW = 30 PTS");
painter.draw_string({50, 205}, style_yellow, "MID ROW = 20 PTS");
painter.draw_string({50, 220}, style_green, "BOT ROW = 10 PTS");
// Draw high score
if (current_instance) {
auto style_white = *ui::Theme::getInstance()->fg_light;
std::string high_score_text = "HIGH SCORE: " + std::to_string(current_instance->highScore);
int16_t high_score_x = (screen_width - high_score_text.length() * 8) / 2;
painter.draw_string({high_score_x, 240}, style_white, high_score_text);
}
menu_initialized = true;
}
static void init_game_over() {
painter.fill_rectangle({0, 0, 240, 320}, Color::black());
auto style_red = *ui::Theme::getInstance()->fg_red;
auto style_yellow = *ui::Theme::getInstance()->fg_yellow;
auto style_cyan = *ui::Theme::getInstance()->fg_cyan;
auto style_white = *ui::Theme::getInstance()->fg_light;
int16_t screen_width = 240;
// Game Over title
int16_t title_x = (screen_width - 9 * 8) / 2;
painter.draw_string({title_x, 42}, style_red, "GAME OVER");
// Divider
int16_t divider_width = 24 * 8;
int16_t divider_x = (screen_width - divider_width) / 2;
painter.draw_string({divider_x, 70}, style_yellow, "========================");
// Score box
int16_t box_width = 22 * 8;
int16_t box_x = (screen_width - box_width) / 2;
painter.fill_rectangle({box_x - 5, 100, box_width + 10, 80}, Color::black());
painter.draw_rectangle({box_x - 5, 100, box_width + 10, 80}, Color::white());
// Display scores
std::string score_text = "SCORE: " + std::to_string(score);
int16_t score_x = (screen_width - score_text.length() * 8) / 2;
painter.draw_string({score_x, 120}, style_cyan, score_text);
std::string wave_text = "WAVE: " + std::to_string(wave);
int16_t wave_x = (screen_width - wave_text.length() * 8) / 2;
painter.draw_string({wave_x, 140}, style_cyan, wave_text);
// High score section
if (current_instance) {
if (score > current_instance->highScore) {
// New high score!
std::string new_high = "NEW HIGH SCORE!";
int16_t new_high_x = (screen_width - new_high.length() * 8) / 2;
painter.draw_string({new_high_x, 200}, style_yellow, new_high);
std::string high_score_text = std::to_string(score);
int16_t high_score_x = (screen_width - high_score_text.length() * 8) / 2;
painter.draw_string({high_score_x, 220}, style_yellow, high_score_text);
} else {
// Show existing high score
std::string high_label = "HIGH SCORE:";
int16_t label_x = (screen_width - high_label.length() * 8) / 2;
painter.draw_string({label_x, 200}, style_white, high_label);
std::string high_score_text = std::to_string(current_instance->highScore);
int16_t high_score_x = (screen_width - high_score_text.length() * 8) / 2;
painter.draw_string({high_score_x, 220}, style_white, high_score_text);
}
}
game_over_initialized = true;
}
static void draw_player() {
// Clear the entire player area
painter.fill_rectangle({0, PLAYER_Y - 4, 240, PLAYER_HEIGHT + 8}, Color::black());
// Draw the classic Space Invaders cannon
Color green = Color::green();
// Top part - the cannon barrel
painter.draw_hline({player_x + 12, PLAYER_Y}, 2, green);
painter.draw_hline({player_x + 12, PLAYER_Y + 1}, 2, green);
// Upper body
painter.draw_hline({player_x + 11, PLAYER_Y + 2}, 4, green);
painter.draw_hline({player_x + 11, PLAYER_Y + 3}, 4, green);
painter.draw_hline({player_x + 10, PLAYER_Y + 4}, 6, green);
painter.draw_hline({player_x + 10, PLAYER_Y + 5}, 6, green);
// Main body
painter.draw_hline({player_x + 2, PLAYER_Y + 6}, 22, green);
painter.draw_hline({player_x + 2, PLAYER_Y + 7}, 22, green);
painter.draw_hline({player_x + 1, PLAYER_Y + 8}, 24, green);
painter.draw_hline({player_x + 1, PLAYER_Y + 9}, 24, green);
painter.draw_hline({player_x, PLAYER_Y + 10}, 26, green);
painter.draw_hline({player_x, PLAYER_Y + 11}, 26, green);
painter.draw_hline({player_x, PLAYER_Y + 12}, 26, green);
painter.draw_hline({player_x, PLAYER_Y + 13}, 26, green);
painter.draw_hline({player_x, PLAYER_Y + 14}, 26, green);
painter.draw_hline({player_x, PLAYER_Y + 15}, 26, green);
}
static void draw_invader(int row, int col) {
int x = invaders_x + col * (INVADER_WIDTH + INVADER_GAP_X);
int y = invaders_y + row * (INVADER_HEIGHT + INVADER_GAP_Y);
// Clear the area first
painter.fill_rectangle({x, y, INVADER_WIDTH, INVADER_HEIGHT}, Color::black());
// Different colors and shapes for different rows
Color color;
if (row == 0) {
color = Color::red(); // Top row - 30 points
} else if (row == 1 || row == 2) {
color = Color::yellow(); // Middle rows - 20 points
} else {
color = Color::green(); // Bottom rows - 10 points
}
// Draw different invader types based on row
if (row == 0) {
// Top row - squid-like invader (30 points)
if (invader_animation_frame) {
// Frame 1 - arms down
painter.draw_hline({x + 8, y + 2}, 4, color);
painter.draw_hline({x + 6, y + 3}, 8, color);
painter.draw_hline({x + 4, y + 4}, 12, color);
painter.draw_hline({x + 2, y + 5}, 16, color);
painter.draw_hline({x + 2, y + 6}, 16, color);
painter.draw_hline({x, y + 7}, 20, color);
painter.draw_hline({x, y + 8}, 20, color);
painter.draw_hline({x + 2, y + 9}, 2, color);
painter.draw_hline({x + 6, y + 9}, 8, color);
painter.draw_hline({x + 16, y + 9}, 2, color);
painter.draw_hline({x + 4, y + 10}, 2, color);
painter.draw_hline({x + 14, y + 10}, 2, color);
} else {
// Frame 2 - arms up
painter.draw_hline({x + 8, y + 2}, 4, color);
painter.draw_hline({x + 6, y + 3}, 8, color);
painter.draw_hline({x + 4, y + 4}, 12, color);
painter.draw_hline({x + 2, y + 5}, 16, color);
painter.draw_hline({x + 2, y + 6}, 16, color);
painter.draw_hline({x, y + 7}, 20, color);
painter.draw_hline({x, y + 8}, 20, color);
painter.draw_hline({x + 4, y + 9}, 2, color);
painter.draw_hline({x + 8, y + 9}, 4, color);
painter.draw_hline({x + 14, y + 9}, 2, color);
painter.draw_hline({x + 2, y + 10}, 2, color);
painter.draw_hline({x + 16, y + 10}, 2, color);
}
} else if (row == 1 || row == 2) {
// Middle rows - crab-like invader (20 points)
if (invader_animation_frame) {
// Frame 1 - arms in
painter.draw_hline({x + 4, y + 2}, 2, color);
painter.draw_hline({x + 14, y + 2}, 2, color);
painter.draw_hline({x + 2, y + 3}, 2, color);
painter.draw_hline({x + 6, y + 3}, 8, color);
painter.draw_hline({x + 16, y + 3}, 2, color);
painter.draw_hline({x + 2, y + 4}, 16, color);
painter.draw_hline({x, y + 5}, 6, color);
painter.draw_hline({x + 8, y + 5}, 4, color);
painter.draw_hline({x + 14, y + 5}, 6, color);
painter.draw_hline({x, y + 6}, 20, color);
painter.draw_hline({x + 2, y + 7}, 16, color);
painter.draw_hline({x + 4, y + 8}, 2, color);
painter.draw_hline({x + 14, y + 8}, 2, color);
painter.draw_hline({x + 2, y + 9}, 4, color);
painter.draw_hline({x + 14, y + 9}, 4, color);
} else {
// Frame 2 - arms out
painter.draw_hline({x + 4, y + 2}, 2, color);
painter.draw_hline({x + 14, y + 2}, 2, color);
painter.draw_hline({x + 2, y + 3}, 2, color);
painter.draw_hline({x + 6, y + 3}, 8, color);
painter.draw_hline({x + 16, y + 3}, 2, color);
painter.draw_hline({x + 2, y + 4}, 16, color);
painter.draw_hline({x, y + 5}, 6, color);
painter.draw_hline({x + 8, y + 5}, 4, color);
painter.draw_hline({x + 14, y + 5}, 6, color);
painter.draw_hline({x, y + 6}, 20, color);
painter.draw_hline({x + 2, y + 7}, 16, color);
painter.draw_hline({x + 4, y + 8}, 2, color);
painter.draw_hline({x + 14, y + 8}, 2, color);
painter.draw_hline({x, y + 9}, 2, color);
painter.draw_hline({x + 6, y + 9}, 2, color);
painter.draw_hline({x + 12, y + 9}, 2, color);
painter.draw_hline({x + 18, y + 9}, 2, color);
}
} else {
// Bottom rows - octopus-like invader (10 points)
if (invader_animation_frame) {
// Frame 1
painter.draw_hline({x + 6, y + 2}, 8, color);
painter.draw_hline({x + 4, y + 3}, 12, color);
painter.draw_hline({x + 2, y + 4}, 16, color);
painter.draw_hline({x, y + 5}, 20, color);
painter.draw_hline({x, y + 6}, 20, color);
painter.draw_hline({x + 4, y + 7}, 4, color);
painter.draw_hline({x + 12, y + 7}, 4, color);
painter.draw_hline({x + 2, y + 8}, 4, color);
painter.draw_hline({x + 8, y + 8}, 4, color);
painter.draw_hline({x + 14, y + 8}, 4, color);
painter.draw_hline({x + 4, y + 9}, 2, color);
painter.draw_hline({x + 14, y + 9}, 2, color);
} else {
// Frame 2
painter.draw_hline({x + 6, y + 2}, 8, color);
painter.draw_hline({x + 4, y + 3}, 12, color);
painter.draw_hline({x + 2, y + 4}, 16, color);
painter.draw_hline({x, y + 5}, 20, color);
painter.draw_hline({x, y + 6}, 20, color);
painter.draw_hline({x + 4, y + 7}, 4, color);
painter.draw_hline({x + 12, y + 7}, 4, color);
painter.draw_hline({x + 2, y + 8}, 4, color);
painter.draw_hline({x + 8, y + 8}, 4, color);
painter.draw_hline({x + 14, y + 8}, 4, color);
painter.draw_hline({x + 2, y + 9}, 2, color);
painter.draw_hline({x + 16, y + 9}, 2, color);
}
}
}
static void draw_all_invaders() {
for (int row = 0; row < INVADER_ROWS; row++) {
for (int col = 0; col < INVADER_COLS; col++) {
if (invaders[row][col]) {
draw_invader(row, col);
}
}
}
}
static void clear_all_invaders() {
for (int row = 0; row < INVADER_ROWS; row++) {
for (int col = 0; col < INVADER_COLS; col++) {
if (invaders[row][col]) {
int x = invaders_x + col * (INVADER_WIDTH + INVADER_GAP_X);
int y = invaders_y + row * (INVADER_HEIGHT + INVADER_GAP_Y);
painter.fill_rectangle({x, y, INVADER_WIDTH, INVADER_HEIGHT}, Color::black());
}
}
}
}
static void clear_all_enemy_bullets() {
// Clear any visible enemy bullets
for (int i = 0; i < MAX_ENEMY_BULLETS; i++) {
if (enemy_bullet_active[i]) {
painter.fill_rectangle({enemy_bullet_x[i], enemy_bullet_y[i], BULLET_WIDTH, BULLET_HEIGHT}, Color::black());
enemy_bullet_active[i] = false;
}
}
}
static void fire_enemy_bullet() {
// Find a random invader to fire from
int active_count = 0;
for (int row = 0; row < INVADER_ROWS; row++) {
for (int col = 0; col < INVADER_COLS; col++) {
if (invaders[row][col]) active_count++;
}
}
if (active_count == 0) return;
int shooter = rand() % active_count;
int count = 0;
for (int row = 0; row < INVADER_ROWS; row++) {
for (int col = 0; col < INVADER_COLS; col++) {
if (invaders[row][col]) {
if (count == shooter) {
// Fire from this invader
for (int i = 0; i < MAX_ENEMY_BULLETS; i++) {
if (!enemy_bullet_active[i]) {
int x = invaders_x + col * (INVADER_WIDTH + INVADER_GAP_X);
int y = invaders_y + row * (INVADER_HEIGHT + INVADER_GAP_Y);
enemy_bullet_x[i] = x + INVADER_WIDTH / 2;
enemy_bullet_y[i] = y + INVADER_HEIGHT;
enemy_bullet_active[i] = true;
return;
}
}
}
count++;
}
}
}
}
static void update_enemy_bullets() {
for (int i = 0; i < MAX_ENEMY_BULLETS; i++) {
if (enemy_bullet_active[i]) {
// Clear old position - but protect the border line
if (enemy_bullet_y[i] != 49) { // Don't clear if we're exactly on the border line
painter.fill_rectangle({enemy_bullet_x[i], enemy_bullet_y[i], BULLET_WIDTH, BULLET_HEIGHT}, Color::black());
}
enemy_bullet_y[i] += ENEMY_BULLET_SPEED;
if (enemy_bullet_y[i] > 320) {
enemy_bullet_active[i] = false;
} else {
// Draw at new position
painter.fill_rectangle({enemy_bullet_x[i], enemy_bullet_y[i], BULLET_WIDTH, BULLET_HEIGHT}, Color::red());
// Check collision with player
if (enemy_bullet_x[i] >= player_x &&
enemy_bullet_x[i] <= player_x + PLAYER_WIDTH &&
enemy_bullet_y[i] >= PLAYER_Y &&
enemy_bullet_y[i] <= PLAYER_Y + PLAYER_HEIGHT) {
// Player hit!
enemy_bullet_active[i] = false;
lives--;
// Update lives display
auto style = *ui::Theme::getInstance()->fg_green;
painter.fill_rectangle({5, 30, 100, 20}, Color::black());
painter.draw_string({5, 30}, style, "Lives: " + std::to_string(lives));
if (lives <= 0) {
game_state = 2; // Game over
save_high_score();
}
}
}
}
}
}
static void update_invaders() {
uint32_t adjusted_delay = (speed_bonus >= invader_move_delay) ? 1 : invader_move_delay - speed_bonus;
if (++invader_move_counter >= adjusted_delay) {
invader_move_counter = 0;
// Clear old positions
clear_all_invaders();
// Check bounds - find the actual leftmost and rightmost invaders
int leftmost = INVADER_COLS;
int rightmost = -1;
for (int col = 0; col < INVADER_COLS; col++) {
for (int row = 0; row < INVADER_ROWS; row++) {
if (invaders[row][col]) {
if (col < leftmost) leftmost = col;
if (col > rightmost) rightmost = col;
}
}
}
bool hit_edge = false;
if (invader_direction > 0) {
// Moving right - check rightmost invader
int right_edge = invaders_x + rightmost * (INVADER_WIDTH + INVADER_GAP_X) + INVADER_WIDTH;
if (right_edge >= 240) {
hit_edge = true;
}
} else {
// Moving left - check leftmost invader
int left_edge = invaders_x + leftmost * (INVADER_WIDTH + INVADER_GAP_X);
if (left_edge <= 0) {
hit_edge = true;
}
}
// Move invaders
if (hit_edge) {
invaders_y += 15; // Move down
invader_direction = -invader_direction;
} else {
invaders_x += invader_direction * 8; // Horizontal movement
}
// Toggle animation frame
invader_animation_frame = !invader_animation_frame;
// Draw new positions
draw_all_invaders();
}
// Enemy firing logic - only in hard mode or always with lower frequency
if (!current_instance || !current_instance->easy_mode) {
if (++enemy_fire_counter >= 120) { // Fire every 2 seconds at 60fps
enemy_fire_counter = 0;
fire_enemy_bullet();
}
}
}
static void fire_bullet() {
for (int i = 0; i < MAX_BULLETS; i++) {
if (!bullet_active[i]) {
bullet_active[i] = true;
bullet_x[i] = player_x + PLAYER_WIDTH / 2 - BULLET_WIDTH / 2;
bullet_y[i] = PLAYER_Y - BULLET_HEIGHT;
break;
}
}
}
static void update_bullets() {
for (int i = 0; i < MAX_BULLETS; i++) {
if (bullet_active[i]) {
painter.fill_rectangle({bullet_x[i], bullet_y[i], BULLET_WIDTH, BULLET_HEIGHT}, Color::black());
bullet_y[i] -= BULLET_SPEED;
if (bullet_y[i] < 50) {
bullet_active[i] = false;
} else {
painter.fill_rectangle({bullet_x[i], bullet_y[i], BULLET_WIDTH, BULLET_HEIGHT}, Color::white());
}
}
}
}
static void check_collisions() {
for (int i = 0; i < MAX_BULLETS; i++) {
if (!bullet_active[i]) continue;
for (int row = 0; row < INVADER_ROWS; row++) {
for (int col = 0; col < INVADER_COLS; col++) {
if (!invaders[row][col]) continue;
int inv_x = invaders_x + col * (INVADER_WIDTH + INVADER_GAP_X);
int inv_y = invaders_y + row * (INVADER_HEIGHT + INVADER_GAP_Y);
// Simple collision check
if (bullet_x[i] < inv_x + INVADER_WIDTH &&
bullet_x[i] + BULLET_WIDTH > inv_x &&
bullet_y[i] < inv_y + INVADER_HEIGHT &&
bullet_y[i] + BULLET_HEIGHT > inv_y) {
// Hit!
bullet_active[i] = false;
painter.fill_rectangle({bullet_x[i], bullet_y[i], BULLET_WIDTH, BULLET_HEIGHT}, Color::black());
invaders[row][col] = false;
painter.fill_rectangle({inv_x, inv_y, INVADER_WIDTH, INVADER_HEIGHT}, Color::black());
// Score based on row: top=30, middle=20, bottom=10
if (row == 0) {
score += 30;
} else if (row == 1 || row == 2) {
score += 20;
} else {
score += 10;
}
return;
}
}
}
}
}
static void check_wave_complete() {
// Check if all invaders are destroyed
bool all_destroyed = true;
for (int row = 0; row < INVADER_ROWS; row++) {
for (int col = 0; col < INVADER_COLS; col++) {
if (invaders[row][col]) {
all_destroyed = false;
break;
}
}
if (!all_destroyed) break;
}
if (all_destroyed) {
// Next wave!
wave++;
speed_bonus = (wave - 1) * 3; // Each wave is slightly faster
if (speed_bonus > 15) speed_bonus = 15; // Cap the speed increase
// Clear any enemy bullets before transitioning
clear_all_enemy_bullets();
// Set state to wave complete and start timer
game_state = 3;
wave_complete_timer = 60; // 1 second at 60fps
// Clear screen and show wave message - but preserve the border line
painter.fill_rectangle({0, 51, 240, 269}, Color::black()); // Start at 51 to preserve line at 49
// Redraw the border line to ensure it's intact - stupid bullet clearing wants to damage it
painter.draw_hline({0, 49}, 240, Color::white());
auto style = *ui::Theme::getInstance()->fg_green;
std::string wave_text = "WAVE " + std::to_string(wave);
int wave_x = (240 - wave_text.length() * 8) / 2;
painter.draw_string({wave_x, 150}, style, wave_text);
return;
}
// Check if invaders reached player
for (int row = 0; row < INVADER_ROWS; row++) {
for (int col = 0; col < INVADER_COLS; col++) {
if (invaders[row][col]) {
int y = invaders_y + row * (INVADER_HEIGHT + INVADER_GAP_Y);
if (y + INVADER_HEIGHT >= PLAYER_Y) { // Actual collision with player
game_state = 2; // Game over
save_high_score();
return;
}
}
}
}
}
void game_timer_check() {
if (game_state == 0) {
// Menu state
if (!menu_initialized) {
init_menu();
}
if (++blink_counter >= 30) {
blink_counter = 0;
blink_state = !blink_state;
auto style = *ui::Theme::getInstance()->fg_red;
if (blink_state) {
painter.draw_string({prompt_x, 260}, style, "PRESS SELECT");
} else {
painter.fill_rectangle({prompt_x, 260, 16 * 8, 20}, Color::black());
}
}
} else if (game_state == 1) {
// Playing state
update_bullets();
update_enemy_bullets();
update_invaders();
check_collisions();
check_wave_complete();
// Update score display
static uint32_t last_score = 999999;
if (score != last_score) {
last_score = score;
auto style = *ui::Theme::getInstance()->fg_green;
painter.fill_rectangle({5, 10, 100, 20}, Color::black());
painter.draw_string({5, 10}, style, "Score: " + std::to_string(score));
// Redraw border line after score update (in case it was damaged) - stupid bullet clearing wants to damage it
painter.draw_hline({0, 49}, 240, Color::white());
}
// Periodically redraw the border line to fix any damage because it's a pain in the ass
static uint32_t border_redraw_counter = 0;
if (++border_redraw_counter >= 60) { // Once per second - might make less if causing flickering
border_redraw_counter = 0;
painter.draw_hline({0, 49}, 240, Color::white());
}
} else if (game_state == 2) {
// Game over state
if (!game_over_initialized) {
init_game_over();
}
if (++blink_counter >= 30) {
blink_counter = 0;
blink_state = !blink_state;
auto style = *ui::Theme::getInstance()->fg_red;
if (blink_state) {
painter.draw_string({prompt_x, 260}, style, "PRESS SELECT");
} else {
painter.fill_rectangle({prompt_x, 260, 16 * 8, 20}, Color::black());
}
}
} else if (game_state == 3) {
// Wave complete state - wait for timer
if (--wave_complete_timer <= 0) {
// Timer expired, start next wave
game_state = 1;
// Clear and redraw game area - preserve border line
painter.fill_rectangle({0, 51, 240, 269}, Color::black()); // Start at 51 to preserve line
// Restore UI
auto style = *ui::Theme::getInstance()->fg_green;
painter.draw_string({5, 10}, style, "Score: " + std::to_string(score));
painter.draw_string({5, 30}, style, "Lives: " + std::to_string(lives));
// Redraw the border line in case it was damaged again
painter.draw_hline({0, 49}, 240, Color::white());
// Initialize new wave of invaders
init_invaders();
draw_all_invaders();
draw_player();
}
}
}
SpaceInvadersView::SpaceInvadersView(NavigationView& nav)
: nav_{nav} {
add_children({&dummy, &button_difficulty});
current_instance = this;
game_timer.attach(&game_timer_check, 1.0 / 60.0);
// Update button text based on loaded setting
button_difficulty.set_text(easy_mode ? "Mode: EASY" : "Mode: HARD");
button_difficulty.on_select = [this](Button&) {
easy_mode = !easy_mode;
button_difficulty.set_text(easy_mode ? "Mode: EASY" : "Mode: HARD");
// Settings will be saved when the view is destroyed
};
}
SpaceInvadersView::~SpaceInvadersView() {
// Settings are automatically saved when destroyed
current_instance = nullptr;
}
void SpaceInvadersView::on_show() {
}
void SpaceInvadersView::paint(Painter& painter) {
(void)painter;
if (!initialized) {
initialized = true;
game_state = 0;
menu_initialized = false;
game_over_initialized = false;
blink_state = true;
blink_counter = 0;
player_x = 107;
score = 0;
lives = 3;
wave = 1;
speed_bonus = 0;
// Initialize arrays
for (int i = 0; i < MAX_BULLETS; i++) {
bullet_active[i] = false;
bullet_x[i] = 0;
bullet_y[i] = 0;
}
for (int i = 0; i < MAX_ENEMY_BULLETS; i++) {
enemy_bullet_active[i] = false;
enemy_bullet_x[i] = 0;
enemy_bullet_y[i] = 0;
}
init_invaders();
}
}
void SpaceInvadersView::frame_sync() {
check_game_timer();
set_dirty();
}
bool SpaceInvadersView::on_encoder(const EncoderEvent delta) {
if (game_state == 1) {
if (delta > 0) {
player_x += 5;
if (player_x > 214) player_x = 214;
draw_player();
} else if (delta < 0) {
player_x -= 5;
if (player_x < 0) player_x = 0;
draw_player();
}
set_dirty();
}
return true;
}
bool SpaceInvadersView::on_key(const KeyEvent key) {
if (key == KeyEvent::Select) {
if (game_state == 0) {
// Start game
game_state = 1;
button_difficulty.hidden(true);
score = 0;
lives = 3;
wave = 1;
speed_bonus = 0;
invader_move_delay = 30;
enemy_fire_counter = 0;
init_invaders();
painter.fill_rectangle({0, 0, 240, 320}, Color::black());
painter.draw_hline({0, 49}, 240, Color::white());
auto style = *ui::Theme::getInstance()->fg_green;
painter.draw_string({5, 10}, style, "Score: 0");
painter.draw_string({5, 30}, style, "Lives: 3");
draw_all_invaders();
draw_player();
set_dirty();
} else if (game_state == 1) {
fire_bullet();
} else if (game_state == 2) {
// Return to menu
game_state = 0;
menu_initialized = false;
game_over_initialized = false;
button_difficulty.hidden(false);
set_dirty();
}
}
return true;
}
} // namespace ui::external_app::spaceinv
+79
View File
@@ -0,0 +1,79 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
#ifndef __UI_SPACEINV_H__
#define __UI_SPACEINV_H__
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "event_m0.hpp"
#include "message.hpp"
#include "irq_controls.hpp"
#include "random.hpp"
#include "lpc43xx_cpp.hpp"
#include "ui_widget.hpp"
#include "app_settings.hpp"
namespace ui::external_app::spaceinv {
using Callback = void (*)(void);
class Ticker {
public:
Ticker() = default;
void attach(Callback func, double delay_sec);
void detach();
};
void check_game_timer();
void game_timer_check();
class SpaceInvadersView : public View {
public:
SpaceInvadersView(NavigationView& nav);
~SpaceInvadersView(); // Destructor will trigger settings save
void on_show() override;
std::string title() const override { return "Space Invaders"; }
void focus() override { dummy.focus(); }
void paint(Painter& painter) override;
void frame_sync();
bool on_encoder(const EncoderEvent event) override;
bool on_key(KeyEvent key) override;
uint32_t highScore = 0;
bool easy_mode = false;
private:
NavigationView& nav_;
Button button_difficulty{
{70, 285, 100, 20},
"Mode: HARD"};
app_settings::SettingsManager settings_{
"spaceinv",
app_settings::Mode::NO_RF,
{{"highscore"sv, &highScore},
{"easy_mode"sv, &easy_mode}}};
Button dummy{
{240, 0, 0, 0},
""};
MessageHandlerRegistration message_handler_frame_sync{
Message::ID::DisplayFrameSync,
[this](const Message* const) {
this->frame_sync();
}};
};
} // namespace ui::external_app::spaceinv
#endif /* __UI_SPACEINV_H__ */
+1
View File
@@ -52,3 +52,4 @@ const std::filesystem::path ook_editor_dir = u"OOKFILES";
const std::filesystem::path hopper_dir = u"HOPPER";
const std::filesystem::path subghz_dir = u"SUBGHZ";
const std::filesystem::path waterfalls_dir = u"WATERFALLS";
const std::filesystem::path macaddress_dir = u"MACADDRESS";
+1
View File
@@ -54,5 +54,6 @@ extern const std::filesystem::path ook_editor_dir;
extern const std::filesystem::path hopper_dir;
extern const std::filesystem::path subghz_dir;
extern const std::filesystem::path waterfalls_dir;
extern const std::filesystem::path macaddress_dir;
#endif /* __FILE_PATH_H__ */
+3 -2
View File
@@ -108,8 +108,9 @@ options_t freqman_bandwidths[6] = {
},
{
// WFMAM for NOAA satellites, 137 Mhz band
{"80kHz (NOAA Apt)", 0}, // Fixed RX demod- WFM config Index 1 : FM+AM for Audio NOAA APT ones.
{"38kHz (NOAA Apt)", 1},
{"80k-NOAA Apt LPF", 0}, // Captured RF IQ filtered BW 80K, APT baseband filtered with Low Pass Filter 4k5 fc -3dB
{"38k-NOAA Apt LPF", 1}, // Captured RF IQ filtered BW 38K, APT baseband filtered with Low Pass Filter 4k5 fc -3dB
{"38k-NOAA Apt BPF", 2}, // Captured RF IQ filtered BW 38K, APT baseband filtered BPF centred to the 2k4 AM subcarrier, BW = 2KHz
},
};
+4 -3
View File
@@ -69,9 +69,10 @@ static constexpr std::array<baseband::WFMConfig, 3> wfm_configs{{
{taps_80k_wfm_decim_0, taps_80k_wfm_decim_1},
}};
static constexpr std::array<baseband::WFMAMConfig, 2> wfmam_configs{{
{taps_16k0_decim_0, taps_80k_wfmam_decim_1},
{taps_16k0_decim_0, taps_38k_wfmam_decim_1},
static constexpr std::array<baseband::WFMAMConfig, 3> wfmam_configs{{
{taps_16k0_decim_0, taps_80k_wfmam_decim_1, taps_64_lp_1875_2166},
{taps_16k0_decim_0, taps_38k_wfmam_decim_1, taps_64_lp_1875_2166},
{taps_16k0_decim_0, taps_38k_wfmam_decim_1, taps_64_bpf_2k4_bw_2k},
}};
} /* namespace */
+136 -1
View File
@@ -1,4 +1,3 @@
#include "theme.hpp"
namespace ui {
@@ -25,6 +24,9 @@ void Theme::SetTheme(ThemeId theme) {
case Red:
current = new ThemeRed();
break;
case Dark:
current = new ThemeDark();
break;
case DefaultGrey:
default:
current = new ThemeDefault();
@@ -725,4 +727,137 @@ ThemeRed::ThemeRed() {
bg_table_header = new Color{205, 30, 0};
}
ThemeDark::ThemeDark() {
bg_lightest = new Style{
.font = font::fixed_8x16,
.background = {32, 32, 32},
.foreground = Color::white(),
};
bg_lightest_small = new Style{
.font = font::fixed_5x8,
.background = {32, 32, 32},
.foreground = Color::white(),
};
bg_light = new Style{
.font = font::fixed_8x16,
.background = {24, 24, 24},
.foreground = Color::white(),
};
bg_medium = new Style{
.font = font::fixed_8x16,
.background = {16, 16, 16},
.foreground = Color::white(),
};
bg_dark = new Style{
.font = font::fixed_8x16,
.background = {8, 8, 8},
.foreground = Color::white(),
};
bg_darker = new Style{
.font = font::fixed_8x16,
.background = {4, 4, 4},
.foreground = Color::white(),
};
bg_darkest = new Style{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::white(),
};
bg_darkest_small = new Style{
.font = font::fixed_5x8,
.background = Color::black(),
.foreground = Color::white(),
};
bg_important_small = new Style{
.font = font::fixed_5x8,
.background = {64, 64, 64},
.foreground = Color::white(),
};
error_dark = new Style{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::red(),
};
warning_dark = new Style{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::yellow(),
};
ok_dark = new Style{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::green(),
};
fg_dark = new Style{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = {96, 96, 96},
};
fg_medium = new Style{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = {128, 128, 128},
};
fg_light = new Style{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::white(),
};
fg_red = new Style{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::red(),
};
fg_green = new Style{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::green(),
};
fg_yellow = new Style{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::yellow(),
};
fg_orange = new Style{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::orange(),
};
fg_blue = new Style{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::blue(),
};
fg_cyan = new Style{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::cyan(),
};
fg_darkcyan = new Style{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::dark_cyan(),
};
fg_magenta = new Style{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::magenta(),
};
option_active = new Style{
.font = font::fixed_8x16,
.background = {64, 64, 64},
.foreground = Color::white(),
};
status_active = new Color{0, 255, 0};
bg_table_header = new Color{48, 48, 48};
}
} // namespace ui
+6
View File
@@ -93,6 +93,11 @@ class ThemeRed : public ThemeTemplate {
ThemeRed();
};
class ThemeDark : public ThemeTemplate {
public:
ThemeDark();
};
class Theme {
public:
enum ThemeId {
@@ -101,6 +106,7 @@ class Theme {
Aqua = 2,
Green = 3,
Red = 4,
Dark = 5,
MAX
};
static ThemeTemplate* getInstance();
+43
View File
@@ -0,0 +1,43 @@
Copyright (C) 2025 HTotoo
# External UI elements
## Read carefully
These widgets are only used in external apps!
The concept is, we move these widgets out of FW space, and compile them with external apps. To all separately. This is multiple include, but since we compile the widget under different namespaces (and those namespaces will be under the external_app namespace) these all will be removed from the base firmware.
This way we can free up some spaces, and still reuse the same widgets in different apps easily.
## How to create external widget
You create a **hpp** file indet the ui/external folder, and include everything you need for your widget as you normally wanted to do.
**Don't forget the ifdef guards!**
**Never use any namespace for your class there!** The namespace of the actual ext app will be used where you include this.
Then create a **cpi** file, where you include your hpp file, and create the implementation of your widget class. Still, don't use namespace.
You won't need to include these files in any cmake file! (see usage, why)
## How to use these widgets
This is important, so follow the rules carefully. If it works, doesn't mean, you did it good!
In your external app's hpp file, you need to include the widget's hpp file.
**But be careful**, you must include it under the ext app's namespace, before your first class. For examlpe:
namespace ui::external_app::gfxeq {
#include "external/ui_grapheq.hpp"
class gfxEQView : public View {
Then you must include the cpi file in the external app's cpp file. Again, it must be under the ext app's namespace!
using namespace portapack;
namespace ui::external_app::gfxeq {
#include "external/ui_grapheq.cpi"
gfxEQView::gfxEQView(NavigationView& nav)
This must be done under each ext app that uses the same widget.
This way, the widget will be compiled multiple times under the external_app namespace, but those will be removed from the base. Each app will be able to use it, easy to handle, easy to create.
+221
View File
@@ -0,0 +1,221 @@
/*
* Copyright (C) 2025 RocketGod
* Copyright (C) 2025 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_grapheq.hpp"
/* GraphEq *************************************************************/
GraphEq::GraphEq(
Rect parent_rect,
bool clickable)
: Widget{parent_rect},
clickable_{clickable},
bar_heights(NUM_BARS, 0),
prev_bar_heights(NUM_BARS, 0) {
if (clickable) {
set_focusable(true);
// previous_data.resize(length_, 0);
}
}
void GraphEq::set_parent_rect(const Rect new_parent_rect) {
Widget::set_parent_rect(new_parent_rect);
calculate_params();
}
void GraphEq::calculate_params() {
y_top = screen_rect().top();
RENDER_HEIGHT = parent_rect().height();
BAR_WIDTH = (parent_rect().width() - (BAR_SPACING * (NUM_BARS - 1))) / NUM_BARS;
HORIZONTAL_OFFSET = screen_rect().left();
}
bool GraphEq::is_paused() const {
return paused_;
}
void GraphEq::set_paused(bool paused) {
paused_ = paused;
needs_background_redraw = true;
set_dirty();
}
bool GraphEq::is_clickable() const {
return clickable_;
}
void GraphEq::getAccessibilityText(std::string& result) {
result = paused_ ? "paused GraphEq" : "GraphEq";
}
void GraphEq::getWidgetName(std::string& result) {
result = "GraphEq";
}
bool GraphEq::on_key(const KeyEvent key) {
if (!clickable_) return false;
if (key == KeyEvent::Select) {
set_paused(!paused_);
if (on_select) {
on_select(*this);
}
return true;
}
return false;
}
bool GraphEq::on_keyboard(const KeyboardEvent key) {
if (!clickable_) return false;
if (key == 32 || key == 10) {
set_paused(!paused_);
if (on_select) {
on_select(*this);
}
return true;
}
return false;
}
bool GraphEq::on_touch(const TouchEvent event) {
if (!clickable_) return false;
switch (event.type) {
case TouchEvent::Type::Start:
focus();
return true;
case TouchEvent::Type::End:
set_paused(!paused_);
if (on_select) {
on_select(*this);
}
return true;
default:
return false;
}
}
void GraphEq::set_theme(Color base_color_, Color peak_color_) {
base_color = base_color_;
peak_color = peak_color_;
set_dirty();
}
void GraphEq::update_audio_spectrum(const AudioSpectrum& spectrum) {
const float bin_frequency_size = 48000.0f / 128;
for (int bar = 0; bar < NUM_BARS; bar++) {
float start_freq = FREQUENCY_BANDS[bar];
float end_freq = FREQUENCY_BANDS[bar + 1];
int start_bin = std::max(1, (int)(start_freq / bin_frequency_size));
int end_bin = std::min(127, (int)(end_freq / bin_frequency_size));
if (start_bin >= end_bin) {
end_bin = start_bin + 1;
}
float total_energy = 0;
int bin_count = 0;
for (int bin = start_bin; bin <= end_bin; bin++) {
total_energy += spectrum.db[bin];
bin_count++;
}
float avg_db = bin_count > 0 ? (total_energy / bin_count) : 0;
// Manually boost highs for better visual balance
float treble_boost = 1.0f;
if (bar == 10)
treble_boost = 1.7f;
else if (bar >= 9)
treble_boost = 1.3f;
else if (bar >= 7)
treble_boost = 1.3f;
// Mid emphasis for a V-shape effect
float mid_boost = 1.0f;
if (bar == 4 || bar == 5 || bar == 6) mid_boost = 1.2f;
float amplified_db = avg_db * treble_boost * mid_boost;
if (amplified_db > 255) amplified_db = 255;
float band_scale = 1.0f;
int target_height = (amplified_db * RENDER_HEIGHT * band_scale) / 255;
if (target_height > RENDER_HEIGHT) {
target_height = RENDER_HEIGHT;
}
// Adjusted to look nice to my eyes
float rise_speed = 0.8f;
float fall_speed = 1.0f;
if (target_height > bar_heights[bar]) {
bar_heights[bar] = bar_heights[bar] * (1.0f - rise_speed) + target_height * rise_speed;
} else {
bar_heights[bar] = bar_heights[bar] * (1.0f - fall_speed) + target_height * fall_speed;
}
}
set_dirty();
}
void GraphEq::paint(Painter& painter) {
if (!visible()) return;
if (!is_calculated) { // calc positions first
calculate_params();
is_calculated = true;
}
if (needs_background_redraw) {
painter.fill_rectangle(screen_rect(), Theme::getInstance()->bg_darkest->background);
needs_background_redraw = false;
}
if (paused_) {
return;
}
const int num_segments = RENDER_HEIGHT / SEGMENT_HEIGHT;
uint16_t bottom = screen_rect().bottom();
for (int bar = 0; bar < NUM_BARS; bar++) {
int x = HORIZONTAL_OFFSET + bar * (BAR_WIDTH + BAR_SPACING);
int active_segments = (bar_heights[bar] * num_segments) / RENDER_HEIGHT;
if (prev_bar_heights[bar] > active_segments) {
int clear_height = (prev_bar_heights[bar] - active_segments) * SEGMENT_HEIGHT;
int clear_y = bottom - prev_bar_heights[bar] * SEGMENT_HEIGHT;
painter.fill_rectangle({x, clear_y, BAR_WIDTH, clear_height}, Theme::getInstance()->bg_darkest->background);
}
for (int seg = 0; seg < active_segments; seg++) {
int y = bottom - (seg + 1) * SEGMENT_HEIGHT;
if (y < y_top) break;
Color segment_color = (seg >= active_segments - 2 && seg < active_segments) ? peak_color : base_color;
painter.fill_rectangle({x, y, BAR_WIDTH, SEGMENT_HEIGHT - 1}, segment_color);
}
prev_bar_heights[bar] = active_segments;
}
}
+88
View File
@@ -0,0 +1,88 @@
/*
* Copyright (C) 2025 RocketGod
* Copyright (C) 2025 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.
*/
#ifndef __UI_GRAPHEQ_H__
#define __UI_GRAPHEQ_H__
#include "ui_widget.hpp"
class GraphEq : public Widget {
public:
std::function<void(GraphEq&)> on_select{};
GraphEq(Rect parent_rect, bool clickable = false);
GraphEq(const GraphEq&) = delete;
GraphEq(GraphEq&&) = delete;
GraphEq& operator=(const GraphEq&) = delete;
GraphEq& operator=(GraphEq&&) = delete;
bool is_paused() const;
void set_paused(bool paused);
bool is_clickable() const;
void paint(Painter& painter) override;
bool on_key(const KeyEvent key) override;
bool on_touch(const TouchEvent event) override;
bool on_keyboard(const KeyboardEvent event) override;
void set_parent_rect(const Rect new_parent_rect) override;
void getAccessibilityText(std::string& result) override;
void getWidgetName(std::string& result) override;
void update_audio_spectrum(const AudioSpectrum& spectrum);
void set_theme(Color base_color, Color peak_color);
private:
bool is_calculated{false};
bool paused_{false};
bool clickable_{false};
bool needs_background_redraw{true}; // Redraw background only when needed.
Color base_color = Color(255, 0, 255);
Color peak_color = Color(255, 255, 255);
std::vector<ui::Dim> bar_heights;
std::vector<ui::Dim> prev_bar_heights;
ui::Dim y_top = 2 * 16;
ui::Dim RENDER_HEIGHT = 288;
ui::Dim BAR_WIDTH = 20;
ui::Dim HORIZONTAL_OFFSET = 2;
static const int NUM_BARS = 11;
static const int BAR_SPACING = 2;
static const int SEGMENT_HEIGHT = 10;
static constexpr std::array<int16_t, NUM_BARS + 1> FREQUENCY_BANDS = {
375, // Bass warmth and low rumble (e.g., deep basslines, kick drum body)
750, // Upper bass punch (e.g., bass guitar punch, kick drum attack)
1500, // Lower midrange fullness (e.g., warmth in vocals, guitar body)
2250, // Midrange clarity (e.g., vocal presence, snare crack)
3375, // Upper midrange bite (e.g., instrument definition, vocal articulation)
4875, // Presence and edge (e.g., guitar bite, vocal sibilance start)
6750, // Lower brilliance (e.g., cymbal shimmer, vocal clarity)
9375, // Brilliance and air (e.g., hi-hat crispness, breathy vocals)
13125, // High treble sparkle (e.g., subtle overtones, synth shimmer)
16875, // Upper treble airiness (e.g., faint harmonics, room ambiance)
20625, // Top-end sheen (e.g., ultra-high harmonics, noise floor)
24375 // Extreme treble limit (e.g., inaudible overtones, signal cutoff, static)
};
void calculate_params(); // re calculate some parameters based on parent_rect()
};
#endif /*__UI_GRAPHEQ_H__*/
+1 -1
View File
@@ -157,7 +157,7 @@ void NoaaAptRx::configure(const NoaaAptRxConfigureMessage& message) {
channel_filter_high_f = taps_38k_wfmam_decim_1.high_frequency_normalized * decim_1_input_fs;
channel_filter_transition = taps_38k_wfmam_decim_1.transition_normalized * decim_1_input_fs;
demod.configure(demod_input_fs, 17000);
audio_filter.configure(taps_64_lp_1875_2166.taps);
audio_filter.configure(taps_64_bpf_2k4_bw_2k.taps);
audio_output.configure(apt_audio_12k_notch_2k4_config, apt_audio_12k_lpf_2000hz_config);
// channel_spectrum.set_decimation_factor(1);
update_params();
+20
View File
@@ -1491,6 +1491,26 @@ constexpr fir_taps_real<64> taps_64_lp_1875_2166{
}},
};
/* 1st Wideband FM demod baseband filter of audio AM tones ,
to pass all DSB band of AM fsubcarrier 2.4Khz mod. with APT */
/* 24kHz int16_t input
* -> FIR filter, BPF center 2k4 carrier ,APT BW 2kHz
* -> 12kHz int16_t output, gain of 1.0 (I think).
*/
constexpr fir_taps_real<64> taps_64_bpf_2k4_bw_2k{
.low_frequency_normalized = -0.1875f, // not updated, this is just for LPF , waterfall GUI, we are not using in BPF NOAA app.
.high_frequency_normalized = 0.1875f, // not used GUI in NOAA App.
.transition_normalized = 0.03f, // not used GUI in NOAA app.
.taps = {{-45, -29, 32, 63, 0, -125, -181, -81, 61,
0, -329, -635, -551, -147, 0, -547, -1404, -1625,
-849, 0, -414, -2118, -3358, -2422, 0, 911, -1792,
-6126, -6773, 0, 11839, 21131, 21131, 11839, 0, -6773,
-6126, -1792, 911, 0, -2422, -3358, -2118, -414, 0,
-849, -1625, -1404, -547, 0, -147, -551, -635, -329,
0, 61, -81, -181, -125, 0, 63, 32, -29,
-45}},
};
// TPMS decimation filters ////////////////////////////////////////////////
// IFIR image-reject filter: fs=2457600, pass=100000, stop=407200, decim=4, fout=614400
+1 -1
View File
@@ -25,7 +25,7 @@
#define __JAMMER_H__
#define JAMMER_CH_WIDTH 1000000
#define JAMMER_MAX_CH 24
#define JAMMER_MAX_CH 80
namespace jammer {
+1 -1
View File
@@ -69,7 +69,7 @@ struct SharedMemory {
union {
ToneData tones_data;
struct {
JammerChannel jammer_channels[24];
JammerChannel jammer_channels[80];
HopperChannel hopper_channels[24];
} dummy_seperate;
uint8_t data[512];
+4
View File
@@ -31,6 +31,8 @@ namespace ui {
// default font height
#define UI_POS_DEFAULT_HEIGHT 16
// small height
#define UI_POS_DEFAULT_HEIGHT_SMALL 8
// default font width
#define UI_POS_DEFAULT_WIDTH 8
// px position of the linenum-th character (Y)
@@ -55,6 +57,8 @@ namespace ui {
#define UI_POS_HEIGHT_REMAINING(linenum) ((int)(screen_height - ((linenum)*UI_POS_DEFAULT_HEIGHT)))
// remaining px from the charnum-th character to the right of the screen
#define UI_POS_WIDTH_REMAINING(charnum) ((int)(screen_width - ((charnum)*UI_POS_DEFAULT_WIDTH)))
// px width of the screen
#define UI_POS_MAXHEIGHT (screen_height)
// Escape sequences for colored text; second character is index into term_colors[]
#define STR_COLOR_BLACK "\x1B\x00"
+200 -2
View File
@@ -1,6 +1,8 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2025 RocketGod
* Copyright (C) 2025 HTotoo
*
* This file is part of PortaPack.
*
@@ -2667,7 +2669,6 @@ bool Waveform::is_clickable() const {
}
void Waveform::getAccessibilityText(std::string& result) {
// no idea what this is in use in any places, but others have it
result = paused_ ? "paused waveform" : "waveform";
}
@@ -2689,7 +2690,6 @@ bool Waveform::on_key(const KeyEvent key) {
}
bool Waveform::on_keyboard(const KeyboardEvent key) {
// no idea what this is for, but others have it
if (!clickable_) return false;
if (key == 32 || key == 10) {
@@ -2822,6 +2822,204 @@ void Waveform::paint(Painter& painter) {
}
}
/* GraphEq *************************************************************/
GraphEq::GraphEq(
Rect parent_rect,
bool clickable)
: Widget{parent_rect},
clickable_{clickable},
bar_heights(NUM_BARS, 0),
prev_bar_heights(NUM_BARS, 0) {
if (clickable) {
set_focusable(true);
// previous_data.resize(length_, 0);
}
}
void GraphEq::set_parent_rect(const Rect new_parent_rect) {
Widget::set_parent_rect(new_parent_rect);
calculate_params();
}
void GraphEq::calculate_params() {
y_top = screen_rect().top();
RENDER_HEIGHT = parent_rect().height();
BAR_WIDTH = (parent_rect().width() - (BAR_SPACING * (NUM_BARS - 1))) / NUM_BARS;
HORIZONTAL_OFFSET = screen_rect().left();
}
bool GraphEq::is_paused() const {
return paused_;
}
void GraphEq::set_paused(bool paused) {
paused_ = paused;
needs_background_redraw = true;
set_dirty();
}
bool GraphEq::is_clickable() const {
return clickable_;
}
void GraphEq::getAccessibilityText(std::string& result) {
result = paused_ ? "paused GraphEq" : "GraphEq";
}
void GraphEq::getWidgetName(std::string& result) {
result = "GraphEq";
}
bool GraphEq::on_key(const KeyEvent key) {
if (!clickable_) return false;
if (key == KeyEvent::Select) {
set_paused(!paused_);
if (on_select) {
on_select(*this);
}
return true;
}
return false;
}
bool GraphEq::on_keyboard(const KeyboardEvent key) {
if (!clickable_) return false;
if (key == 32 || key == 10) {
set_paused(!paused_);
if (on_select) {
on_select(*this);
}
return true;
}
return false;
}
bool GraphEq::on_touch(const TouchEvent event) {
if (!clickable_) return false;
switch (event.type) {
case TouchEvent::Type::Start:
focus();
return true;
case TouchEvent::Type::End:
set_paused(!paused_);
if (on_select) {
on_select(*this);
}
return true;
default:
return false;
}
}
void GraphEq::set_theme(Color base_color_, Color peak_color_) {
base_color = base_color_;
peak_color = peak_color_;
set_dirty();
}
void GraphEq::update_audio_spectrum(const AudioSpectrum& spectrum) {
const float bin_frequency_size = 48000.0f / 128;
for (int bar = 0; bar < NUM_BARS; bar++) {
float start_freq = FREQUENCY_BANDS[bar];
float end_freq = FREQUENCY_BANDS[bar + 1];
int start_bin = std::max(1, (int)(start_freq / bin_frequency_size));
int end_bin = std::min(127, (int)(end_freq / bin_frequency_size));
if (start_bin >= end_bin) {
end_bin = start_bin + 1;
}
float total_energy = 0;
int bin_count = 0;
for (int bin = start_bin; bin <= end_bin; bin++) {
total_energy += spectrum.db[bin];
bin_count++;
}
float avg_db = bin_count > 0 ? (total_energy / bin_count) : 0;
// Manually boost highs for better visual balance
float treble_boost = 1.0f;
if (bar == 10)
treble_boost = 1.7f;
else if (bar >= 9)
treble_boost = 1.3f;
else if (bar >= 7)
treble_boost = 1.3f;
// Mid emphasis for a V-shape effect
float mid_boost = 1.0f;
if (bar == 4 || bar == 5 || bar == 6) mid_boost = 1.2f;
float amplified_db = avg_db * treble_boost * mid_boost;
if (amplified_db > 255) amplified_db = 255;
float band_scale = 1.0f;
int target_height = (amplified_db * RENDER_HEIGHT * band_scale) / 255;
if (target_height > RENDER_HEIGHT) {
target_height = RENDER_HEIGHT;
}
// Adjusted to look nice to my eyes
float rise_speed = 0.8f;
float fall_speed = 1.0f;
if (target_height > bar_heights[bar]) {
bar_heights[bar] = bar_heights[bar] * (1.0f - rise_speed) + target_height * rise_speed;
} else {
bar_heights[bar] = bar_heights[bar] * (1.0f - fall_speed) + target_height * fall_speed;
}
}
set_dirty();
}
void GraphEq::paint(Painter& painter) {
if (!visible()) return;
if (!is_calculated) { // calc positions first
calculate_params();
is_calculated = true;
}
if (needs_background_redraw) {
painter.fill_rectangle(screen_rect(), Theme::getInstance()->bg_darkest->background);
needs_background_redraw = false;
}
if (paused_) {
return;
}
const int num_segments = RENDER_HEIGHT / SEGMENT_HEIGHT;
uint16_t bottom = screen_rect().bottom();
for (int bar = 0; bar < NUM_BARS; bar++) {
int x = HORIZONTAL_OFFSET + bar * (BAR_WIDTH + BAR_SPACING);
int active_segments = (bar_heights[bar] * num_segments) / RENDER_HEIGHT;
if (prev_bar_heights[bar] > active_segments) {
int clear_height = (prev_bar_heights[bar] - active_segments) * SEGMENT_HEIGHT;
int clear_y = bottom - prev_bar_heights[bar] * SEGMENT_HEIGHT;
painter.fill_rectangle({x, clear_y, BAR_WIDTH, clear_height}, Theme::getInstance()->bg_darkest->background);
}
for (int seg = 0; seg < active_segments; seg++) {
int y = bottom - (seg + 1) * SEGMENT_HEIGHT;
if (y < y_top) break;
Color segment_color = (seg >= active_segments - 2 && seg < active_segments) ? peak_color : base_color;
painter.fill_rectangle({x, y, BAR_WIDTH, SEGMENT_HEIGHT - 1}, segment_color);
}
prev_bar_heights[bar] = active_segments;
}
}
/* VuMeter **************************************************************/
VuMeter::VuMeter(
+62
View File
@@ -1,6 +1,8 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2025 RocketGod
* Copyright (C) 2025 HTotoo
*
* This file is part of PortaPack.
*
@@ -1015,6 +1017,66 @@ class Waveform : public Widget {
bool if_ever_painted_pause{false}; // for prevent the "hidden" label keeps painting and being expensive
};
class GraphEq : public Widget {
public:
std::function<void(GraphEq&)> on_select{};
GraphEq(Rect parent_rect, bool clickable = false);
GraphEq(const GraphEq&) = delete;
GraphEq(GraphEq&&) = delete;
GraphEq& operator=(const GraphEq&) = delete;
GraphEq& operator=(GraphEq&&) = delete;
bool is_paused() const;
void set_paused(bool paused);
bool is_clickable() const;
void paint(Painter& painter) override;
bool on_key(const KeyEvent key) override;
bool on_touch(const TouchEvent event) override;
bool on_keyboard(const KeyboardEvent event) override;
void set_parent_rect(const Rect new_parent_rect) override;
void getAccessibilityText(std::string& result) override;
void getWidgetName(std::string& result) override;
void update_audio_spectrum(const AudioSpectrum& spectrum);
void set_theme(Color base_color, Color peak_color);
private:
bool is_calculated{false};
bool paused_{false};
bool clickable_{false};
bool needs_background_redraw{true}; // Redraw background only when needed.
Color base_color = Color(255, 0, 255);
Color peak_color = Color(255, 255, 255);
std::vector<ui::Dim> bar_heights;
std::vector<ui::Dim> prev_bar_heights;
ui::Dim y_top = 2 * 16;
ui::Dim RENDER_HEIGHT = 288;
ui::Dim BAR_WIDTH = 20;
ui::Dim HORIZONTAL_OFFSET = 2;
static const int NUM_BARS = 11;
static const int BAR_SPACING = 2;
static const int SEGMENT_HEIGHT = 10;
static constexpr std::array<int16_t, NUM_BARS + 1> FREQUENCY_BANDS = {
375, // Bass warmth and low rumble (e.g., deep basslines, kick drum body)
750, // Upper bass punch (e.g., bass guitar punch, kick drum attack)
1500, // Lower midrange fullness (e.g., warmth in vocals, guitar body)
2250, // Midrange clarity (e.g., vocal presence, snare crack)
3375, // Upper midrange bite (e.g., instrument definition, vocal articulation)
4875, // Presence and edge (e.g., guitar bite, vocal sibilance start)
6750, // Lower brilliance (e.g., cymbal shimmer, vocal clarity)
9375, // Brilliance and air (e.g., hi-hat crispness, breathy vocals)
13125, // High treble sparkle (e.g., subtle overtones, synth shimmer)
16875, // Upper treble airiness (e.g., faint harmonics, room ambiance)
20625, // Top-end sheen (e.g., ultra-high harmonics, noise floor)
24375 // Extreme treble limit (e.g., inaudible overtones, signal cutoff, static)
};
void calculate_params(); // re calculate some parameters based on parent_rect()
};
class VuMeter : public Widget {
public:
VuMeter(Rect parent_rect, uint32_t LEDs, bool show_max);
+1 -2
View File
@@ -3,6 +3,5 @@
Licensed under [GNU GPL v3](../../../LICENSE)
USAGE:
- Copy file from: https://raw.githubusercontent.com/kx1t/planefence-airlinecodes/main/airlinecodes.txt
- Run Python 3 script: `./make_airlines_db.py`
- Copy file to /ADSB folder on SDCARD
- Move file "airlines.db" to /ADSB folder on SDCARD
@@ -325,7 +325,7 @@ AMT,ATA Airlines,,United States
AMU,Air Macau,AIR MACAO,Macao
AMV,AMC Airlines,,Egypt
AMW,Air Midwest,,United States
AMX,Aeroméxico,AEROMEXICO,Mexico
AMX,Aeromexico,AEROMEXICO,Mexico
AMY,Air Ambar,AIR AMBAR,Dominican Republic
AMZ,Amiya Airline,AMIYA AIR,Nigeria
ANA,All Nippon Airways,ALL NIPPON,Japan
@@ -408,7 +408,7 @@ ARC,Air Routing International Corp.,,United States
ARD,Aerocondor,,Portugal
ARE,Aires Aerovías de Integración Regional S.A.,AIRES,Colombia
ARF,Aero Flight,,Germany
ARG,Aerolíneas Argentinas,ARGENTINA,Argentina
ARG,Aerolineas Argentinas,ARGENTINA,Argentina
ARH,Aerohelicopteros,AEROHELCA,Venezuela
ARI,Aero Vics,AEROVICS,Mexico
ARJ,Aerojet de Costa Rica S.A.,,Costa Rica
@@ -820,7 +820,7 @@ BNT,Bentiu Air Transport,BENTIU AIR,Sudan
BNV,Benane Aviation Corporation,BENANE,Mauritania
BNW,British North West Airlines,BRITISH NORTH,United Kingdom
BNX,LAI - Línea Aérea IAACA,AIR BARINAS,Venezuela
BNZ,Aerolíneas Bonanza,AERO BONANZA,Mexico
BNZ,Bonza,BONZA,Australia
BOA,Boniair,KUMANOVO,Macedonia
BOB,Backbone A/S,BACKBONE,Denmark
BOC,Aerobona,AEROBONA,Mexico
@@ -1465,6 +1465,7 @@ DET,DETA Air,SAMAL,Kazakhstan
DEV,Red Devils Parachute Display Team,RED DEVILS,United Kingdom
DFA,Aero Coach Aviation,AERO COACH,United States
DFC,Aeropartner,DARK BLUE,Czech Republic
DFL,Babcock AirAmbulance,MEDIFLIGHT,Sweden
DFS,Dwyer Aircraft Services,DWYAIR,United States
DGA,Yellow River Delta General Aviation,YELLOW RIVER,China
DGO,DGO Jet,DGO JET,Mexico
@@ -1592,8 +1593,10 @@ DVA,Discovery Airways,DISCOVERY AIRWAYS,United States
DVB,Don Avia,DONSEBAI,Kazakhstan
DVI,Aero Davinci International,AERO DAVINCI,Mexico
DVN,Adventia,,Spain
DVY,Legends Airways,DEVIL RAY,United States
DWA,Dense Airways,,United States
DWD,Deutsche Luftverkehrsgesellschaft (DLT),,Germany
DWI,Arajet,DOMINICAN,Dominican Republic
DWN,Dawn Air,DAWN AIR,United States
DWR,Delaware Skyways,DELAWARE,United States
DWT,Darwin Airline,DARWIN,Switzerland
@@ -1905,6 +1908,7 @@ EZJ,Ezjet GT,GUYANA JET,Guyana
EZS,easyJet Switzerland,TOPSWISS,Switzerland
EZX,Eagle Express Air Charter,EAGLEXPRESS,Malaysia
EZY,easyJet UK,EASY,United Kingdom
EZZ,ETF Airways,ENTERPRIZE,Croatia
FAB,First Air,FIRST AIR,Canada
FAC,Atlantic Helicopters,FAROECOPTER,Denmark
FAD,Rog-Air,AIR FRONTIER,Canada
@@ -2034,7 +2038,7 @@ FJI,Fiji Airways,PACIFIC,Fiji
FJK,Fly Jet Kz,,Kazakhstan
FJM,Fly Jamaica Airways,GREENHEART,Jamaica
FJO,Flexjet Operations Malta Limited,FLEX MALTA,Malta
FRJ,Fly Jordan,SOLITAIRE AIR,Jordan
FJR,Fly Jordan,SOLITAIRE AIR,Jordan
FJS,Florida Jet Service,FLORIDAJET,United States
FKA,Flying kangaroo Airline,,Australia
FKI,FLM Aviation Mohrdieck,KIEL AIR,Germany
@@ -2397,7 +2401,7 @@ GPL,DLR,GERMAN POLAR,Germany
GPM,Grup Air-Med,GRUPOMED,Spain
GPR,GPM Aeroservicio,GPM AEROSERVICIO,Mexico
GPX,GP Aviation,,Bulgaria
GRA,Guardian Air Asset Management,FLEX,South Africa
GRA,Costa Rica Green Airways,,,Costa Rica
GRB,Phoenix Air Group,GREY BIRD,United States
GRD,National Grid plc,GRID,United Kingdom
GRE,Air Scotland,GREECE AIRWAYS,Greece
@@ -2459,7 +2463,7 @@ GWN,Gwyn Aviation,GWYN,United Kingdom
GWR,Aura Airlines,LEMON,Spain
GWS,General Airways,GENAIR,South Africa
GWY,USA3000 Airlines,GETAWAY,United States
GXA,Grixona,GRIXONA,Moldova
GXA,GlobalX Airlines,GEMINI,United States
GXG,GermanXL,,Germany
GXL,Star XL German Airlines,STARDUST,Germany
GXY,Galaxy Airlines,GALAX,Japan
@@ -2608,6 +2612,7 @@ HMC,Heliamerica De Mexico,HELIAMERICA,Mexico
HMD,Charlie Hammonds Flying Service,HAMMOND,United States
HME,Babcock MCS,HELIMED,United Kingdom
HMF,Norrlandsflyg,LIFEGUARD SWEDEN,Sweden
HMJ,Harmony Jets,HARMONG JETS,Malta
HMM,Hamra Air,HAMRA,United Arab Emirates
HMP,Papair Terminal,PAPAIR TERMINAL,Haiti
HMR,North American Charters,HAMMER,Canada
@@ -2623,6 +2628,7 @@ HNR,Haiti National Airlines (HANA),HANAIR,Haiti
HNT,Helicopteros Internacionales,HELICOP INTER,Mexico
HNX,Hankook Airline,,South Korea
HOA,Hola Airlines,HOLA,Spain
HOB,HL Aircraft LLC,HOBBY JET,United States
HOG,Mahogany Air Charters,HOGAN AIR,Zambia
HOL,Holiday Airlines (US Airline),HOLIDAY,United States
HOM,Aero Homex,AERO HOMEX,Mexico
@@ -3077,7 +3083,7 @@ JSM,Jet Stream,,Moldova
JSN,Suncor Energy Inc,JETSUN,Canada
JSP,Palmer Aviation,PALMER,United Kingdom
JSR,Jusur airways,,Egypt
JST,Eastern Australia Airlines,,Australia
JST,Jetstar Airways,,Australia
JSV,Japan Aircraft Service,,Japan
JSW,Jigsaw Project,,United Kingdom
JSX,JetSuiteX Air,BIGSTRIPE,United States
@@ -3617,6 +3623,7 @@ MAK,MAT Macedonian Airlines,MAKAVIO,Macedonia
MAL,Morningstar Air Express,MORNINGSTAR,Canada
MAM,Aeródromo De La Mancha,AEROMAN,Spain
MAN,Mannion Air Charter,MANNION,United States
MAO,Mayo Clinic,MAYO,United States
MAQ,Mac Aviation,MAC AVIATION,Spain
MAR,March Helicopters,MARCH,United Kingdom
MAS,Malaysia Airlines,MALAYSIAN,Malaysia
@@ -3633,6 +3640,7 @@ MBC,Airjet Exploracao Aerea de Carga,MABECO,Angola
MBE,Martin-Baker,MARTIN,United Kingdom
MBG,Zephyr Aviation,CHALGROVE,United Kingdom
MBI,Mountain Bird,MOUNTAIN BIRD,United States
MBK,Chrono Jet,MATBLACK,Canada
MBL,First City Air,FIRST CITY,United Kingdom
MBN,Zambian Airways,ZAMBIANA,Zambia
MBO,Mobil Oil,MOBIL,Canada
@@ -4441,6 +4449,7 @@ PDI,Paradise Island Airways,PARADISE ISLAND,United States
PDQ,PDQ Air Charter,DISPATCH,United States
PDR,COMAV,SPEEDSTER,Namibia
PDT,Piedmont Airlines,PIEDMONT,United States
PDU,Purdue University,PURDUE,United States
PDV,Elicar,ELICAR,Italy
PDY,Pen-Avia,PENDLEY,United Kingdom
PEA,Pan Europeenne Air Service,,France
@@ -4564,6 +4573,7 @@ PNH,Panh,KUBAN LIK,Russia
PNK,Air Pink,AIRPINK,Serbia
PNL,Aero Personal,AEROPERSONAL,Mexico
PNM,Panorama,PANORAMA,Spain
PNO,Panorama Aviation,PANO,Canada
PNP,Pineapple Air,PINEAPPLE AIR,Bahamas
PNR,PAN Air,SKYJET,Spain
PNS,Survey Udara (Penas),PENAS,Indonesia
@@ -4931,6 +4941,7 @@ RNB,Rosneft-Baltika,ROSBALT,Russia
RND,Rutland Aviation,RUTLAND,United Kingdom
RNE,Air Salone,AIR SALONE,Sierra Leone
RNG,Orange Aircraft Leasing,ORANGE,Netherlands
RNI,Rennia Aviation,RENNIA,United States
RNM,Aeronem Air Cargo,AEROMNEM,Ecuador
RNR,Air Cargo Masters,RUNNER,United States
RNS,Ronso,RONSO,Mexico
@@ -5216,6 +5227,7 @@ SFU,Solent Flight,SAINTS,United Kingdom
SFW,Safi Airways,SAFI,AF
SFX,S.K. Logistics,SWAMP FOX,United States
SFY,Gulf Flite Center,SKY FLITE,United States
SFZ,Pionair,SKYFORCE,Australia
SGA,Air Saigon,AIR SAIGON,Vietnam
SGB,Sky King Inc.,SONGBIRD,United States
SGC,SGC Aviation,SAINT GEORGE,Austria
@@ -5270,7 +5282,7 @@ SIE,Sierra Express,SEREX,United States
SIH,Skynet Airlines,BLUEJET,Ireland
SII,Aero Servicios Ejecutivos Internacionales,ASEISA,Mexico
SIJ,Seco International,,Japan
SIL,Servicios Aeronáuticos Integrales,SERVICIOS INTEGRALES,Mexico
SIL,Silver Airways,SILVER WINGS,United States
SIM,Star Air,,Sierra Leone
SIO,Sirio,SIRIO,Italy
SIP,Air Spirit,AIR SPIRIT,United States
@@ -5802,6 +5814,7 @@ TKC,Tikal Jets Airlines,TIKAL,Guatemala
TKE,Take Air Line,ISLAND BIRD,France
TKJ,Tarkim Aviation,TARKIM AVIATION,Turkey
TKK,FlyADVANCED,,United States
TKM,JM Family Aviation,TACOMA,United States
TKR,Canadian Interagency Forest Fire Centre,TANKER,Canada
TKS,Tomsk-Avia,,Russia
TKX,Tropical International Airways,TROPEXPRESS,Saint Kitts and Nevis
@@ -5827,6 +5840,7 @@ TLW,Teamline Air,Teamline,Austria
TLX,Telesis Transair,TELESIS,United States
TLY,Top Fly,TOPFLY,Spain
TMA,Trans Mediterranean Airlines,TANGO LIMA,Lebanon
TMB,Volato,TOMBO,United States
TMC,Travel Management Company,TRAIL BLAZER,United States
TMD,Transmandu,TRANSMANDU,Venezuela
TME,Aero Taxi del Centro de Mexico,TAXICENTRO,Mexico
@@ -6086,6 +6100,7 @@ UDC,DonbassAero,DONBASS AERO,Ukraine
UDN,Dniproavia,DNIEPRO,Ukraine
UEA,United Eagle Airlines,UNITED EAGLE,China
UED,Air LA,AIR L-A,United States
UEE,United Eagle,EAGLE SLOVENIA,Slovenia
UEJ,Jetcorp,,United States
UES,Ues-Avia Aircompany,AVIASYSTEM,Ukraine
UEU,United European Airlines,UNITED EUROPEAN,Romania
@@ -6200,7 +6215,7 @@ VAL,Voyageur Airways,VOYAGEUR,Canada
VAM,Ameravia,AMERAVIA,Uruguay
VAN,Caravan Air,CAMEL,Mauritania
VAP,Phuket Air,PHUKET AIR,Thailand
VAR,Veca Airlines,VECA,El Salvador
VAR,United Aviate Academy,VARNEY,United States
VAS,ATRAN Cargo Airlines,ATRAN,Russian Federation
VAT,Visionair,VISIONAIR,Ireland
VAU,V Australia Airlines,,Australia
@@ -6278,7 +6293,7 @@ VIS,Vision Air International,,Pakistan
VIT,Aviastar Mandiri,AVIASTAR,Indonesia
VIV,Aeroenlaces Nacionales,AEROENLACES,Mexico
VIZ,Aerovis Airlines,AEROVIZ,Ukraine
VJA,ValuJet Airlines,CRITTER,United States
VJA,Vista America,ICONIC,United States
VJC,Vietjet Air,VIETJET,Vietnam
VJE,AvJet Routing,,United Arab Emirates
VJM,Viajes Ejecutivos Mexicanos,VIAJES MEXICANOS,Mexico
@@ -6320,7 +6335,7 @@ VNG,Aero Servicios Vanguardia,VANGUARDIA,Mexico
VNK,Vipport Joint Stock Company,,Russia
VNL,Vanilla Air,VANILLA,Japan
VNP,Virgin Pacific,,Fiji
VNT,Avient Air Zambia,AVIENT,Zambia
VNT,Ventura,VENTURA,United States
VNX,Fly Advance,VANCE,United States
VNZ,Tbilaviamsheni,TBILAVIA,Georgia
VOA,Viaggio Air,VIAGGIO,Bulgaria
@@ -6331,7 +6346,7 @@ VOI,Volaris,VOLARIS,Mexico
VOL,Blue Chip Jet,BLUE SPEED,Sweden
VOO,Volotea,,Spain
VOR,Flight Calibration Services Ltd.,FLIGHT CAL,United Kingdom
VOS,Rovos Air,ROVOS,South Africa
VOS,Volaris El Salvador,JETSAL,South Africa
VOZ,Virgin Australia,VELOCITY,Australia
VPA,DanubeWings,VIP TAXI,Slovakia
VPB,Veteran Air,VETERAN,Ukraine
@@ -6422,7 +6437,7 @@ WAU,Wizz Air Ukraine,,Ukraine
WAV,Warbelow's Air Ventures,WARBELOW,United States
WAW,Wings Airways,WING SHUTTLE,United States
WAY,Airways,GARONNE,France
WAZ,Wizz Air,WIZZ SKY,United Arab Emirates
WAZ,Wizz Air Abu Dabi,WIZZ SKY,United Arab Emirates
WBA,Finncomm Airlines,WESTBIRD,Finland
WBR,Multi-Aero,WEBER,United States
WCA,West Coast Airways,WEST-LEONE,Sierra Leone
@@ -6499,6 +6514,8 @@ WLX,West Air Luxembourg,WEST LUX,Luxembourg
WMA,Watermakers Air,WATERMAKERS,United States
WML,Chantilly Air,MARLIN,United States
WMN,Trident Aircraft,WATERMAN,United States
WMT,Wizz Air Malta,WIZZAIR MALTA,Malta
WMU,W.M. Uni. College of Aviation,SKYBRONCO,United States
WNA,Winair,WINAIR,United States
WNR,Wondair on Demand Aviation,WONDAIR,Spain
WOA,World Airways,WORLD,United States
@@ -6535,6 +6552,7 @@ WTP,Westpoint Air,WESTPOINT,Canada
WTV,Western Aviators,WESTAVIA,United States
WUE,Adolf Wurth GmbH,FASTY,Germany
WUK,Wizz Air UK,WIZZ GO,United Kingdom
WUP,Wheels Up,UPJET,United States
WVA,Hand D Aviation,WABASH VALLEY,United States
WVL,Wizz Air Bulgaria,WIZZBUL,Bulgaria
WVZ,Winair,,HR
@@ -6548,7 +6566,7 @@ WYC,Wycombe Air Centre,WYCOMBE,United Kingdom
WYG,Wyoming Airlines,WYOMING,United States
WYT,2 Sqn No 1 Elementary Flying Training School,WYTON,United Kingdom
WZP,Zip,ZIPPER,Canada
WZZ,Wizz Air,WIZZAIR,Hungary
WZZ,Wizz Air Hungary,WIZZAIR,Hungary
X9F,FRA Air,,Germany
XAA,Aeronautical Radio Inc,,United States
XAB,Xabre Aerolineas,AERO XABRE,Mexico
@@ -1,5 +1,6 @@
#!/usr/bin/env python3
# Copyright (C) 2021 ArjanOnwezen
# Copyright (C) 2025 Tommaso Ventafridda
#
# 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
@@ -22,26 +23,251 @@
# as a source.
# -------------------------------------------------------------------------------------
import csv
import os
import shutil
import unicodedata
icao_codes=bytearray()
airlines_countries=bytearray()
row_count=0
database=open("airlines.db", "wb")
with open('airlinecodes.txt', 'rt', encoding="utf-8") as csv_file:
sorted_lines=sorted(csv_file.readlines()[1:])
for row in csv.reader(sorted_lines, quotechar='"', delimiter=',', quoting=csv.QUOTE_ALL, skipinitialspace=True):
icao_code=row[0]
# Normalize some unicode characters
airline=unicodedata.normalize('NFKD', row[1][:32]).encode('ascii', 'ignore')
country=unicodedata.normalize('NFKD', row[3][:32]).encode('ascii', 'ignore')
if len(icao_code) == 3 :
airline_padding=bytearray()
country_padding=bytearray()
icao_codes=icao_codes+bytearray(icao_code+'\0', encoding='ascii')
airline_padding=bytearray('\0' * (32 - len(airline)), encoding='ascii')
country_padding=bytearray('\0' * (32 - len(country)), encoding='ascii')
airlines_countries=airlines_countries+bytearray(airline+airline_padding+country+country_padding)
row_count+=1
database.write(icao_codes+airlines_countries)
print("Total of", row_count, "ICAO codes stored in database")
import urllib.request
from dataclasses import dataclass
from typing import Dict, Set, Tuple
@dataclass
class AirlineRecord:
"""Represents an airline record with all relevant fields"""
icao_code: str
airline: str
country: str
def __hash__(self):
return hash(self.icao_code)
def __eq__(self, other):
if not isinstance(other, AirlineRecord):
return False
return self.icao_code == other.icao_code
def data_equals(self, other):
"""Check if all data fields are equal (excluding ICAO code which is the key)"""
if not isinstance(other, AirlineRecord):
return False
return self.airline == other.airline and self.country == other.country
def backup_previous_file() -> bool:
"""Backup the existing airlinecodes.txt file if it exists. Returns True if backup was made."""
if os.path.exists("airlinecodes.txt"):
shutil.copy2("airlinecodes.txt", "airlinecodes_previous.txt")
print("Backed up previous airline codes file")
return True
return False
def download_airline_codes() -> None:
"""Download the airline codes file from GitHub"""
url = "https://raw.githubusercontent.com/kx1t/planefence-airlinecodes/main/airlinecodes.txt"
print(f"Downloading airline codes database from {url}...")
try:
urllib.request.urlretrieve(url, "airlinecodes.txt")
print("Download completed successfully.")
except Exception as e:
print(f"Error downloading airline codes file: {e}")
raise
def parse_airline_file(filename: str) -> Dict[str, AirlineRecord]:
"""Parse airline codes file and return a dictionary of records"""
records = {}
if not os.path.exists(filename):
return records
try:
with open(filename, "rt", encoding="utf-8") as csv_file:
sorted_lines = sorted(csv_file.readlines()[1:])
for row in csv.reader(
sorted_lines,
quotechar='"',
delimiter=",",
quoting=csv.QUOTE_ALL,
skipinitialspace=True,
):
if len(row) >= 4: # Ensure we have enough columns
icao_code = row[0]
# Normalize some unicode characters
airline = (
unicodedata.normalize("NFKD", row[1][:32])
.encode("ascii", "ignore")
.decode("ascii")
)
country = (
unicodedata.normalize("NFKD", row[3][:32])
.encode("ascii", "ignore")
.decode("ascii")
)
if len(icao_code) == 3:
records[icao_code] = AirlineRecord(
icao_code=icao_code, airline=airline, country=country
)
except Exception as e:
print(f"Warning: Could not parse airline file {filename}: {e}")
return {}
return records
def compare_records(
old_records: Dict[str, AirlineRecord], new_records: Dict[str, AirlineRecord]
) -> Tuple[Set[str], Set[str], Set[str]]:
"""Compare old and new records, return sets of new, deleted, and changed ICAO codes"""
old_keys = set(old_records.keys())
new_keys = set(new_records.keys())
new_icao_codes = new_keys - old_keys
deleted_icao_codes = old_keys - new_keys
# Check for changes in existing records
changed_icao_codes = set()
for icao in old_keys & new_keys:
if not old_records[icao].data_equals(new_records[icao]):
changed_icao_codes.add(icao)
return new_icao_codes, deleted_icao_codes, changed_icao_codes
def write_database(records: Dict[str, AirlineRecord]) -> int:
"""Write records to database file using original format"""
icao_codes = bytearray()
airlines_countries = bytearray()
row_count = 0
# Sort by ICAO code for consistency
sorted_records = sorted(records.values(), key=lambda r: r.icao_code)
for record in sorted_records:
# Normalize and encode data (same as original)
airline = unicodedata.normalize("NFKD", record.airline[:32]).encode(
"ascii", "ignore"
)
country = unicodedata.normalize("NFKD", record.country[:32]).encode(
"ascii", "ignore"
)
# Add ICAO code with null terminator (original format)
icao_codes = icao_codes + bytearray(record.icao_code + "\0", encoding="ascii")
# Add padded data fields (original format)
airline_padding = bytearray("\0" * (32 - len(airline)), encoding="ascii")
country_padding = bytearray("\0" * (32 - len(country)), encoding="ascii")
airlines_countries = airlines_countries + bytearray(
airline + airline_padding + country + country_padding
)
row_count += 1
with open("airlines.db", "wb") as database:
database.write(icao_codes + airlines_countries)
return row_count
def create_database() -> None:
"""Create the airline database from the downloaded file"""
# Backup existing file before downloading new one
has_previous = backup_previous_file()
# Download new file
download_airline_codes()
# Parse both files for comparison
old_records = {}
if has_previous:
print("Parsing previous airline codes file...")
old_records = parse_airline_file("airlinecodes_previous.txt")
print(f"Found {len(old_records)} records in previous file")
print("Parsing new airline codes file...")
new_records = parse_airline_file("airlinecodes.txt")
print(f"Found {len(new_records)} records in new file")
# Compare records if we have a previous version
if old_records:
new_icao_codes, deleted_icao_codes, changed_icao_codes = compare_records(
old_records, new_records
)
# Print change statistics
print("\n" + "=" * 50)
print("AIRLINE CODES CHANGE SUMMARY")
print("=" * 50)
print(f"New records: {len(new_icao_codes):>8}")
print(f"Deleted records: {len(deleted_icao_codes):>8}")
print(f"Changed records: {len(changed_icao_codes):>8}")
print(f"Total records: {len(new_records):>8}")
print("=" * 50)
# Show examples of changes (limited to avoid spam)
if new_icao_codes and len(new_icao_codes) <= 15:
print(f"\nNew ICAO codes: {', '.join(sorted(new_icao_codes))}")
elif new_icao_codes:
sample_new = sorted(list(new_icao_codes))[:10]
print(
f"\nSample new ICAO codes: {', '.join(sample_new)} (and {len(new_icao_codes)-10} more)"
)
if deleted_icao_codes and len(deleted_icao_codes) <= 15:
print(f"Deleted ICAO codes: {', '.join(sorted(deleted_icao_codes))}")
elif deleted_icao_codes:
sample_deleted = sorted(list(deleted_icao_codes))[:10]
print(
f"Sample deleted ICAO codes: {', '.join(sample_deleted)} (and {len(deleted_icao_codes)-10} more)"
)
if changed_icao_codes and len(changed_icao_codes) <= 15:
print(f"Changed ICAO codes: {', '.join(sorted(changed_icao_codes))}")
elif changed_icao_codes:
sample_changed = sorted(list(changed_icao_codes))[:10]
print(
f"Sample changed ICAO codes: {', '.join(sample_changed)} (and {len(changed_icao_codes)-10} more)"
)
# Show some specific examples of changes
if changed_icao_codes:
print(f"\nExample changes:")
for icao in sorted(list(changed_icao_codes))[:3]:
old_rec = old_records[icao]
new_rec = new_records[icao]
if old_rec.airline != new_rec.airline:
print(
f" {icao}: Airline '{old_rec.airline}''{new_rec.airline}'"
)
if old_rec.country != new_rec.country:
print(
f" {icao}: Country '{old_rec.country}''{new_rec.country}'"
)
else:
print(
"\nNo previous airline codes file found - this appears to be the first run"
)
# Create database from new records
print("\nCreating airline database...")
row_count = write_database(new_records)
print("Total of", row_count, "ICAO codes stored in database")
def cleanup_temp() -> None:
"""Cleanup temporary files created during the process"""
if os.path.exists("airlinecodes_previous.txt"):
os.remove("airlinecodes_previous.txt")
if __name__ == "__main__":
try:
create_database()
cleanup_temp()
except Exception as e:
print(f"Error creating airline database: {e}")
else:
print("Airline database created successfully.")
+1 -2
View File
@@ -3,6 +3,5 @@
Licensed under [GNU GPL v3](../../../LICENSE)
USAGE:
- Copy file from: https://opensky-network.org/datasets/metadata/aircraftDatabase.csv
- Run Python 3 script: `./make_icao24_db.py`
- Copy file to /ADSB folder on SDCARD
- Move "icao24.db" file to /ADSB folder on SDCARD
File diff suppressed because it is too large Load Diff
+267 -34
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
# Copyright (C) 2021 ArjanOnwezen
# Copyright (C) 2025 Tommaso Ventafridda
#
# 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
@@ -20,43 +21,275 @@
# -------------------------------------------------------------------------------------
# Create icao24.db, used for ADS-B receiver application, using
# https://opensky-network.org/datasets/metadata/aircraftDatabase.csv
# https://opensky-network.org/datasets/metadata/aircraftDatabase.csv
# as a source.
# -------------------------------------------------------------------------------------
import csv
import unicodedata
icao24_codes=bytearray()
data=bytearray()
row_count=0
import os
import shutil
import urllib.request
from dataclasses import dataclass
from typing import Dict, Set, Tuple
database=open("icao24.db", "wb")
with open('aircraftDatabase.csv', 'rt') as csv_file:
sorted_lines=sorted(csv_file.readlines()[1:])
for row in csv.reader(sorted_lines, quotechar='"', delimiter=',', quoting=csv.QUOTE_ALL, skipinitialspace=True):
# only store in case enough info is available
if len(row) == 27 and len(row[0]) == 6 and len(row[1]) > 0:
icao24_code=row[0][:6].upper()
registration=row[1][:8].encode('ascii', 'ignore')
manufacturer=row[3][:32].encode('ascii', 'ignore')
model=row[4][:32].encode('ascii', 'ignore')
# in case icao aircraft type isn't, use ac type like BALL for balloon
if len(row[8]) == 3:
actype=row[8][:3].encode('ascii', 'ignore')
else:
actype=row[5][:4].encode('ascii', 'ignore')
owner=row[13][:32].encode('ascii', 'ignore')
operator=row[9][:32].encode('ascii', 'ignore')
#padding
icao24_codes.extend(bytearray(icao24_code+'\0', encoding='ascii'))
registration_padding=bytearray('\0' * (9 - len(registration)), encoding='ascii')
manufacturer_padding=bytearray('\0' * (33 - len(manufacturer)), encoding='ascii')
model_padding=bytearray('\0' * (33 - len(model)), encoding='ascii')
actype_padding=bytearray('\0' * (5 - len(actype)), encoding='ascii')
owner_padding=bytearray('\0' * (33 - len(owner)), encoding='ascii')
operator_padding=bytearray('\0' * (33 - len(operator)), encoding='ascii')
data.extend(bytearray(registration+registration_padding+manufacturer+manufacturer_padding+model+model_padding+actype+actype_padding+owner+owner_padding+operator+operator_padding))
row_count+=1
database.write(icao24_codes+data)
print("Total of", row_count, "ICAO codes stored in database")
@dataclass
class AircraftRecord:
"""Represents an aircraft record with all relevant fields"""
icao24: str
registration: str
manufacturer: str
model: str
actype: str
owner: str
operator: str
def __hash__(self):
return hash(self.icao24)
def __eq__(self, other):
if not isinstance(other, AircraftRecord):
return False
return self.icao24 == other.icao24
def data_equals(self, other):
"""Check if all data fields are equal (excluding ICAO24 which is the key)"""
if not isinstance(other, AircraftRecord):
return False
return (
self.registration == other.registration
and self.manufacturer == other.manufacturer
and self.model == other.model
and self.actype == other.actype
and self.owner == other.owner
and self.operator == other.operator
)
def backup_previous_csv() -> bool:
"""Backup the existing CSV file if it exists. Returns True if backup was made."""
if os.path.exists("aircraftDatabase.csv"):
shutil.copy2("aircraftDatabase.csv", "aircraftDatabase_previous.csv")
print("Backed up previous CSV file")
return True
return False
def download_aircraft_database() -> None:
"""Download the aircraft database file from OpenSky Network"""
url = "https://opensky-network.org/datasets/metadata/aircraftDatabase.csv"
print(f"Downloading aircraft database from {url}...")
try:
urllib.request.urlretrieve(url, "aircraftDatabase.csv")
print("Download completed successfully.")
except Exception as e:
print(f"Error downloading aircraft database file: {e}")
raise
def parse_csv_file(filename: str) -> Dict[str, AircraftRecord]:
"""Parse CSV file and return a dictionary of records"""
records = {}
if not os.path.exists(filename):
return records
try:
with open(filename, "rt", encoding="utf-8") as csv_file:
sorted_lines = sorted(csv_file.readlines()[1:])
for row in csv.reader(
sorted_lines,
quotechar='"',
delimiter=",",
quoting=csv.QUOTE_ALL,
skipinitialspace=True,
):
# only store in case enough info is available
if len(row) == 27 and len(row[0]) == 6 and len(row[1]) > 0:
icao24_code = row[0][:6].upper()
registration = row[1][:8]
manufacturer = row[3][:32]
model = row[4][:32]
# in case icao aircraft type isn't, use ac type like BALL for balloon
if len(row[8]) == 3:
actype = row[8][:3]
else:
actype = row[5][:4]
owner = row[13][:32]
operator = row[9][:32]
records[icao24_code] = AircraftRecord(
icao24=icao24_code,
registration=registration,
manufacturer=manufacturer,
model=model,
actype=actype,
owner=owner,
operator=operator,
)
except Exception as e:
print(f"Warning: Could not parse CSV file {filename}: {e}")
return {}
return records
def compare_records(
old_records: Dict[str, AircraftRecord], new_records: Dict[str, AircraftRecord]
) -> Tuple[Set[str], Set[str], Set[str]]:
"""Compare old and new records, return sets of new, deleted, and changed ICAO codes"""
old_keys = set(old_records.keys())
new_keys = set(new_records.keys())
new_icao_codes = new_keys - old_keys
deleted_icao_codes = old_keys - new_keys
# Check for changes in existing records
changed_icao_codes = set()
for icao in old_keys & new_keys:
if not old_records[icao].data_equals(new_records[icao]):
changed_icao_codes.add(icao)
return new_icao_codes, deleted_icao_codes, changed_icao_codes
def write_database(records: Dict[str, AircraftRecord]) -> int:
"""Write records to database file using original format"""
# bytearrays to store icao24 codes and data
icao24_codes = bytearray()
data = bytearray()
row_count = 0
# Sort by ICAO code for consistency
sorted_records = sorted(records.values(), key=lambda r: r.icao24)
for record in sorted_records:
# Encode data fields
registration = record.registration.encode("ascii", "ignore")
manufacturer = record.manufacturer.encode("ascii", "ignore")
model = record.model.encode("ascii", "ignore")
actype = record.actype.encode("ascii", "ignore")
owner = record.owner.encode("ascii", "ignore")
operator = record.operator.encode("ascii", "ignore")
# Add ICAO code with null terminator (original format)
icao24_codes.extend(bytearray(record.icao24 + "\0", encoding="ascii"))
# Add padded data fields (original format)
registration_padding = bytearray(
"\0" * (9 - len(registration)), encoding="ascii"
)
manufacturer_padding = bytearray(
"\0" * (33 - len(manufacturer)), encoding="ascii"
)
model_padding = bytearray("\0" * (33 - len(model)), encoding="ascii")
actype_padding = bytearray("\0" * (5 - len(actype)), encoding="ascii")
owner_padding = bytearray("\0" * (33 - len(owner)), encoding="ascii")
operator_padding = bytearray("\0" * (33 - len(operator)), encoding="ascii")
data.extend(
bytearray(
registration
+ registration_padding
+ manufacturer
+ manufacturer_padding
+ model
+ model_padding
+ actype
+ actype_padding
+ owner
+ owner_padding
+ operator
+ operator_padding
)
)
row_count += 1
with open("icao24.db", "wb") as database:
database.write(icao24_codes + data)
return row_count
def create_database() -> None:
"""Create the ICAO24 database from the downloaded file"""
# Backup existing CSV before downloading new one
has_previous = backup_previous_csv()
# Download new CSV
download_aircraft_database()
# Parse both CSV files for comparison
old_records = {}
if has_previous:
print("Parsing previous CSV file...")
old_records = parse_csv_file("aircraftDatabase_previous.csv")
print(f"Found {len(old_records)} records in previous CSV")
print("Parsing new CSV file...")
new_records = parse_csv_file("aircraftDatabase.csv")
print(f"Found {len(new_records)} records in new CSV")
# Compare records if we have a previous version
if old_records:
new_icao_codes, deleted_icao_codes, changed_icao_codes = compare_records(
old_records, new_records
)
# Print change statistics
print("\n" + "=" * 50)
print("CSV CHANGE SUMMARY")
print("=" * 50)
print(f"New records: {len(new_icao_codes):>8}")
print(f"Deleted records: {len(deleted_icao_codes):>8}")
print(f"Changed records: {len(changed_icao_codes):>8}")
print(f"Total records: {len(new_records):>8}")
print("=" * 50)
# Show examples of changes (limited to avoid spam)
if new_icao_codes and len(new_icao_codes) <= 10:
print(f"\nNew ICAO24 codes: {', '.join(sorted(new_icao_codes))}")
elif new_icao_codes:
sample_new = sorted(list(new_icao_codes))[:5]
print(
f"\nSample new ICAO24 codes: {', '.join(sample_new)} (and {len(new_icao_codes)-5} more)"
)
if deleted_icao_codes and len(deleted_icao_codes) <= 10:
print(f"Deleted ICAO24 codes: {', '.join(sorted(deleted_icao_codes))}")
elif deleted_icao_codes:
sample_deleted = sorted(list(deleted_icao_codes))[:5]
print(
f"Sample deleted ICAO24 codes: {', '.join(sample_deleted)} (and {len(deleted_icao_codes)-5} more)"
)
if changed_icao_codes and len(changed_icao_codes) <= 10:
print(f"Changed ICAO24 codes: {', '.join(sorted(changed_icao_codes))}")
elif changed_icao_codes:
sample_changed = sorted(list(changed_icao_codes))[:5]
print(
f"Sample changed ICAO24 codes: {', '.join(sample_changed)} (and {len(changed_icao_codes)-5} more)"
)
else:
print("\nNo previous CSV found - this appears to be the first run")
# Create database from new records
print("\nCreating ICAO24 database...")
row_count = write_database(new_records)
print("Total of", row_count, "ICAO codes stored in database")
def cleanup_previous_csv() -> None:
"""Remove the previous CSV file if it exists"""
if os.path.exists("aircraftDatabase_previous.csv"):
os.remove("aircraftDatabase_previous.csv")
if __name__ == "__main__":
try:
create_database()
cleanup_previous_csv()
except Exception as e:
print(f"Error creating ICAO24 database: {e}")
else:
print("ICAO24 database created successfully.")
@@ -0,0 +1,130 @@
#!/usr/bin/env python3
# Copyright (C) 2025 Tommaso Ventafridda
#
# 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.
#
# -------------------------------------------------------------------------------------
# Create macaddress.db, used for BLE receiver application, using
# https://standards-oui.ieee.org/oui/oui.txt as a source.
# -------------------------------------------------------------------------------------
import urllib.request
import unicodedata
import re
from typing import List, Tuple
def download_oui_file() -> str:
"""Download the OUI file from IEEE"""
url = "https://standards-oui.ieee.org/oui/oui.txt"
print(f"Downloading OUI database from {url}...")
try:
with urllib.request.urlopen(url) as response:
content = response.read().decode("utf-8")
print("Download completed successfully.")
return content
except Exception as e:
print(f"Error downloading OUI file: {e}")
raise
def parse_oui_data(content: str) -> List[Tuple[str, str]]:
"""Parse the OUI file content and extract MAC prefixes and vendor names"""
entries = []
lines = content.split("\n")
for _, line in enumerate(lines):
# Look for lines with (hex) pattern
if "(hex)" in line:
# Extract MAC prefix and vendor name
match = re.match(
r"^([0-9A-F]{2}-[0-9A-F]{2}-[0-9A-F]{2})\s+\(hex\)\s+(.+)$",
line.strip(),
)
if match:
# Remove dashes: "28-6F-B9" -> "286FB9"
mac_prefix = match.group(1).replace(
"-", ""
)
vendor_name = match.group(2).strip()
# Normalize vendor name and limit to 63 characters
vendor_name = (
unicodedata.normalize("NFKD", vendor_name[:63])
.encode("ascii", "ignore")
.decode("ascii")
)
if mac_prefix and vendor_name:
entries.append((mac_prefix, vendor_name))
print(f"Parsed {len(entries)} MAC address entries.")
return entries
def create_database(
entries: List[Tuple[str, str]], output_filename: str = "macaddress.db"
):
"""Create the binary database file."""
# Sort entries by MAC prefix for binary search
entries.sort(key=lambda x: x[0])
mac_prefixes = bytearray()
vendor_data = bytearray()
row_count = 0
for mac_prefix, vendor_name in entries:
# MAC prefix: 6 hex chars + null terminator = 7 bytes
mac_prefixes += bytearray(mac_prefix + "\0", encoding="ascii")
# Vendor name: pad to 64 bytes
vendor_bytes = vendor_name.encode("ascii", "ignore")
vendor_padding = bytearray("\0" * (64 - len(vendor_bytes)), encoding="ascii")
vendor_data += vendor_bytes + vendor_padding
row_count += 1
# Write database: index section followed by data section
with open(output_filename, "wb") as database:
database.write(mac_prefixes + vendor_data)
print(f"Created database '{output_filename}' with {row_count} MAC address entries.")
print(f"Index section: {len(mac_prefixes)} bytes")
print(f"Data section: {len(vendor_data)} bytes")
print(f"Total database size: {len(mac_prefixes) + len(vendor_data)} bytes")
def main():
"""Main function to create the MAC address database."""
try:
oui_content = download_oui_file()
entries = parse_oui_data(oui_content)
if not entries:
print("No valid entries found in OUI file!")
return
create_database(entries)
print("MAC address database creation completed successfully!")
except Exception as e:
print(f"Error creating MAC address database: {e}")
return 1
if __name__ == "__main__":
exit(main())
Binary file not shown.
Binary file not shown.
+12 -34
View File
@@ -1,5 +1,4 @@
# Drone Analog FPV Channels
# https://oscarliang.com/fpv-channels/
# 5.8GHz Analog FPV Channels
f=5865000000,d=FPV Ch A-1
f=5845000000,d=FPV Ch A-2
f=5825000000,d=FPV Ch A-3
@@ -40,30 +39,6 @@ f=5806000000,d=FPV Ch R-5
f=5843000000,d=FPV Ch R-6
f=5880000000,d=FPV Ch R-7
f=5917000000,d=FPV Ch R-8
f=5362000000,d=FPV Ch D-1
f=5399000000,d=FPV Ch D-2
f=5436000000,d=FPV Ch D-3
f=5473000000,d=FPV Ch D-4
f=5510000000,d=FPV Ch D-5
f=5547000000,d=FPV Ch D-6
f=5584000000,d=FPV Ch D-7
f=5621000000,d=FPV Ch D-8
f=5325000000,d=FPV Ch U-1
f=5348000000,d=FPV Ch U-2
f=5366000000,d=FPV Ch U-3
f=5384000000,d=FPV Ch U-4
f=5402000000,d=FPV Ch U-5
f=5420000000,d=FPV Ch U-6
f=5438000000,d=FPV Ch U-7
f=5456000000,d=FPV Ch U-8
f=5474000000,d=FPV Ch O-1
f=5492000000,d=FPV Ch O-2
f=5510000000,d=FPV Ch O-3
f=5528000000,d=FPV Ch O-4
f=5546000000,d=FPV Ch O-5
f=5564000000,d=FPV Ch O-6
f=5582000000,d=FPV Ch O-7
f=5600000000,d=FPV Ch O-8
f=5333000000,d=FPV Ch L-1
f=5373000000,d=FPV Ch L-2
f=5413000000,d=FPV Ch L-3
@@ -72,11 +47,14 @@ f=5493000000,d=FPV Ch L-5
f=5533000000,d=FPV Ch L-6
f=5573000000,d=FPV Ch L-7
f=5613000000,d=FPV Ch L-8
f=5653000000,d=FPV Ch H-1
f=5693000000,d=FPV Ch H-2
f=5733000000,d=FPV Ch H-3
f=5773000000,d=FPV Ch H-4
f=5813000000,d=FPV Ch H-5
f=5853000000,d=FPV Ch H-6
f=5893000000,d=FPV Ch H-7
f=5933000000,d=FPV Ch H-8
# 1.2-1.3GHz Analog Channels
f=1080000000,d=FPV Ch 1
f=1120000000,d=FPV Ch 2
f=1160000000,d=FPV Ch 3
f=1200000000,d=FPV Ch 4
f=1240000000,d=FPV Ch 5
f=1258000000,d=FPV Ch 6
f=1280000000,d=FPV Ch 7
f=1320000000,d=FPV Ch 8
f=1360000000,d=FPV Ch 9
+1 -1
View File
@@ -14,7 +14,7 @@
87,108,FM BROADCAST USA
76,108,FM BROADCAST BRAZIL
76,95,FM BROADCAST JAPAN
65,74,FM BROADCAST RUSSIA
65,74,FM OIRT EAST EU
# CITIZENS BAND RADIO
26,28,CB RADIO
# COMMON PUBLIC SERVICE BANDS
Binary file not shown.