Compare commits

...

69 Commits

Author SHA1 Message Date
gullradriel 4c5275247e update version (#2735)
Co-authored-by: gullradriel <gullradriel@no-mail.com>
2025-07-11 15:57:04 +02:00
gullradriel a0185d28eb update submodule (#2734)
Co-authored-by: gullradriel <gullradriel@no-mail.com>
2025-07-09 21:33:38 +02:00
RocketGod 2500df310f Add radio settings, new app icon, and other UI improvements (#2732) 2025-07-03 11:03:51 -07:00
Totoo 54f9ff116b AIS map improv (#2725)
* AIS map improv

* format code mismatch with vc
2025-06-29 22:47:18 +02:00
Totoo 47f231ad63 Add blue bar to subghzd+weather (#2724) 2025-06-29 19:31:05 +08:00
Netro f90d3fabce Adding simple FSK Rx Processor. Can be used with New Apps. (#2716)
* Work to allow for unique beacon parsing functions.

* Fixing pull.

* Changes.

* Formatting.

* Fix Copyright

* Update firmware/application/apps/ble_rx_app.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update firmware/baseband/proc_btlerx.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* PR suggestions.

* Fix String.

* FSK Rx Improvements. Works for my custom protocol.

* Fix buffer size.

* Refactor

* Formatting.

* Formatting.

* Fixing compiling, and BLE Rx UI/Performance.

* More improvements.

* Fixing stuck state.

* More stuck parsing fix.

* Combining PR changes.

* Improvements from previous PR.

* Fix dbM calculation relative to device RSSI.

* Formatting.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: TJ <tj.baginski@cognosos.com>
2025-06-29 01:02:12 +02:00
RocketGod 4e276cdc71 Battleship (#2720)
* Made the Battleship 2P 2PP game - FSK is wip
* Using POCSAG
2025-06-28 20:02:29 +02:00
plomek 0eb03373b1 Added 3d printed cases for the H4M (#2715) 2025-06-25 19:16:43 +02:00
Netro d5ea0f0369 BLE Rx Improvements (#2710)
* Work to allow for unique beacon parsing functions.
* Fix Copyright
* Update firmware/application/apps/ble_rx_app.cpp
* Update firmware/baseband/proc_btlerx.cpp
* PR suggestions.
* Fix String.
* Refactor
2025-06-25 19:14:04 +02:00
RocketGod 22cc311447 Update app icons for Space Invaders and Dino Game (#2713) 2025-06-25 16:08:48 +02:00
RocketGod 4fbba205ad Made the Blackjack game (#2712)
* Made the Blackjack game
* Format Blackjack main.cpp
* Changed spade to diamond for dark mode visibility
* Format code
2025-06-25 16:07:56 +02:00
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
Totoo 2602c417be ui new coord system examples and macros (#2672) 2025-05-27 17:37:41 +08:00
Totoo aa1264cf91 getres cmd (#2671) 2025-05-27 15:18:33 +08:00
Brumi-2021 c79abccffd wfm_filters_GUI_name_std (#2668) 2025-05-25 20:04:08 +02:00
Brumi-2021 d3c9dc1f10 Storing_selected_NOAA_filter_in_settings_file (#2665)
* Storing_selected_NOAA_filter_in_settings_file

* format_issues
2025-05-24 18:18:24 +02:00
Totoo 3c8335c942 Audio to right (#2664)
* r.align
2025-05-23 12:27:57 +02:00
Totoo ac31f4781c screen max width fixes (#2663)
* max width fixes

* format
2025-05-23 08:42:19 +02:00
Totoo c80cae3638 fix png part 2025-05-23 00:23:35 +08:00
Totoo a1d7cf2b86 Prepare for display orientation part 1 (#2661) 2025-05-22 23:24:53 +08:00
zix 6f6d863a14 refactor the serial log logic of BLE Rx (#2660) 2025-05-21 07:07:26 -07:00
RocketGod 43a1bc0445 Jammer app add modes (#2659)
* 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.

* 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.
2025-05-19 19:16:28 +02:00
OpenSourceSDRLab 0d02f431c7 Upload the PCB file of PortaPack H4 and update the schematic file (#2657)
* Create README.txt

* Update README.txt

* Add files via upload

The gerber files of the portapack h4.

* delete

* Upload the gerber files for H4

* Create README.txt

This is the V1.0 version PCB file of PortaPack H4.

* Upload the pcb file for H4

* Update README.txt

* Update LCD_TF_Schematic.pdf
2025-05-19 23:40:02 +12:00
Brumi-2021 322b06d670 Improve_RF_sensitivity_NOAA_signal (#2654) 2025-05-18 12:49:24 +02:00
gullradriel db187301ab adding 10Hz and 50Hz to freqman_steps (#2652) 2025-05-11 21:36:47 +02:00
horrordash 84cb32ee14 slightly improved ads-b receiver module (#2649)
* slightly improved ads-b receiver module:
* fix Heading, Speed and Vrate decoders
* decode more ModeS messages
* log all ModeS messages (except DF11)
* fix formatting (clang-style); advice on data alignment taken into account
* ADS-B module: convert Indicated AirSpeed to True AirSpeed if altitute is known
* ADS-B rx module: replacing floating point with integer arithmetic
2025-05-11 21:11:15 +02:00
Brumi-2021 61bd696b69 Explicit_naming_wefax_NOAA_and_small_addition (#2651) 2025-05-11 21:10:48 +02:00
Totoo db65ae192a Noaa apt decoder (#2648) 2025-05-11 20:24:52 +02:00
RocketGod 7553dafffc Update ui_tetris.cpp (#2650)
Start "I" tetromino a bit higher and block rotate if it will cause out of bounds collision.
2025-05-10 13:08:41 +02:00
sommermorgentraum cfdd1a4c22 remove dead code (#2647) 2025-05-07 06:17:12 +02:00
gullradriel d4342e01a4 going back to WFM as main baseband in main.cpp as NFM is now making the apps crashing. Looks like last additions to WFM made it bigger. (#2646) 2025-05-06 16:50:59 +08:00
Brumi-2021 4342f5c0ee Adding_WFM_AM_mode_to_Audio_App (#2644)
* Adding_WFM_AM_mode_to_Audio_App
* more precise values for cos and sin theta, fix sen_theta to sin_theta
* fix sen_theta to sin_theta
2025-05-05 17:48:01 +02:00
Richard bd781ce37b Fix for #2538 (#2635)
* Fix for #2538

Fix for #2538
Added on_bandwidth_changed Callback to ui_transmitter.hpp
Modified the field_bw.on_change lambda in the TransmitterView constructor to trigger the on_bandwidth_changed callback
Connected the Callback in ui_siggen.cpp

I am not a C++ programmer so this change was proposed by Gemini AI.

I have built and tested the App and it works as expected and I don't think the change will have any unexpected side effects.

* Fix clang issues

Fix clang issues

* Update ui_transmitter.cpp

typo

* Revised change

The proposed change mirrors the way a change to the frequency (on_edit_frequency) is triggered in ui_siggen by the tx_view.
The bw parameter is not passed because it is stored in _setting in the tx_view and will be read by update_config.
A change to the bw is not checked against auto_update to keep its behaviour consistent with a change to the gain, amplitude or frequency.

* Make changes to the channel_bandwidth dynamic whist playing

Behaviour of channel bandwidth is now consistent with frequency, amp and gain.

* comment edit

* revert hackrf submodule checkpoint to the repo

* comment

---------

Co-authored-by: zxkmm <zxkmm@hotmail.com>
2025-05-02 04:19:41 +00:00
Erwin Ried c2e05dea48 Update README.md
Links were dead for opensourcesdrlab
2025-04-25 13:01:54 +02:00
sommermorgentraum de3212cba5 spectrum cursor opt again (#2634)
* spectrum cursor opt

* fmt

* remove blink

* remove End event

* cleanup
2025-04-22 21:14:31 +08:00
sommermorgentraum 9aff3f4121 Fix bug that created by PR "Added menu group for transceivers" (#2630) 2025-04-17 07:42:05 +02:00
sommermorgentraum 6b6a00d511 Support IPS screen & brightness set for IPS screen (#2629)
* _

* format

* format

* format
2025-04-17 07:40:28 +02:00
Oleg Belousov 806219f46e Added menu group for transceivers (#2623)
* Added menu group for transceivers

* Reorder apps icons
2025-04-15 16:46:49 +08:00
Luca a153cbf30b Adding 8.33KHz spacing (#2628)
* Adding 8.33KHz spacing
* remove extern options_db_t freqman_steps which is now brought by ui_receiver.hpp
* use freqman db steps instead of static array
Co-authored-by: gullradriel <gullradriel@no-mail.com>
2025-04-14 18:59:50 +00:00
sommermorgentraum 584af02dba Touch on waterfall to set cursor pos (#2624)
* init

* fix typo that found by Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-04-14 20:47:41 +02:00
gullradriel f344a1ca40 fix comments (#2622) 2025-04-11 11:48:07 +00:00
gullradriel 511675c9a1 Fix default waterfall file (#2621)
* correct default settings from file
* correct colors names
2025-04-11 19:30:18 +08:00
haruk 7bbd608c90 Delete dead code in ble_rx_app.cpp (#2620)
Duplicate include on line 24 removed.
2025-04-11 10:49:09 +00:00
Oleg Belousov f941ae871a Custom waterfall colors (#2617)
* Custom waterfall gradient
* Installing a custom waterfall gradient via fileman
* default file for user friendly swap
Co-authored-by: gullradriel <gullradriel@no-mail.com>
2025-04-11 10:46:04 +00:00
RocketGod eff214cc79 Enhance Graphic Equalizer Visualization with Improved Frequency Bands and Response (#2614) 2025-04-10 22:16:18 +02:00
Oleg Belousov 319ed3e7b4 Improved make_bitmap tool (#2615) 2025-04-10 12:21:19 +08:00
258 changed files with 16730 additions and 7502 deletions
+1 -1
View File
@@ -1 +1 @@
v2.0.2
v2.1.0
+1 -1
View File
@@ -1 +1 @@
v2.1.0
v2.2.0
+1 -1
View File
@@ -28,7 +28,7 @@ This repository expands upon the previous work by many people and aims to consta
## What to buy?
<!---not direct to h4m but to opensourcesdrlab https://share.hackrf.app/TUOLYI--->
:heavy_check_mark: ![Static Badge](https://img.shields.io/badge/NEW-yellow) The fabulous H4M [complete](https://share.hackrf.app/ABDO6H) or [upgrade](https://share.hackrf.app/FPLM1H), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://share.hackrf.app/99SAMT). Join their giveaways on discord (check the badge on top). _EU customers_ can purchase via [Lab401](https://share.hackrf.app/0CI2CR).
:heavy_check_mark: ![Static Badge](https://img.shields.io/badge/NEW-yellow) The fabulous H4M [complete](https://share.hackrf.app/9UMBN5) or [upgrade](https://share.hackrf.app/30CQ55), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://share.hackrf.app/99SAMT). Join their giveaways on discord (check the badge on top). _EU customers_ can purchase via [Lab401](https://share.hackrf.app/0CI2CR).
:heavy_check_mark: A recommended one is this [PortaPack H2](https://www.ebay.com/itm/116382397447), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/3d-printed-enclosure).
+1
View File
@@ -191,6 +191,7 @@ set(CPPSRC
clock_manager.cpp
core_control.cpp
database.cpp
gradient.cpp
rfm69.cpp
event_m0.cpp
file_reader.cpp
+3
View File
@@ -188,6 +188,7 @@ void copy_to_radio_model(const AppSettings& settings) {
settings.am_config_index,
settings.nbfm_config_index,
settings.wfm_config_index,
settings.wfmam_config_index,
settings.squelch);
}
@@ -218,6 +219,7 @@ void copy_from_radio_model(AppSettings& settings) {
settings.am_config_index = receiver_model.am_configuration();
settings.nbfm_config_index = receiver_model.nbfm_configuration();
settings.wfm_config_index = receiver_model.wfm_configuration();
settings.wfmam_config_index = receiver_model.wfmam_configuration();
}
settings.step = receiver_model.frequency_step();
@@ -271,6 +273,7 @@ SettingsManager::SettingsManager(
bindings_.emplace_back("am_config_index"sv, &settings_.am_config_index);
bindings_.emplace_back("nbfm_config_index"sv, &settings_.nbfm_config_index);
bindings_.emplace_back("wfm_config_index"sv, &settings_.wfm_config_index);
bindings_.emplace_back("wfmam_config_index"sv, &settings_.wfmam_config_index);
bindings_.emplace_back("squelch"sv, &settings_.squelch);
}
+1
View File
@@ -139,6 +139,7 @@ struct AppSettings {
uint8_t am_config_index = 0;
uint8_t nbfm_config_index = 0;
uint8_t wfm_config_index = 0;
uint8_t wfmam_config_index = 0;
uint8_t squelch = 80;
uint8_t volume;
// NOTE: update COMMON_APP_SETTINGS_COUNT when adding to this
+40 -2
View File
@@ -301,7 +301,7 @@ AISRecentEntryDetailView::AISRecentEntryDetailView(NavigationView& nav) {
ais::format::text(entry_.name),
0,
GeoPos::alt_unit::METERS,
GeoPos::spd_unit::NONE,
GeoPos::spd_unit::KNOTS,
ais::format::latlon_float(entry_.last_position.latitude.normalized()),
ais::format::latlon_float(entry_.last_position.longitude.normalized()),
entry_.last_position.true_heading,
@@ -324,7 +324,31 @@ AISRecentEntryDetailView& AISRecentEntryDetailView::operator=(const AISRecentEnt
void AISRecentEntryDetailView::update_position() {
if (send_updates)
geomap_view->update_position(ais::format::latlon_float(entry_.last_position.latitude.normalized()), ais::format::latlon_float(entry_.last_position.longitude.normalized()), (float)entry_.last_position.true_heading, 0, entry_.last_position.speed_over_ground);
geomap_view->update_position(ais::format::latlon_float(entry_.last_position.latitude.normalized()), ais::format::latlon_float(entry_.last_position.longitude.normalized()), (float)entry_.last_position.true_heading, 0, entry_.last_position.speed_over_ground > 1022 ? 0 : entry_.last_position.speed_over_ground);
}
bool AISRecentEntryDetailView::add_map_marker(const AISRecentEntry& entry) {
if (geomap_view && send_updates) {
GeoMarker marker{};
marker.lon = ais::format::latlon_float(entry.last_position.longitude.normalized());
marker.lat = ais::format::latlon_float(entry.last_position.latitude.normalized());
marker.angle = entry.last_position.true_heading;
marker.tag = entry.call_sign.empty() ? to_string_dec_uint(entry.mmsi) : entry.call_sign;
auto markerStored = geomap_view->store_marker(marker);
return markerStored == MARKER_STORED;
}
return false;
}
void AISRecentEntryDetailView::update_map_markers(AISRecentEntries& entries) {
if (geomap_view && send_updates) {
geomap_view->clear_markers();
for (const auto& entry : entries) {
// if (entry.last_position.latitude.is_valid() && entry.last_position.longitude.is_valid()) {
add_map_marker(entry);
// }
}
update_position(); // to update view
}
}
void AISRecentEntryDetailView::focus() {
@@ -415,14 +439,27 @@ AISAppView::AISAppView(NavigationView& nav)
audio::set_rate(audio::Rate::Hz_24000);
audio::output::start();
}
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
on_tick_second();
};
}
AISAppView::~AISAppView() {
rtc_time::signal_tick_second -= signal_token_tick_second;
audio::output::stop();
receiver_model.disable();
baseband::shutdown();
}
void AISAppView::on_tick_second() {
++timer_seconds;
if (timer_seconds % 10 == 0) {
if (recent_entry_detail_view.hidden()) return;
recent_entry_detail_view.update_map_markers(recent);
}
}
void AISAppView::focus() {
options_channel.focus();
}
@@ -461,6 +498,7 @@ void AISAppView::on_show_detail(const AISRecentEntry& entry) {
recent_entry_detail_view.hidden(false);
recent_entry_detail_view.set_entry(entry);
recent_entry_detail_view.focus();
recent_entry_detail_view.update_map_markers(recent);
}
} /* namespace ui */
+8 -1
View File
@@ -125,10 +125,14 @@ class AISRecentEntryDetailView : public View {
void update_position();
void focus() override;
void paint(Painter&) override;
bool add_map_marker(const AISRecentEntry& entry);
void update_map_markers(AISRecentEntries& entries);
AISRecentEntryDetailView(const AISRecentEntryDetailView& Entry);
AISRecentEntryDetailView& operator=(const AISRecentEntryDetailView& Entry);
GeoMapView* get_geomap_view() { return geomap_view; }
private:
AISRecentEntry entry_{};
@@ -210,11 +214,13 @@ class AISAppView : public View {
};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
{screen_width - 2 * 8, 0 * 16}};
Channel channel{
{21 * 8, 5, 6 * 8, 4},
};
SignalToken signal_token_tick_second{};
uint8_t timer_seconds = 0;
MessageHandlerRegistration message_handler_packet{
Message::ID::AISPacket,
@@ -229,6 +235,7 @@ class AISAppView : public View {
void on_packet(const ais::Packet& packet);
void on_show_list();
void on_show_detail(const AISRecentEntry& entry);
void on_tick_second();
};
} /* namespace ui */
+34 -2
View File
@@ -114,6 +114,26 @@ WFMOptionsView::WFMOptionsView(
};
}
/* WFMAMAptOptionsView *******************************************************/
WFMAMAptOptionsView::WFMAMAptOptionsView(
Rect parent_rect,
const Style* style)
: View{parent_rect} {
set_style(style);
add_children({
&label_config,
&options_config,
});
freqman_set_bandwidth_option(WFMAM_MODULATION, options_config); // adding the common message from freqman.cpp to the options_config
options_config.set_by_value(receiver_model.wfmam_configuration());
options_config.on_change = [this](size_t, OptionsField::value_t n) {
receiver_model.set_wfmam_configuration(n);
};
}
/* AMFMAptOptionsView *********************************************************/
AMFMAptOptionsView::AMFMAptOptionsView(
@@ -415,6 +435,12 @@ void AnalogAudioView::on_show_options_modulation() {
text_ctcss.hidden(true);
break;
case ReceiverModel::Mode::WFMAudioAMApt:
widget = std::make_unique<WFMAMAptOptionsView>(options_view_rect, Theme::getInstance()->option_active);
waterfall.show_audio_spectrum_view(true);
text_ctcss.hidden(true);
break;
case ReceiverModel::Mode::AMAudioFMApt:
widget = std::make_unique<AMFMAptOptionsView>(this, options_view_rect, Theme::getInstance()->option_active);
waterfall.show_audio_spectrum_view(false);
@@ -462,7 +488,10 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
case ReceiverModel::Mode::WidebandFMAudio:
image_tag = portapack::spi_flash::image_tag_wfm_audio;
break;
case ReceiverModel::Mode::AMAudioFMApt: // TODO pending to update it.
case ReceiverModel::Mode::WFMAudioAMApt:
image_tag = portapack::spi_flash::image_tag_wfm_audio;
break;
case ReceiverModel::Mode::AMAudioFMApt:
image_tag = portapack::spi_flash::image_tag_am_audio;
break;
case ReceiverModel::Mode::SpectrumAnalysis:
@@ -501,7 +530,10 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
case ReceiverModel::Mode::WidebandFMAudio:
sampling_rate = 48000;
break;
case ReceiverModel::Mode::AMAudioFMApt: // TODO Wefax mode.
case ReceiverModel::Mode::WFMAudioAMApt:
sampling_rate = 12000;
break;
case ReceiverModel::Mode::AMAudioFMApt:
sampling_rate = 12000;
break;
default:
+25 -7
View File
@@ -74,7 +74,7 @@ class AMFMAptOptionsView : public View {
OptionsField options_config{
{3 * 8, 0 * 16},
6, // Max option length chars
17, // Max option length chars "USB+FM(Wefax Apt)"
{
// Using common messages from freqman_ui.cpp In HF USB , Here we only need USB Audio demod, + post-FM demod fsubcarrier FM tone to get APT signal.
}};
@@ -132,6 +132,23 @@ class WFMOptionsView : public View {
}};
};
class WFMAMAptOptionsView : public View {
public:
WFMAMAptOptionsView(Rect parent_rect, const Style* style);
private:
Text label_config{
{0 * 8, 0 * 16, 2 * 8, 1 * 16},
"BW",
};
OptionsField options_config{
{3 * 8, 0 * 16},
16, // Max option char length "80k-NOAA Apt LPF" , example.
{
// Using common messages from freqman_ui.cpp
}};
};
class SPECOptionsView : public View {
public:
SPECOptionsView(AnalogAudioView* view, Rect parent_rect, const Style* style);
@@ -168,7 +185,7 @@ class SPECOptionsView : public View {
{19 * 8, 0 * 16, 11 * 8, 1 * 16}, // 18 (x col.) x char_size, 12 (length) x 8 blanking space to delete previous chars.
"Rx_IQ_CAL "};
NumberField field_rx_iq_phase_cal{
{28 * 8, 0 * 16},
{screen_width - 2 * 8, 0 * 16},
2,
{0, 63}, // 5 or 6 bits IQ CAL phase adjustment (range updated later)
1,
@@ -215,7 +232,7 @@ class AnalogAudioView : public View {
uint8_t zoom_factor_amfm{0}; // initial zoom factor in AMFM mode
uint8_t previous_AM_mode_option{0}; // GUI 5 AM modes : (0..4 ) (DSB9K, DSB6K, USB,LSB, CW). Used to select proper FIR filter (0..11) AM mode + offset 0 (zoom+1) or +6 (if zoom+2)
uint8_t previous_zoom{0}; // GUI ZOOM+1, ZOOM+2 , equivalent to two values offset 0 (zoom+1) or +6 (if zoom+2)
//
app_settings::SettingsManager settings_{
"rx_audio",
app_settings::Mode::RX,
@@ -227,7 +244,7 @@ class AnalogAudioView : public View {
{"previous_zoom"sv, &previous_zoom}, // we are saving and restoring AMFM ZOOM factor from Settings.
}};
const Rect options_view_rect{0 * 8, 1 * 16, 30 * 8, 1 * 16};
const Rect options_view_rect{0 * 8, 1 * 16, screen_width, 1 * 16};
const Rect nbfm_view_rect{0 * 8, 1 * 16, 18 * 8, 1 * 16};
size_t spec_bw_index = 0;
@@ -261,11 +278,12 @@ class AnalogAudioView : public View {
{"NFM ", toUType(ReceiverModel::Mode::NarrowbandFMAudio)},
{"WFM ", toUType(ReceiverModel::Mode::WidebandFMAudio)},
{"SPEC", toUType(ReceiverModel::Mode::SpectrumAnalysis)},
{"AMFM", toUType(ReceiverModel::Mode::AMAudioFMApt)} // Added to handle HF WeatherFax , SSB (USB demod) + Tone_Subcarrier FM demod
{"AMFM", toUType(ReceiverModel::Mode::AMAudioFMApt)}, // Added to handle HF WeatherFax , SSB (USB demod) + Tone_Subcarrier FM demod
{"FMAM", toUType(ReceiverModel::Mode::WFMAudioAMApt)} // Added to handle SAT NOAA APT
}};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
{screen_width - 2 * 8, 0 * 16}};
Text text_ctcss{
{16 * 8, 1 * 16, 14 * 8, 1 * 16},
@@ -274,7 +292,7 @@ class AnalogAudioView : public View {
std::unique_ptr<Widget> options_widget{};
RecordView record_view{
{0 * 8, 2 * 16, 30 * 8, 1 * 16},
{0 * 8, 2 * 16, screen_width, 1 * 16},
u"AUD",
u"AUDIO",
RecordView::FileType::WAV,
+1 -1
View File
@@ -170,7 +170,7 @@ class BLECommView : public View {
"-"};
Console console{
{0, 4 * 16, 240, 240}};
{0, 4 * 16, ui::screen_width, ui::screen_height - 80}};
std::string str_log{""};
bool logging{false};
+187 -79
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.
*
@@ -21,7 +22,6 @@
* Boston, MA 02110-1301, USA.
*/
#include "ble_rx_app.hpp"
#include "ble_rx_app.hpp"
#include "ui_modemsetup.hpp"
@@ -34,6 +34,7 @@
#include "portapack_persistent_memory.hpp"
#include "ui_fileman.hpp"
#include "ui_textentry.hpp"
#include "usb_serial_asyncmsg.hpp"
using namespace portapack;
using namespace modems;
@@ -67,6 +68,50 @@ uint64_t copy_mac_address_to_uint64(const uint8_t* macAddress) {
return result;
}
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;
}
void reverse_byte_array(uint8_t* arr, int length) {
int start = 0;
int end = length - 1;
@@ -150,8 +195,15 @@ void RecentEntriesTable<BleRecentEntries>::draw(
line = to_string_mac_address(entry.packetData.macAddress, 6, false);
}
std::string hitsStr;
if (!entry.informationString.empty()) {
hitsStr = entry.informationString;
} else {
hitsStr = to_string_dec_int(entry.numHits);
}
// Pushing single digit values down right justified.
std::string hitsStr = to_string_dec_int(entry.numHits);
int hitsDigits = hitsStr.length();
uint8_t hits_spacing = 8 - hitsDigits;
@@ -165,7 +217,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 +233,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 +429,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();
}
@@ -447,6 +510,8 @@ BLERxView::BLERxView(NavigationView& nav)
&button_switch,
&recent_entries_view});
async_tx_states_when_entered = portapack::async_tx_enabled;
recent_entries_view.on_select = [this](const BleRecentEntry& entry) {
nav_.push<BleRecentEntryDetailView>(entry);
};
@@ -454,9 +519,9 @@ BLERxView::BLERxView(NavigationView& nav)
check_serial_log.on_select = [this](Checkbox&, bool v) {
serial_logging = v;
if (v) {
usb_serial_thread = std::make_unique<UsbSerialThread>();
portapack::async_tx_enabled = true;
} else {
usb_serial_thread.reset();
portapack::async_tx_enabled = false;
}
};
check_serial_log.set_value(serial_logging);
@@ -481,7 +546,6 @@ BLERxView::BLERxView(NavigationView& nav)
logger = std::make_unique<BLELogger>();
check_log.on_select = [this](Checkbox&, bool v) {
str_log = "";
logging = v;
if (logger && logging)
@@ -503,6 +567,7 @@ BLERxView::BLERxView(NavigationView& nav)
button_clear_list.on_select = [this](Button&) {
recent.clear();
recent_entries_view.set_dirty();
};
button_switch.on_select = [&nav](Button&) {
@@ -544,7 +609,10 @@ BLERxView::BLERxView(NavigationView& nav)
options_filter.on_change = [this](size_t index, int32_t v) {
filter_index = (uint8_t)index;
recent.clear();
handle_filter_options(v);
uniqueParsing = filter_index == 2 ? true : false;
recent_entries_view.set_dirty();
};
options_channel.set_selected_index(channel_index, true);
@@ -717,10 +785,61 @@ bool BLERxView::saveFile(const std::filesystem::path& path) {
}
void BLERxView::on_data(BlePacketData* packet) {
if (!logging) {
str_log = "";
uint64_t macAddressEncoded = copy_mac_address_to_uint64(packet->macAddress);
// Start of Packet stuffing.
// Masking off the top 2 bytes to avoid invalid keys.
uint64_t key = macAddressEncoded & 0xFFFFFFFFFFFF;
bool packetExists = false;
// If found store into tempEntry to modify.
auto it = find(recent, key);
if (it != recent.end()) {
recent.push_front(*it);
recent.erase(it);
updateEntry(packet, recent.front(), (ADV_PDU_TYPE)packet->type);
packetExists = true;
} else {
recent.emplace_front(key);
truncate_entries(recent);
packetExists = updateEntry(packet, recent.front(), (ADV_PDU_TYPE)packet->type);
// If parsing failed, remove entry.
if (!packetExists) {
recent.erase(recent.begin());
}
}
if (packetExists) {
handle_filter_options(options_filter.selected_index());
handle_entries_sort(options_sort.selected_index());
if (!searchList.empty()) {
auto it = searchList.begin();
while (it != searchList.end()) {
std::string searchStr = (std::string)*it;
if (recent.front().dataString.find(searchStr) != std::string::npos) {
searchList.erase(it);
found_count++;
break;
}
it++;
}
text_found_count.set(to_string_dec_uint(found_count) + "/" + to_string_dec_uint(total_count));
}
}
log_ble_packet(packet);
}
void BLERxView::log_ble_packet(BlePacketData* packet) {
str_console = "";
str_console += pdu_type_to_string((ADV_PDU_TYPE)packet->type);
str_console += " Len:";
str_console += to_string_dec_uint(packet->size);
@@ -734,49 +853,13 @@ void BLERxView::on_data(BlePacketData* packet) {
str_console += to_string_hex(packet->data[i], 2);
}
uint64_t macAddressEncoded = copy_mac_address_to_uint64(packet->macAddress);
// Start of Packet stuffing.
// Masking off the top 2 bytes to avoid invalid keys.
auto& entry = ::on_packet(recent, macAddressEncoded & 0xFFFFFFFFFFFF);
updateEntry(packet, entry, (ADV_PDU_TYPE)packet->type);
// Add entries if they meet the criteria.
// auto value = filter;
// resetFilteredEntries(recent, [&value](const BleRecentEntry& entry) {
// return (entry.dataString.find(value) == std::string::npos) && (entry.nameString.find(value) == std::string::npos);
// });
handle_filter_options(options_filter.selected_index());
handle_entries_sort(options_sort.selected_index());
// Log at End of Packet.
if (logger && logging) {
logger->log_raw_data(str_console + "\r\n");
logger->log_raw_data(str_console);
}
if (serial_logging) {
usb_serial_thread->serial_str = str_console + "\r\n";
usb_serial_thread->str_ready = true;
}
str_console = "";
if (!searchList.empty()) {
auto it = searchList.begin();
while (it != searchList.end()) {
std::string searchStr = (std::string)*it;
if (entry.dataString.find(searchStr) != std::string::npos) {
searchList.erase(it);
found_count++;
break;
}
it++;
}
text_found_count.set(to_string_dec_uint(found_count) + "/" + to_string_dec_uint(total_count));
UsbSerialAsyncmsg::asyncmsg(str_console); // new line handled there, no need here.
}
}
@@ -840,13 +923,10 @@ void BLERxView::on_timer() {
timer_count = 0;
if (auto_channel) {
int min = 37;
int max = 39;
field_frequency.set_value(get_freq_by_channel_number(channel_number));
baseband::set_btlerx(channel_number);
int randomChannel = min + std::rand() % (max - min + 1);
field_frequency.set_value(get_freq_by_channel_number(randomChannel));
baseband::set_btlerx(randomChannel);
channel_number = (channel_number < 39) ? channel_number + 1 : 37;
}
}
if (ble_rx_error != BLE_RX_NO_ERROR) {
@@ -917,20 +997,23 @@ void BLERxView::set_parent_rect(const Rect new_parent_rect) {
}
BLERxView::~BLERxView() {
portapack::async_tx_enabled = async_tx_states_when_entered;
receiver_model.disable();
baseband::shutdown();
}
void BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type) {
bool BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type) {
std::string data_string;
bool success = false;
int i;
for (i = 0; i < packet->dataLen; i++) {
data_string += to_string_hex(packet->data[i], 2);
}
entry.dbValue = packet->max_dB;
entry.dbValue = packet->max_dB - (receiver_model.lna() + receiver_model.vga() + (receiver_model.rf_amp() ? 14 : 0));
entry.timestamp = to_string_timestamp(rtc_time::now());
entry.dataString = data_string;
@@ -946,9 +1029,14 @@ void BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry,
entry.packetData.macAddress[4] = packet->macAddress[4];
entry.packetData.macAddress[5] = packet->macAddress[5];
entry.numHits++;
entry.pduType = pdu_type;
entry.channelNumber = channel_number;
entry.numHits++;
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++) {
@@ -958,35 +1046,55 @@ void BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry,
entry.include_name = check_name.value();
// Only parse name for advertisment packets and empty name entries
if ((pdu_type == ADV_IND || pdu_type == ADV_NONCONN_IND || pdu_type == SCAN_RSP || pdu_type == ADV_SCAN_IND) && entry.nameString.empty()) {
ADV_PDU_PAYLOAD_TYPE_0_2_4_6* advertiseData = (ADV_PDU_PAYLOAD_TYPE_0_2_4_6*)entry.packetData.data;
uint8_t currentByte = 0;
uint8_t length = 0;
uint8_t type = 0;
std::string decoded_data;
for (currentByte = 0; (currentByte < entry.packetData.dataLen);) {
length = advertiseData->Data[currentByte++];
type = advertiseData->Data[currentByte++];
// Subtract 1 because type is part of the length.
for (int i = 0; i < length - 1; i++) {
// parse the name of bluetooth device: 0x08->Shortened Local Name; 0x09->Complete Local Name
if (type == 0x08 || type == 0x09) {
decoded_data += (char)advertiseData->Data[currentByte];
}
currentByte++;
}
if (!decoded_data.empty()) {
entry.nameString = std::move(decoded_data);
break;
}
if (pdu_type == ADV_IND || pdu_type == ADV_NONCONN_IND || pdu_type == SCAN_RSP || pdu_type == ADV_SCAN_IND) {
if (uniqueParsing) {
// Add your unique beacon parsing function here.
}
if (!success && !uniqueParsing) {
success = parse_beacon_data(packet->data, packet->dataLen, entry.nameString, entry.informationString);
}
} else if (pdu_type == ADV_DIRECT_IND || pdu_type == SCAN_REQ) {
ADV_PDU_PAYLOAD_TYPE_1_3* directed_mac_data = (ADV_PDU_PAYLOAD_TYPE_1_3*)entry.packetData.data;
reverse_byte_array(directed_mac_data->A1, 6);
}
return success;
}
bool BLERxView::parse_beacon_data(const uint8_t* data, uint8_t length, std::string& nameString, std::string& informationString) {
uint8_t currentByte, currentLength, currentType = 0;
std::string tempName = "";
for (currentByte = 0; currentByte < length;) {
currentLength = data[currentByte++];
currentType = data[currentByte++];
// Subtract 1 because type is part of the length.
for (int i = 0; ((i < currentLength - 1) && (currentByte < length)); i++) {
// parse the name of bluetooth device: 0x08->Shortened Local Name; 0x09->Complete Local Name
if (currentType == 0x08 || currentType == 0x09) {
tempName += (char)data[currentByte];
}
currentByte++;
}
if (!tempName.empty()) {
nameString = tempName;
break;
}
}
informationString = "";
if (!informationString.empty()) {
// Option to change title of Hits Column.
// Setting to default for now.
columns.set(1, "Hits", 7);
}
return true;
}
} /* namespace ui */
+38 -12
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,10 +74,17 @@ 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;
static constexpr Key invalid_key = 0xffffffff;
static constexpr Key invalid_key = 0xFFFFFFFFFFFF;
uint64_t macAddress;
int dbValue;
@@ -83,10 +92,12 @@ struct BleRecentEntry {
std::string timestamp;
std::string dataString;
std::string nameString;
std::string informationString;
bool include_name;
uint16_t numHits;
ADV_PDU_TYPE pduType;
uint8_t channelNumber;
MAC_VENDOR_STATUS vendor_status;
bool entryFound;
BleRecentEntry()
@@ -101,10 +112,12 @@ struct BleRecentEntry {
timestamp{},
dataString{},
nameString{},
informationString{},
include_name{},
numHits{},
pduType{},
channelNumber{},
vendor_status{MAC_VENDOR_UNKNOWN},
entryFound{} {
}
@@ -152,6 +165,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},
@@ -198,15 +218,18 @@ class BLERxView : public View {
bool saveFile(const std::filesystem::path& path);
std::unique_ptr<UsbSerialThread> usb_serial_thread{};
void on_data(BlePacketData* packetData);
void log_ble_packet(BlePacketData* packet);
void on_filter_change(std::string value);
void on_file_changed(const std::filesystem::path& new_file_path);
void file_error();
void on_timer();
void handle_entries_sort(uint8_t index);
void handle_filter_options(uint8_t index);
void updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type);
bool updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type);
bool parse_beacon_data(const uint8_t* data, uint8_t length, std::string& nameString, std::string& informationString);
NavigationView& nav_;
RxRadioState radio_state_{
2402000000 /* frequency */,
4000000 /* bandwidth */,
@@ -216,9 +239,11 @@ class BLERxView : public View {
uint8_t channel_index{0};
uint8_t sort_index{0};
uint8_t filter_index{0};
bool uniqueParsing = false;
std::string filter{};
bool logging{false};
bool serial_logging{false};
bool async_tx_states_when_entered{false};
bool name_enable{true};
app_settings::SettingsManager settings_{
@@ -242,7 +267,7 @@ class BLERxView : public View {
bool auto_channel = false;
int16_t timer_count{0};
int16_t timer_period{6}; // 100ms
int16_t timer_period{2}; // 25ms
std::string filterBuffer{};
std::string listFileBuffer{};
@@ -306,9 +331,10 @@ class BLERxView : public View {
OptionsField options_filter{
{18 * 8 + 2, 2 * 8},
4,
7,
{{"Data", 0},
{"MAC", 1}}};
{"MAC", 1},
{"Unique", 2}}};
Checkbox check_log{
{10 * 8, 4 * 8 + 2},
@@ -340,18 +366,18 @@ class BLERxView : public View {
true};
// Console console{
// {0, 10 * 8, 240, 240}};
// {0, 10 * 8, screen_height, screen_height-80}};
Button button_clear_list{
{2 * 8, 320 - (16 + 32), 7 * 8, 32},
{2 * 8, screen_height - (16 + 32), 7 * 8, 32},
"Clear"};
Button button_save_list{
{11 * 8, 320 - (16 + 32), 11 * 8, 32},
{11 * 8, screen_height - (16 + 32), 11 * 8, 32},
"Export CSV"};
Button button_switch{
{240 - 6 * 8, 320 - (16 + 32), 4 * 8, 32},
{screen_width - 6 * 8, screen_height - (16 + 32), 4 * 8, 32},
"Tx"};
std::string str_log{""};
@@ -360,10 +386,10 @@ class BLERxView : public View {
BleRecentEntries recent{};
BleRecentEntries tempList{};
const RecentEntriesColumns columns{{
{"Mac Address", 17},
RecentEntriesColumns columns{{
{"Name", 17},
{"Hits", 7},
{"dB", 4},
{"dBm", 4},
}};
BleRecentEntriesView recent_entries_view{columns, recent};
+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) {
+2 -5
View File
@@ -216,7 +216,7 @@ class BLETxView : public View {
true};
ImageButton button_play{
{28 * 8, 2 * 16, 2 * 8, 1 * 16},
{screen_width - 2 * 8, 2 * 16, 2 * 8, 1 * 16},
&bitmap_play,
Theme::getInstance()->fg_green->foreground,
Theme::getInstance()->fg_green->background};
@@ -287,11 +287,8 @@ class BLETxView : public View {
Labels label_data_packet{
{{0 * 8, 9 * 16}, "Packet Data:", Theme::getInstance()->fg_light->foreground}};
Console console{
{0, 9 * 18, 240, 240}};
TextViewer dataEditView{
{0, 9 * 18, 240, 240}};
{0, 9 * 18, screen_width, screen_height - 80}};
Button button_clear_marked{
{1 * 8, 14 * 16, 13 * 8, 3 * 8},
+1 -1
View File
@@ -109,7 +109,7 @@ class CaptureAppView : public View {
/*small*/ true};
RecordView record_view{
{0 * 8, 2 * 16, 30 * 8, 1 * 16},
{0 * 8, 2 * 16, screen_width, 1 * 16},
u"BBD_????.*",
captures_dir,
RecordView::FileType::RawS16,
+1 -1
View File
@@ -166,7 +166,7 @@ class ERTAppView : public View {
};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
{screen_width - 2 * 8, 0 * 16}};
MessageHandlerRegistration message_handler_packet{
Message::ID::ERTPacket,
+1 -1
View File
@@ -259,7 +259,7 @@ class POCSAGAppView : public View {
' ',
true /*wrap*/};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
{screen_width - 2 * 8, 0 * 16}};
Image image_status{
{0 * 8 + 4, 1 * 16 + 2, 16, 16},
+24 -5
View File
@@ -91,10 +91,6 @@ void SoundBoardView::start_tx(const uint32_t id) {
auto reader = std::make_unique<WAVFileReader>();
uint32_t tone_key_index = options_tone_key.selected_index();
uint32_t sample_rate;
uint8_t bits_per_sample;
stop();
if (!reader->open(u"/WAV/" + file_list[id].native())) {
@@ -108,7 +104,9 @@ void SoundBoardView::start_tx(const uint32_t id) {
// button_play.set_bitmap(&bitmap_stop);
sample_rate = reader->sample_rate();
uint32_t sample_rate = reader->sample_rate();
tone_key_index = options_tone_key.selected_index();
bits_per_sample = reader->bits_per_sample();
replay_thread = std::make_unique<ReplayThread>(
@@ -155,6 +153,23 @@ void SoundBoardView::on_tx_progress(const uint32_t progress) {
progressbar.set_value(progress);
}
void SoundBoardView::update_config() {
// NB: this were called by the on_bandwidth_changed() callback,
// so other val would be updated too when bw changed. currently it's safe but be careful.
baseband::set_audiotx_config(
1536000 / 20, // Update vu-meter at 20Hz
transmitter_model.channel_bandwidth(),
0, // Gain is unused
8, // shift_bits_s16, default 8 bits, but also unused
bits_per_sample,
TONES_F2D(tone_key_frequency(tone_key_index), TONES_SAMPLERATE),
false, // AM
false, // DSB
false, // USB
false // LSB
);
}
void SoundBoardView::on_select_entry() {
tx_view.focus();
}
@@ -289,6 +304,10 @@ SoundBoardView::SoundBoardView(
};
};
tx_view.on_bandwidth_changed = [this]() {
update_config();
};
tx_view.on_start = [this]() {
start_tx(menu_view.highlighted_index());
};
+8 -5
View File
@@ -70,6 +70,8 @@ class SoundBoardView : public View {
uint32_t playing_id{};
uint32_t page = 1;
uint32_t c_page = 1;
uint32_t tone_key_index = 1;
uint8_t bits_per_sample = 1;
std::vector<std::filesystem::path> file_list{};
@@ -90,6 +92,7 @@ class SoundBoardView : public View {
void on_tx_progress(const uint32_t progress);
void refresh_list();
void on_select_entry();
void update_config();
Labels labels{
{{24 * 8, 180}, "Vol:", Theme::getInstance()->fg_light->foreground},
@@ -104,10 +107,10 @@ class SoundBoardView : public View {
"<="};
Text page_info{
{0, 29 * 8, 30 * 8, 16}};
{0, 29 * 8, screen_width, 16}};
MenuView menu_view{
{0, 0, 240, 175},
{0, 0, screen_width, 175},
true};
Text text_empty{
{7 * 8, 12 * 8, 16 * 8, 16},
@@ -128,9 +131,9 @@ class SoundBoardView : public View {
{}};
AudioVolumeField field_volume{
{28 * 8, 180}};
{screen_width - 2 * 8, 180}};
Text text_volume_disabled{
{28 * 8, 180, 3 * 8, 16},
{screen_width - 2 * 8, 180, 3 * 8, 16},
"--"};
Checkbox check_loop{
@@ -144,7 +147,7 @@ class SoundBoardView : public View {
"Random"};
ProgressBar progressbar{
{0 * 8, 31 * 8 + 2, 30 * 8, 4}};
{0 * 8, 31 * 8 + 2, screen_width, 4}};
TransmitterView tx_view{
16 * 16,
@@ -34,6 +34,7 @@ constexpr std::string_view mayhem_information_list[] = {
"RedFox-Fr,nemanjan00,",
"MichalLeonBorsuk,",
"MatiasFernandez,Giorgiofox",
"TommasoVentafridda",
" ",
"#Havoc:",
"jboone,furrtek,eried,argilo,",
@@ -6,6 +6,10 @@
#include <cstdint>
/*
TODO: Now it is dyn width. There should be an algorithm to fill the menu based on it's size.
*/
namespace ui {
class AboutView : public View {
public:
@@ -22,11 +26,11 @@ class AboutView : public View {
uint16_t frame_sync_count{0};
void on_frame_sync();
MenuView menu_view{
{0, 0, 240, 264},
{0, 0, screen_width, screen_height - 56},
true};
Button button_ok{
{240 / 3, 270, 240 / 3, 24},
{screen_width / 3, screen_height - 50, screen_width / 3, 24},
"OK",
};
+146 -36
View File
@@ -40,6 +40,8 @@ namespace pmem = portapack::persistent_memory;
namespace ui {
static const char speed_type_msg[][6] = {" Spd:", " IAS:", " TAS:"};
static std::string get_map_tag(const AircraftRecentEntry& entry) {
return trimr(entry.callsign.empty() ? entry.icao_str : entry.callsign);
}
@@ -70,18 +72,34 @@ void RecentEntriesTable<AircraftRecentEntries>::draw(
entry_string +=
(entry.callsign.empty() ? entry.icao_str + " " : entry.callsign + " ") +
to_string_dec_uint((unsigned int)(entry.pos.altitude / 100), 4) +
to_string_dec_uint((unsigned int)entry.velo.speed, 4) +
to_string_dec_uint((unsigned int)(entry.amp >> 9), 4) + " " +
(entry.hits <= 999 ? to_string_dec_uint(entry.hits, 3) + " " : "1k+ ") +
to_string_dec_uint(entry.age, 4);
to_string_dec_uint((unsigned int)(entry.pos.altitude / 100), 4);
if (entry.velo.type == SPD_IAS && entry.pos.alt_valid) { // IAS can be converted to TAS
// It is generally accepted that for every thousand feet of altitude,
// true airspeed is approximately 2% higher than indicated airspeed.
// Since the application CPU has no floating point unit, we avoid floating point here
// tas = entry.velo.speed + (float)entry.pos.altitude / 1000.0 * 0.02 * entry.velo.speed;
unsigned int tas = entry.velo.speed + entry.pos.altitude * 2 * entry.velo.speed / 100000;
entry_string +=
to_string_dec_uint(tas, 4) + '*' +
to_string_dec_uint((unsigned int)(entry.amp >> 9), 3);
} else {
entry_string +=
to_string_dec_uint((unsigned int)entry.velo.speed, 4) +
to_string_dec_uint((unsigned int)(entry.amp >> 9), 4);
}
entry_string += " " +
(entry.hits <= 999 ? to_string_dec_uint(entry.hits, 3) + " " : "1k+ ") +
to_string_dec_uint(entry.age, 4);
painter.draw_string(
target_rect.location(),
style,
entry_string);
if (entry.pos.valid)
if (entry.pos.pos_valid)
painter.draw_bitmap(target_rect.location() + Point(8 * 8, 0),
bitmap_target, target_color, style.background);
}
@@ -93,22 +111,31 @@ void ADSBLogger::log(const ADSBLogEntry& log_entry) {
log_line.reserve(100);
log_line = log_entry.raw_data;
log_line += "ICAO:" + log_entry.icao;
log_line += " ICAO:" + log_entry.icao;
if (log_entry.sqwk)
log_line += " Squawk:" + to_string_dec_uint(log_entry.sqwk, 4, '0');
if (!log_entry.callsign.empty())
log_line += " " + log_entry.callsign;
if (log_entry.pos.valid)
log_line += " Alt:" + to_string_dec_int(log_entry.pos.altitude) +
" Lat:" + to_string_decimal(log_entry.pos.latitude, 7) +
if (log_entry.pos.alt_valid)
log_line += " Alt:" + to_string_dec_int(log_entry.pos.altitude);
if (log_entry.pos.pos_valid)
log_line += " Lat:" + to_string_decimal(log_entry.pos.latitude, 7) +
" Lon:" + to_string_decimal(log_entry.pos.longitude, 7);
if (log_entry.vel.valid)
log_line += " Type:" + to_string_dec_uint(log_entry.vel_type) +
" Hdg:" + to_string_dec_uint(log_entry.vel.heading) +
" Spd: " + to_string_dec_int(log_entry.vel.speed);
speed_type_msg[log_entry.vel.type] +
to_string_dec_int(log_entry.vel.speed) +
" Vrate:" + to_string_dec_int(log_entry.vel.v_rate);
if (log_entry.sil != 0)
log_line += " Sil:" + to_string_dec_uint(log_entry.sil);
log_file.write_entry(log_line);
}
@@ -258,7 +285,7 @@ ADSBRxDetailsView::ADSBRxDetailsView(
get_map_tag(entry_),
entry_.pos.altitude,
GeoPos::alt_unit::FEET,
GeoPos::spd_unit::MPH,
GeoPos::spd_unit::HIDDEN,
entry_.pos.latitude,
entry_.pos.longitude,
entry_.velo.heading);
@@ -356,11 +383,12 @@ void ADSBRxDetailsView::refresh_ui() {
text_callsign.set(entry_.callsign);
text_infos.set(entry_.info_string);
std::string str_sil = (entry_.sil > 0) ? " Sil:" + to_string_dec_uint(entry_.sil) : "";
std::string str_sqw = (entry_.sqwk > 0) ? " Sqw:" + to_string_dec_uint(entry_.sqwk) : "";
if (entry_.velo.heading < 360 && entry_.velo.speed >= 0)
text_info2.set("Hdg:" + to_string_dec_uint(entry_.velo.heading) +
" Spd:" + to_string_dec_int(entry_.velo.speed) + str_sil);
speed_type_msg[entry_.velo.type] + to_string_dec_int(entry_.velo.speed) + str_sil + str_sqw);
else
text_info2.set(str_sil);
text_info2.set(str_sil + str_sqw);
text_frame_pos_even.set(to_string_hex_array(entry_.frame_pos_even.get_raw_data(), 14));
text_frame_pos_odd.set(to_string_hex_array(entry_.frame_pos_odd.get_raw_data(), 14));
@@ -381,7 +409,7 @@ ADSBRxView::ADSBRxView(NavigationView& nav) {
&status_good_frame,
&field_volume});
recent_entries_view.set_parent_rect({0, 16, 240, 272});
recent_entries_view.set_parent_rect({0, 16, screen_width, 272});
recent_entries_view.on_select = [this, &nav](const AircraftRecentEntry& entry) {
detail_key = entry.key();
details_view = nav.push<ADSBRxDetailsView>(entry);
@@ -421,14 +449,22 @@ void ADSBRxView::focus() {
void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
auto frame = message->frame;
uint32_t ICAO_address = frame.get_ICAO_address();
status_frame.toggle();
// Bad frame, skip it.
if (!frame.check_CRC() || ICAO_address == 0)
return;
uint32_t ICAO_address;
uint32_t crc = frame.check_CRC();
if (crc != 0) {
if (find(recent, crc) != recent.end())
ICAO_address = crc;
else
return; // Bad frame, skip it.
} else {
ICAO_address = frame.get_ICAO_address();
if (ICAO_address == 0)
return; // Bad frame, skip it.
}
ADSBLogEntry log_entry;
status_good_frame.toggle();
rtc::RTC datetime;
@@ -440,42 +476,68 @@ void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
entry.inc_hit();
entry.reset_age();
if (pmem::beep_on_packets()) {
baseband::request_audio_beep(1000, 24000, 60);
}
// Store smoothed amplitude on updates.
entry.amp = entry.hits == 0
? message->amp
: ((entry.amp * 15) + message->amp) >> 4;
log_entry.raw_data = to_string_hex_array(frame.get_raw_data(), 14);
uint8_t df = frame.get_DF();
if (df == 11) // do not log DF11, because messages arrive too frequently
return;
ADSBLogEntry log_entry;
uint8_t* raw_data = frame.get_raw_data();
if (df & 0x10) // 112 bits
log_entry.raw_data = to_string_hex_array(raw_data, 14);
else { // 56 bits
log_entry.raw_data = to_string_hex_array(raw_data, 7);
log_entry.raw_data.append(14, ' ');
}
log_entry.icao = entry.icao_str;
if (frame.get_DF() == DF_ADSB) {
// 17: // Extended squitter
// 18: // Extended squitter/non-transponder
if (df == DF_ADSB) {
uint8_t msg_type = frame.get_msg_type();
uint8_t msg_sub = frame.get_msg_sub();
uint8_t* raw_data = frame.get_raw_data();
// 4: // surveillance, altitude reply
if ((msg_type >= AIRCRAFT_ID_L) && (msg_type <= AIRCRAFT_ID_H)) {
// transmitted when horizontal position information is not available but altitude information is available
if (msg_type == 0) {
// Q-bit must be present
if (raw_data[5] & 1) {
int altitude = ((((raw_data[5] & 0xFE) << 3) | ((raw_data[6] & 0xF0) >> 4)) * 25) - 1000;
log_entry.pos.altitude = entry.pos.altitude = altitude;
log_entry.pos.alt_valid = entry.pos.alt_valid = true;
}
}
// 1-4: Aircraft identification
else if ((msg_type >= AIRCRAFT_ID_L) && (msg_type <= AIRCRAFT_ID_H)) {
entry.set_callsign(decode_frame_id(frame));
log_entry.callsign = entry.callsign;
}
// 9:
// 18: // Extended squitter/non-transponder
// 21: // Comm-B, identity reply
// 20: // Comm-B, altitude reply
// 9-18: Airborne position (w/Baro Altitude)
// 20-22: Airborne position (w/GNSS Height)
else if (((msg_type >= AIRBORNE_POS_BARO_L) && (msg_type <= AIRBORNE_POS_BARO_H)) ||
((msg_type >= AIRBORNE_POS_GPS_L) && (msg_type <= AIRBORNE_POS_GPS_H))) {
entry.set_frame_pos(frame, raw_data[6] & 4);
log_entry.pos = entry.pos;
if (entry.pos.valid) {
if (entry.pos.pos_valid) {
std::string str_info =
"Alt:" + to_string_dec_int(entry.pos.altitude) +
" Lat:" + to_string_decimal(entry.pos.latitude, 2) +
" Lon:" + to_string_decimal(entry.pos.longitude, 2);
entry.set_info_string(std::move(str_info));
}
// 19: Airborne velocities
} else if (msg_type == AIRBORNE_VEL && msg_sub >= VEL_GND_SUBSONIC && msg_sub <= VEL_AIR_SUPERSONIC) {
entry.set_frame_velo(frame);
log_entry.vel = entry.velo;
@@ -485,11 +547,59 @@ void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
}
}
logger->log(log_entry);
// 4: // surveillance, altitude reply
// 20: // Comm-B, altitude reply
// 21: // Comm-B, identity reply
if (df == 0 || df == 4 || df == 20) { // Decode the 13 bit AC altitude field
uint8_t m_bit = raw_data[3] & (1 << 6);
uint8_t q_bit = raw_data[3] & (1 << 4);
int altitude = 0;
if (pmem::beep_on_packets()) {
baseband::request_audio_beep(1000, 24000, 60);
if (!m_bit) { // units -> FEET
if (q_bit) { // N is the 11 bit integer resulting from the removal of bit Q and M
int n = ((raw_data[2] & 31) << 6) |
((raw_data[3] & 0x80) >> 2) |
((raw_data[3] & 0x20) >> 1) |
(raw_data[3] & 15);
// The final altitude is due to the resulting number multiplied by 25, minus 1000.
altitude = 25 * n - 1000;
if (altitude < 0)
altitude = 0;
} // else N is an 11 bit Gillham coded altitude
}
log_entry.pos.altitude = entry.pos.altitude = altitude;
log_entry.pos.alt_valid = entry.pos.alt_valid = true;
}
if (df == 5 || df == 21 ||
(df == 17 && frame.get_msg_type() == 28 && frame.get_msg_sub() == 1)) { // Decode the squawk code
uint8_t* s = (df == 17) ? raw_data + 5 : raw_data + 2; // calc start of the code
uint16_t sqwk{0};
sqwk = ((s[1] & 0x80) >> 5) | ((s[0] & 0x02) >> 0) | ((s[0] & 0x08) >> 3); // A
sqwk *= 10;
sqwk += ((s[1] & 0x02) << 1) | ((s[1] & 0x08) >> 2) | ((s[1] & 0x20) >> 5); // B
sqwk *= 10;
sqwk += ((s[0] & 0x01) << 2) | ((s[0] & 0x04) >> 1) | ((s[0] & 0x10) >> 4); // C
sqwk *= 10;
sqwk += ((s[1] & 0x01) << 2) | ((s[1] & 0x04) >> 1) | ((s[1] & 0x10) >> 4); // D
log_entry.sqwk = entry.sqwk = sqwk;
}
if (df == 20 || df == 21) {
if (raw_data[4] == 0x20) { // try decode as BDS20
std::string callsign = decode_frame_id(frame);
if (callsign.find('#') == std::string::npos) { // all chars OK
entry.set_callsign(callsign);
log_entry.callsign = callsign;
}
}
}
logger->log(log_entry);
}
void ADSBRxView::on_tick_second() {
@@ -544,7 +654,7 @@ void ADSBRxView::refresh_ui() {
}
// NB: current entry also gets a marker so it shows up if map is panned.
if (map_needs_update && entry.pos.valid && entry.state <= ADSBAgeState::Recent) {
if (map_needs_update && entry.pos.pos_valid && entry.state <= ADSBAgeState::Recent) {
map_needs_update = details_view->add_map_marker(entry);
}
+17 -15
View File
@@ -95,8 +95,8 @@ struct AircraftRecentEntry {
ADSBAgeState state{ADSBAgeState::Invalid};
uint32_t age{0}; // In seconds
uint32_t amp{0};
adsb_pos pos{false, 0, 0, 0};
adsb_vel velo{false, 0, 999, 0};
adsb_pos pos{false, false, 0, 0, 0};
adsb_vel velo{false, SPD_GND, 0, 999, 0};
ADSBFrame frame_pos_even{};
ADSBFrame frame_pos_odd{};
@@ -105,6 +105,7 @@ struct AircraftRecentEntry {
std::string info_string{};
uint8_t sil{0}; // Surveillance integrity level
uint16_t sqwk{0};
AircraftRecentEntry(const uint32_t ICAO_address)
: ICAO_address{ICAO_address} {
@@ -152,8 +153,8 @@ struct AircraftRecentEntry {
age += delta;
if (age < ADSBAgeLimit::Current)
state = pos.valid ? ADSBAgeState::Invalid
: ADSBAgeState::Current;
state = pos.pos_valid ? ADSBAgeState::Current
: ADSBAgeState::Invalid;
else if (age < ADSBAgeLimit::Recent)
state = ADSBAgeState::Recent;
@@ -178,6 +179,7 @@ struct ADSBLogEntry {
adsb_vel vel{};
uint8_t vel_type{};
uint8_t sil{};
uint16_t sqwk{};
};
// TODO: Make logging optional.
@@ -228,7 +230,7 @@ class ADSBRxAircraftDetailsView : public View {
"-"};
Text text_model{
{0 * 8, 6 * 16, 30 * 8, 16},
{0 * 8, 6 * 16, screen_width, 16},
"-"};
Text text_type{
@@ -236,19 +238,19 @@ class ADSBRxAircraftDetailsView : public View {
"-"};
Text text_number_of_engines{
{18 * 8, 8 * 16, 30 * 8, 16},
{18 * 8, 8 * 16, screen_width, 16},
"-"};
Text text_engine_type{
{0 * 8, 10 * 16, 30 * 8, 16},
{0 * 8, 10 * 16, screen_width, 16},
"-"};
Text text_owner{
{0 * 8, 12 * 16, 30 * 8, 16},
{0 * 8, 12 * 16, screen_width, 16},
"-"};
Text text_operator{
{0 * 8, 14 * 16, 30 * 8, 16},
{0 * 8, 14 * 16, screen_width, 16},
"-"};
Button button_close{
@@ -310,7 +312,7 @@ class ADSBRxDetailsView : public View {
"-"};
Text text_airline{
{0 * 8, 4 * 16, 30 * 8, 16},
{0 * 8, 4 * 16, screen_width, 16},
"-"};
Text text_country{
@@ -318,18 +320,18 @@ class ADSBRxDetailsView : public View {
"-"};
Text text_infos{
{0 * 8, 6 * 16, 30 * 8, 16},
{0 * 8, 6 * 16, screen_width, 16},
"-"};
Text text_info2{
{0 * 8, 7 * 16, 30 * 8, 16},
{0 * 8, 7 * 16, screen_width, 16},
"-"};
Text text_frame_pos_even{
{0 * 8, 14 * 16, 30 * 8, 16},
{0 * 8, 14 * 16, screen_width, 16},
"-"};
Text text_frame_pos_odd{
{0 * 8, 16 * 16, 30 * 8, 16},
{0 * 8, 16 * 16, screen_width, 16},
"-"};
Button button_aircraft_details{
@@ -440,7 +442,7 @@ class ADSBRxView : public View {
};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
{screen_width - 2 * 8, 0 * 16}};
MessageHandlerRegistration message_handler_frame{
Message::ID::ADSBFrame,
+2 -2
View File
@@ -247,8 +247,8 @@ APRSTableView::APRSTableView(NavigationView& nav, Rect parent_rec)
details_view.hidden(true);
recent_entries_view.set_parent_rect({0, 0, 240, 280});
details_view.set_parent_rect({0, 0, 240, 280});
recent_entries_view.set_parent_rect({0, 0, screen_width, screen_width - 40});
details_view.set_parent_rect({0, 0, screen_width, screen_width - 40});
recent_entries_view.on_select = [this](const APRSRecentEntry& entry) {
this->on_show_detail(entry);
+5 -5
View File
@@ -134,7 +134,7 @@ class APRSDetailsView : public View {
bool send_updates{false};
Console console{
{0, 0 * 16, 240, 224}};
{0, 0 * 16, screen_width, 224}};
Button button_done{
{160, 14 * 16, 8 * 8, 3 * 16},
@@ -220,7 +220,7 @@ class APRSRxView : public View {
{21 * 8, 5, 6 * 8, 4}};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
{screen_width - 2 * 8, 0 * 16}};
OptionsField options_region{
{0 * 8, 0 * 8},
@@ -241,7 +241,7 @@ class APRSRxView : public View {
// DEBUG
RecordView record_view{
{0 * 8, 1 * 16, 30 * 8, 1 * 16},
{0 * 8, 1 * 16, screen_width, 1 * 16},
u"AFS_????.WAV",
aprs_dir,
RecordView::FileType::WAV,
@@ -249,7 +249,7 @@ class APRSRxView : public View {
4};
Console console{
{0, 2 * 16, 240, 240}};
{0, 2 * 16, screen_width, screen_height - 80}};
std::unique_ptr<APRSLogger> logger{};
};
@@ -265,7 +265,7 @@ class APRSRXView : public View {
private:
NavigationView& nav_;
Rect view_rect = {0, 3 * 8, 240, 280};
Rect view_rect = {0, 3 * 8, screen_width, screen_height - 40};
APRSRxView view_stream{nav_, view_rect};
APRSTableView view_table{nav_, view_rect};
+1 -1
View File
@@ -91,7 +91,7 @@ class APRSTXView : public View {
' '};
Text text_payload{
{0 * 8, 5 * 16, 30 * 8, 16},
{0 * 8, 5 * 16, screen_width, 16},
"-"};
Button button_set{
{0 * 8, 6 * 16, 80, 32},
+1 -1
View File
@@ -90,7 +90,7 @@ class BattinfoView : public View {
"-"};
Text text_warn{
{1 * 8, 8 * 16, 30 * 8, 2 * 16},
{1 * 8, 8 * 16, screen_width, 2 * 16},
""}; */
Button button_mode{
+2 -2
View File
@@ -189,7 +189,7 @@ class BHTView : public View {
tx_modes tx_mode = IDLE;
Rect view_rect = {0, 3 * 8, 240, 176};
Rect view_rect = {0, 3 * 8, screen_width, 176};
XylosView view_xylos{view_rect};
EPARView view_EPAR{view_rect};
@@ -218,7 +218,7 @@ class BHTView : public View {
' '};
ProgressBar progressbar{
{0 * 8, 29 * 8, 30 * 8, 16},
{0 * 8, 29 * 8, screen_width, 16},
};
TransmitterView tx_view{
@@ -42,7 +42,7 @@ class BMPFileViewer : public View {
private:
NavigationView& nav_;
std::filesystem::path path_{};
BMPViewer bmp{{0, 0, 240, 320}};
BMPViewer bmp{{0, 0, screen_width, screen_height}};
};
} // namespace ui
+2 -2
View File
@@ -78,11 +78,11 @@ class BTLERxView : public View {
nav_};
Button button_modem_setup{
{240 - 12 * 8, 1 * 16, 96, 24},
{screen_width - 12 * 8, 1 * 16, 96, 24},
"Modem setup"};
Console console{
{0, 4 * 16, 240, 240}};
{0, 4 * 16, screen_width, screen_height - 80}};
MessageHandlerRegistration message_handler_packet{
Message::ID::AFSKData,
+3 -3
View File
@@ -202,10 +202,10 @@ RegistersView::RegistersView(
};
button_done.on_select = [&nav](Button&) { nav.pop(); };
registers_widget.set_parent_rect({0, 48, 240, 192});
registers_widget.set_parent_rect({0, 48, screen_width, 192});
registers_widget.set_page(0);
text_title.set_parent_rect({(240 - static_cast<int>(title.size()) * 8) / 2, 16,
text_title.set_parent_rect({(screen_width - static_cast<int>(title.size()) * 8) / 2, 16,
static_cast<int>(title.size()) * 8, 16});
text_title.set(title);
@@ -482,7 +482,7 @@ DebugPmemView::DebugPmemView(NavigationView& nav)
: registers_widget(RegistersWidgetConfig{CT_PMEM, PMEM_SIZE_BYTES, page_size, 8}) {
add_children({&registers_widget, &text_checksum, &text_checksum2, &button_ok});
registers_widget.set_parent_rect({0, 32, 240, 192});
registers_widget.set_parent_rect({0, 32, screen_width, 192});
text_checksum.set("Size: " + to_string_dec_uint(portapack::persistent_memory::data_size(), 3) + " CRC: " + to_string_hex(portapack::persistent_memory::pmem_stored_checksum(), 8));
text_checksum2.set("Calculated CRC: " + to_string_hex(portapack::persistent_memory::pmem_calculated_checksum(), 8));
+3 -3
View File
@@ -283,7 +283,7 @@ class DebugMemoryDumpView : public View {
"Write"};
Button button_done{
{128, 240, 96, 24},
{128, screen_height - 80, 96, 24},
"Done"};
Labels labels{
@@ -332,7 +332,7 @@ class DebugPmemView : public View {
Text text_checksum2{{16, 248, 208, 16}};
Button button_ok{
{240 / 3, 270, 240 / 3, 24},
{screen_width / 3, 270, screen_width / 3, 24},
"OK",
};
@@ -364,7 +364,7 @@ public:
private:
Console console {
{ 8, 16, 224, 240 }
{ 8, 16, 224, screen_height-80 }
};
Button button_exit {
+2 -2
View File
@@ -124,7 +124,7 @@ class EncodersConfigView : public View {
""};
Waveform waveform{
{0, 17 * 8, 240, 32},
{0, 17 * 8, screen_width, 32},
waveform_buffer,
0,
0,
@@ -201,7 +201,7 @@ class EncodersView : public View {
void start_tx(const bool scan);
void on_tx_progress(const uint32_t progress, const bool done);
Rect view_rect = {0, 4 * 8, 240, 168};
Rect view_rect = {0, 4 * 8, screen_width, 168};
EncodersConfigView view_config{nav_, view_rect};
EncodersScanView view_scan{nav_, view_rect};
@@ -92,6 +92,9 @@ void ExternalModuleView::on_tick_second() {
case app_location_t::TX:
btnText += " (TX)";
break;
case app_location_t::TRX:
btnText += " (TRX)";
break;
case app_location_t::SETTINGS:
btnText += " (Settings)";
break;
@@ -84,7 +84,7 @@ class ExternalModuleView : public View {
Text text_app5_name{{24, 160, 200, 16}};
Button dummy{
{240, 0, 0, 0},
{screen_width, 0, 0, 0},
""};
SignalToken signal_token_tick_second{};
+81 -92
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,14 +438,29 @@ 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();
}
void FileManBaseView::copy_waterfall(std::filesystem::path path) {
nav_.push<ModalMessageView>(
"Install", " Use this gradient file\n for all waterfalls?", YESNO,
[this, path](bool choice) {
if (choice) {
delete_file(default_gradient_file);
copy_file(path, default_gradient_file);
}
});
}
const FileManBaseView::file_assoc_t& FileManBaseView::get_assoc(
const fs::path& ext) const {
size_t index = 0;
@@ -444,7 +486,7 @@ FileLoadView::FileLoadView(
add_children({&menu_view});
// Resize menu view to fill screen
menu_view.set_parent_rect({0, 3 * 8, 240, 29 * 8});
menu_view.set_parent_rect({0, 3 * 8, screen_width, 29 * 8});
refresh_list();
@@ -478,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) {
@@ -685,7 +670,11 @@ bool FileManagerView::handle_file_open() {
auto ext = path.extension();
if (path_iequal(txt_ext, ext)) {
nav_.push<TextEditorView>(path);
if (path_iequal(current_path, u"/" + waterfalls_dir)) {
copy_waterfall(path);
} else {
nav_.push<TextEditorView>(path);
}
return true;
} else if (is_cxx_capture_file(path) || path_iequal(ppl_ext, ext)) {
// TODO: Enough memory to push?
+6 -3
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,8 @@ 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_;
@@ -124,7 +127,7 @@ class FileManBaseView : public View {
};
MenuView menu_view{
{0, 2 * 8, 240, 26 * 8},
{0, 2 * 8, screen_width, 26 * 8},
true};
Button button_exit{
@@ -171,7 +174,7 @@ private:
};
Text text_path {
{ 0 * 8, 2 * 16, 30 * 8, 16 },
{ 0 * 8, 2 * 16, screen_width, 16 },
"",
};
@@ -181,7 +184,7 @@ private:
};
Text text_name {
{ 0 * 8, 7 * 16, 30 * 8, 16 },
{ 0 * 8, 7 * 16, screen_width, 16 },
"",
};
@@ -80,7 +80,7 @@ FlashUtilityView::FlashUtilityView(NavigationView& nav)
add_children({&labels,
&menu_view});
menu_view.set_parent_rect({0, 3 * 8, 240, 33 * 8});
menu_view.set_parent_rect({0, 3 * 8, screen_width, 33 * 8});
ensure_directory(apps_dir);
ensure_directory(firmware_dir);
@@ -60,7 +60,7 @@ class FlashUtilityView : public View {
{{4, 4}, "Select firmware to flash:", Theme::getInstance()->bg_darkest->foreground}};
MenuView menu_view{
{0, 2 * 8, 240, 26 * 8},
{0, 2 * 8, screen_width, 26 * 8},
true};
std::filesystem::path extract_tar(std::filesystem::path::string_type path, ui::Painter& painter); // extracts the tar file, and returns the firmware.bin path from it. empty string if no fw
+2 -2
View File
@@ -45,8 +45,8 @@ using option_db_t = std::pair<std::string_view, int32_t>;
using options_db_t = std::vector<option_db_t>;
extern options_db_t freqman_modulations;
extern options_db_t freqman_bandwidths[5];
extern options_db_t freqman_steps;
extern options_db_t freqman_bandwidths[6];
// extern options_db_t freqman_steps; // now included via ui_receiver.hpp
extern options_db_t freqman_steps_short;
options_t dboptions_to_options(const options_db_t& dboptions) {
+1 -1
View File
@@ -100,7 +100,7 @@ class FrequencySaveView : public FreqManBaseView {
{{0 * 8, 6 * 16}, "Description:", Theme::getInstance()->bg_darkest->foreground}};
TextField field_description{
{0 * 8, 7 * 16, 30 * 8, 1 * 16},
{0 * 8, 7 * 16, screen_width, 1 * 16},
""};
Button button_save{
+1
View File
@@ -34,6 +34,7 @@ namespace ui {
IQTrimView::IQTrimView(NavigationView& nav)
: nav_{nav} {
power_buckets_.resize(screen_width);
add_children({
&labels,
&field_path,
+2 -2
View File
@@ -96,7 +96,7 @@ class IQTrimView : public View {
std::filesystem::path path_{};
Optional<iq::CaptureInfo> info_{};
std::array<iq::PowerBuckets::Bucket, screen_width> power_buckets_{};
std::vector<iq::PowerBuckets::Bucket> power_buckets_{};
TrimProgressUI progress_ui{};
Labels labels{
@@ -112,7 +112,7 @@ class IQTrimView : public View {
};
TextField field_path{
{0 * 8, 1 * 16, 30 * 8, 1 * 16},
{0 * 8, 1 * 16, screen_width, 1 * 16},
"Open File..."};
Point pos_lines{0 * 8, 4 * 16};
@@ -94,10 +94,10 @@ void GlassView::get_max_power(const ChannelSpectrum& spectrum, uint8_t bin, uint
rf::Frequency GlassView::get_freq_from_bin_pos(uint8_t pos) {
rf::Frequency freq_at_pos = 0;
if (mode == LOOKING_GLASS_SINGLEPASS) {
// starting from the middle, minus 8 ignored bin on each side. Since pos is [-120,120] after the (pos - 120), it's divided by SCREEN_W(240)/2 => 120
freq_at_pos = f_center_ini + ((pos - 120) * ((looking_glass_range - ((16 * looking_glass_range) / SPEC_NB_BINS)) / 2)) / (SCREEN_W / 2);
// starting from the middle, minus 8 ignored bin on each side. Since pos is [-120,120] after the (pos - 120), it's divided by screen_width(240)/2 => 120
freq_at_pos = f_center_ini + ((pos - 120) * ((looking_glass_range - ((16 * looking_glass_range) / SPEC_NB_BINS)) / 2)) / (screen_width / 2);
} else
freq_at_pos = f_min + (2 * offset * each_bin_size) + (pos * looking_glass_range) / SCREEN_W;
freq_at_pos = f_min + (2 * offset * each_bin_size) + (pos * looking_glass_range) / screen_width;
return freq_at_pos;
}
@@ -122,15 +122,15 @@ void GlassView::reset_live_view() {
// Clear screen in peak mode.
if (live_frequency_view == 2)
display.fill_rectangle({{0, 108 + 16}, {SCREEN_W, SCREEN_H - (108 + 16)}}, {0, 0, 0});
display.fill_rectangle({{0, 108 + 16}, {screen_width, screen_height - (108 + 16)}}, {0, 0, 0});
}
void GlassView::add_spectrum_pixel(uint8_t power) {
spectrum_row[pixel_index] = spectrum_rgb3_lut[power]; // row of colors
spectrum_row[pixel_index] = gradient.lut[power]; // row of colors
spectrum_data[pixel_index] = (live_frequency_integrate * spectrum_data[pixel_index] + power) / (live_frequency_integrate + 1); // smoothing
pixel_index++;
if (pixel_index == SCREEN_W) // got an entire waterfall line
if (pixel_index == screen_width) // got an entire waterfall line
{
if (live_frequency_view > 0) {
constexpr int rssi_sample_range = SPEC_NB_BINS;
@@ -140,22 +140,22 @@ void GlassView::add_spectrum_pixel(uint8_t power) {
constexpr int raw_min = rssi_sample_range * rssi_voltage_min / adc_voltage_max;
constexpr int raw_max = rssi_sample_range * rssi_voltage_max / adc_voltage_max;
constexpr int raw_delta = raw_max - raw_min;
const range_t<int> y_max_range{0, 320 - (108 + 16)};
const range_t<int> y_max_range{0, screen_height - (108 + 16)};
// drawing and keeping track of max freq
for (uint16_t xpos = 0; xpos < SCREEN_W; xpos++) {
for (uint16_t xpos = 0; xpos < screen_width; xpos++) {
// save max powerwull freq
if (spectrum_data[xpos] > max_freq_power) {
max_freq_power = spectrum_data[xpos];
max_freq_hold = get_freq_from_bin_pos(xpos);
}
int16_t point = y_max_range.clip(((spectrum_data[xpos] - raw_min) * (320 - (108 + 16))) / raw_delta);
int16_t point = y_max_range.clip(((spectrum_data[xpos] - raw_min) * (screen_height - (108 + 16))) / raw_delta);
uint8_t color_gradient = (point * 255) / 212;
// clear if not in peak view
if (live_frequency_view != 2) {
display.fill_rectangle({{xpos, 108 + 16}, {1, SCREEN_H - point}}, {0, 0, 0});
display.fill_rectangle({{xpos, 108 + 16}, {1, screen_height - point}}, {0, 0, 0});
}
display.fill_rectangle({{xpos, SCREEN_H - point}, {1, point}}, {color_gradient, 0, uint8_t(255 - color_gradient)});
display.fill_rectangle({{xpos, screen_height - point}, {1, point}}, {color_gradient, 0, uint8_t(255 - color_gradient)});
}
if (last_max_freq != max_freq_hold) {
last_max_freq = max_freq_hold;
@@ -163,7 +163,7 @@ void GlassView::add_spectrum_pixel(uint8_t power) {
}
plot_marker(marker_pixel_index);
} else {
display.draw_pixels({{0, display.scroll(1)}, {SCREEN_W, 1}}, spectrum_row); // new line at top, one less var, speedier
display.draw_pixels({{0, display.scroll(1)}, {screen_width, 1}}, spectrum_row); // new line at top, one less var, speedier
}
pixel_index = 0; // Start New cascade line
}
@@ -199,7 +199,7 @@ bool GlassView::process_bins(uint8_t* powerlevel) {
void GlassView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
baseband::spectrum_streaming_stop();
// Convert bins of this spectrum slice into a representative max_power and when enough, into pixels
// we actually need SCREEN_W (240) of those bins
// we actually need screen_width (240) of those bins
for (uint8_t bin = 0; bin < bin_length; bin++) {
get_max_power(spectrum, bin, max_power);
if (max_power > range_max_power)
@@ -238,7 +238,7 @@ void GlassView::on_hide() {
}
void GlassView::on_show() {
display.scroll_set_area(109, 319); // Restart scroll on the correct coordinates
display.scroll_set_area(109, screen_height - 1); // Restart scroll on the correct coordinates
baseband::spectrum_streaming_start();
}
@@ -253,11 +253,11 @@ void GlassView::on_range_changed() {
// if the view is done in one pass, show it like in analog_audio_app
mode = LOOKING_GLASS_SINGLEPASS;
offset = 2;
bin_length = SCREEN_W;
bin_length = screen_width;
ignore_dc = 0;
looking_glass_bandwidth = looking_glass_range;
looking_glass_sampling_rate = looking_glass_range;
each_bin_size = looking_glass_bandwidth / SCREEN_W;
each_bin_size = looking_glass_bandwidth / screen_width;
looking_glass_step = looking_glass_bandwidth;
f_center_ini = f_min + (looking_glass_bandwidth / 2); // Initial center frequency for sweep
} else {
@@ -269,7 +269,7 @@ void GlassView::on_range_changed() {
if (mode == LOOKING_GLASS_FASTSCAN) {
offset = 2;
ignore_dc = 4;
bin_length = SCREEN_W;
bin_length = screen_width;
} else { // if( mode == LOOKING_GLASS_SLOWSCAN )
offset = 2;
bin_length = 80;
@@ -279,7 +279,7 @@ void GlassView::on_range_changed() {
f_center_ini = f_min - (offset * each_bin_size) + (looking_glass_bandwidth / 2); // Initial center frequency for sweep
}
search_span = looking_glass_range / MHZ_DIV;
marker_pixel_step = looking_glass_range / SCREEN_W; // Each pixel value in Hz
marker_pixel_step = looking_glass_range / screen_width; // Each pixel value in Hz
pixel_index = 0;
max_power = 0;
@@ -304,10 +304,10 @@ void GlassView::plot_marker(uint8_t pos) {
{
shift_y = 16;
}
portapack::display.fill_rectangle({0, 100 + shift_y, SCREEN_W, 8}, Theme::getInstance()->bg_darkest->background); // Clear old marker and whole marker rectangle btw
portapack::display.fill_rectangle({pos - 2, 100 + shift_y, 5, 3}, Theme::getInstance()->fg_red->foreground); // Red marker top
portapack::display.fill_rectangle({pos - 1, 103 + shift_y, 3, 3}, Theme::getInstance()->fg_red->foreground); // Red marker middle
portapack::display.fill_rectangle({pos, 106 + shift_y, 1, 2}, Theme::getInstance()->fg_red->foreground); // Red marker bottom
portapack::display.fill_rectangle({0, 100 + shift_y, screen_width, 8}, Theme::getInstance()->bg_darkest->background); // Clear old marker and whole marker rectangle btw
portapack::display.fill_rectangle({pos - 2, 100 + shift_y, 5, 3}, Theme::getInstance()->fg_red->foreground); // Red marker top
portapack::display.fill_rectangle({pos - 1, 103 + shift_y, 3, 3}, Theme::getInstance()->fg_red->foreground); // Red marker middle
portapack::display.fill_rectangle({pos, 106 + shift_y, 1, 2}, Theme::getInstance()->fg_red->foreground); // Red marker bottom
}
void GlassView::update_min(int32_t v) {
@@ -358,6 +358,11 @@ GlassView::GlassView(
NavigationView& nav)
: nav_(nav) {
baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
spectrum_row.resize(screen_width);
spectrum_data.resize(screen_width);
if (!gradient.load_file(default_gradient_file)) {
gradient.set_default();
}
add_children({&labels,
&field_frequency_min,
@@ -433,11 +438,11 @@ GlassView::GlassView(
freq_stats.hidden(true);
button_jump.hidden(true);
button_rst.hidden(true);
display.scroll_set_area(109, 319); // Restart scroll on the correct coordinates.
display.scroll_set_area(109, screen_height - 1); // Restart scroll on the correct coordinates.
break;
case 1: // LEVEL
display.fill_rectangle({{0, 108}, {SCREEN_W, 24}}, {0, 0, 0});
display.fill_rectangle({{0, 108}, {screen_width, 24}}, {0, 0, 0});
display.scroll_disable();
level_integration.hidden(false);
freq_stats.hidden(false);
@@ -447,7 +452,7 @@ GlassView::GlassView(
case 2: // PEAK
default:
display.fill_rectangle({{0, 108}, {SCREEN_W, 24}}, {0, 0, 0});
display.fill_rectangle({{0, 108}, {screen_width, 24}}, {0, 0, 0});
display.scroll_disable();
level_integration.hidden(false);
freq_stats.hidden(false);
@@ -487,9 +492,9 @@ GlassView::GlassView(
field_marker.on_encoder_change = [this](TextField&, EncoderEvent delta) {
if ((marker_pixel_index + delta) < 0)
marker_pixel_index = marker_pixel_index + delta + SCREEN_W;
else if ((marker_pixel_index + delta) > SCREEN_W)
marker_pixel_index = marker_pixel_index + delta - SCREEN_W;
marker_pixel_index = marker_pixel_index + delta + screen_width;
else if ((marker_pixel_index + delta) > screen_width)
marker_pixel_index = marker_pixel_index + delta - screen_width;
else
marker_pixel_index = marker_pixel_index + delta;
on_marker_change();
@@ -527,7 +532,7 @@ GlassView::GlassView(
};
set_spec_iq_phase_calibration_value(get_spec_iq_phase_calibration_value()); // initialize iq_phase_calibration in radio
display.scroll_set_area(109, 319);
display.scroll_set_area(109, screen_height - 1); // Restart scroll on the correct coordinates
// trigger:
// Discord User jteich: WidebandSpectrum::on_message to set the trigger value. In WidebandSpectrum::execute,
@@ -535,7 +540,7 @@ GlassView::GlassView(
// at which time it pushes the buffer up with channel_spectrum.feed
baseband::set_spectrum(looking_glass_bandwidth, trigger);
marker_pixel_index = SCREEN_W / 2;
marker_pixel_index = screen_width / 2;
on_range_changed(); // Force a UI update.
receiver_model.set_sampling_rate(looking_glass_sampling_rate); // 20mhz
@@ -35,7 +35,7 @@
#include "ui_receiver.hpp"
#include "string_format.hpp"
#include "analog_audio_app.hpp"
#include "spectrum_color_lut.hpp"
#include "gradient.hpp"
namespace ui {
@@ -51,9 +51,6 @@ namespace ui {
#define LOOKING_GLASS_SINGLEPASS 2
// one spectrum line number of bins
#define SPEC_NB_BINS 256
// screen dimensions
#define SCREEN_W 240
#define SCREEN_H 320
class GlassView : public View {
public:
@@ -74,6 +71,7 @@ class GlassView : public View {
private:
NavigationView& nav_;
Gradient gradient{};
RxRadioState radio_state_{ReceiverModel::Mode::SpectrumAnalysis};
// Settings
rf::Frequency f_min = 260 * MHZ_DIV; // Default to 315/433 remote range.
@@ -154,8 +152,8 @@ class GlassView : public View {
uint8_t min_color_power{0}; // Filter cutoff level.
uint32_t pixel_index{0};
std::array<Color, SCREEN_W> spectrum_row{};
std::array<uint8_t, SCREEN_W> spectrum_data{};
std::vector<Color> spectrum_row{};
std::vector<uint8_t> spectrum_data{};
ChannelSpectrumFIFO* fifo{};
int32_t steps = 1;
@@ -167,7 +165,7 @@ class GlassView : public View {
rf::Frequency max_freq_hold = 0;
rf::Frequency last_max_freq = 0;
int16_t max_freq_power = -1000;
uint8_t bin_length = SCREEN_W;
uint8_t bin_length = screen_width;
uint8_t offset = 0;
uint8_t ignore_dc = 0;
@@ -221,7 +219,7 @@ class GlassView : public View {
{}};
ButtonWithEncoder button_beep_squelch{
{240 - 8 * 8, 2 * 16 + 4, 8 * 8, 1 * 8},
{screen_width - 8 * 8, 2 * 16 + 4, 8 * 8, 1 * 8},
""};
TextField field_marker{
@@ -292,15 +290,15 @@ class GlassView : public View {
}};
Button button_jump{
{SCREEN_W - 4 * 8, 5 * 16, 4 * 8, 16},
{screen_width - 4 * 8, 5 * 16, 4 * 8, 16},
"JMP"};
Button button_rst{
{SCREEN_W - 9 * 8, 5 * 16, 4 * 8, 16},
{screen_width - 9 * 8, 5 * 16, 4 * 8, 16},
"RST"};
Text freq_stats{
{0 * 8, 5 * 16, SCREEN_W - 10 * 8, 8},
{0 * 8, 5 * 16, screen_width - 10 * 8, 8},
""};
MessageHandlerRegistration message_handler_spectrum_config{
+2 -2
View File
@@ -202,7 +202,7 @@ void MicTXView::rxaudio(bool enable) {
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
// receiver_model.set_nbfm_configuration(n); is called above, depending user's selection (8k5, 11k, 16k).
break;
case MIC_MOD_WFM: // WFM, BW 200Khz aprox, or the two new addional BW filters (180k, 40k)
case MIC_MOD_WFM: // WFM, BW 200Khz aprox, or the two new addional BW filters (180k, 80k)
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
// receiver_model.set_wfm_configuration(n); is called above, depending user's selection (200k, 180k, 0k).
@@ -300,7 +300,7 @@ void MicTXView::update_receiver_rxbw(void) {
receiver_model.set_nbfm_configuration(rxbw_index); // we are in NFM/FM case, we need to select proper NFM/FM RX channel filter, NFM BW 8K5(0), NFM BW 11K(1), FM BW 16K (2)
break;
case MIC_MOD_WFM:
receiver_model.set_wfm_configuration(rxbw_index); // we are in WFM case, we need to select proper WFB RX BW filter, WFM BW 200K(0), WFM BW 180K(1), WFM BW 40K(2)
receiver_model.set_wfm_configuration(rxbw_index); // we are in WFM case, we need to select proper WFB RX BW filter, WFM BW 200K(0), WFM BW 180K(1), WFM BW 80K(2)
break;
case MIC_MOD_AM:
receiver_model.set_am_configuration(rxbw_index); // we are in AM TX mode, we need to select proper AM full path config AM-9K filter. 0+0 =>AM-9K(0), 0+1=1 =>AM-6K(1),
+1 -1
View File
@@ -350,7 +350,7 @@ class MicTXView : public View {
};
Button tx_button{
{10 * 8, 30 * 8, 10 * 8, 5 * 8},
{10 * 8, screen_width, 10 * 8, 5 * 8},
"PTT TX",
true};
+3 -3
View File
@@ -107,7 +107,7 @@ class PlaylistView : public View {
void handle_replay_thread_done(uint32_t return_code);
Text text_filename{
{0 * 8, 0 * 16, 30 * 8, 16}};
{0 * 8, 0 * 16, screen_width, 16}};
FrequencyField field_frequency{
{0 * 8, 1 * 16}};
@@ -139,13 +139,13 @@ class PlaylistView : public View {
true};
ImageButton button_play{
{28 * 8, 2 * 16, 2 * 8, 1 * 16},
{screen_width - 2 * 8, 2 * 16, 2 * 8, 1 * 16},
&bitmap_play,
Theme::getInstance()->fg_green->foreground,
Theme::getInstance()->fg_green->background};
Text text_track{
{0 * 8, 3 * 16, 30 * 8, 16}};
{0 * 8, 3 * 16, screen_width, 16}};
NewButton button_prev{
{2 * 8, 4 * 16, 4 * 8, 2 * 16},
+2 -2
View File
@@ -122,10 +122,10 @@ class POCSAGTXView : public View {
}};
Text text_message{
{0 * 8, 16 * 8, 30 * 8, 16},
{0 * 8, 16 * 8, screen_width, 16},
""};
Text text_message_l2{
{0 * 8, 18 * 8, 30 * 8, 16},
{0 * 8, 18 * 8, screen_width, 16},
""};
Button button_message{
+1 -1
View File
@@ -160,7 +160,7 @@ class RDSView : public View {
void start_tx();
Rect view_rect = {0, 8 * 8, 240, 192};
Rect view_rect = {0, 8 * 8, screen_width, 192};
RDSPSNView view_PSN{nav_, view_rect};
RDSRadioTextView view_radiotext{nav_, view_rect};
+6 -6
View File
@@ -335,7 +335,7 @@ ReconView::ReconView(NavigationView& nav)
tx_view.hidden(true);
// set record View
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
record_view = std::make_unique<RecordView>(Rect{0, 0, screen_width, 1 * 16},
u"AUTO_AUDIO", audio_dir,
RecordView::FileType::WAV, 4096, 4);
record_view->set_filename_date_frequency(true);
@@ -1164,18 +1164,18 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
}
if (new_mod == SPEC_MODULATION) {
if (persistent_memory::recon_repeat_recorded()) {
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
record_view = std::make_unique<RecordView>(Rect{0, 0, screen_width, 1 * 16},
u"RECON_REPEAT.C16", captures_dir,
RecordView::FileType::RawS16, 16384, 3);
record_view->set_filename_as_is(true);
} else {
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
record_view = std::make_unique<RecordView>(Rect{0, 0, screen_width, 1 * 16},
u"AUTO_RAW", captures_dir,
RecordView::FileType::RawS16, 16384, 3);
record_view->set_filename_date_frequency(true);
}
} else {
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
record_view = std::make_unique<RecordView>(Rect{0, 0, screen_width, 1 * 16},
u"AUTO_AUDIO", audio_dir,
RecordView::FileType::WAV, 4096, 4);
record_view->set_filename_date_frequency(true);
@@ -1404,8 +1404,8 @@ void ReconView::start_repeat() {
std::string delay_message = "TX DELAY: " + to_string_dec_uint(delay) + "s";
// update display information
p.fill_rectangle({0, (SCREEN_H / 2) - 16, SCREEN_W, 64}, Theme::getInstance()->fg_light->foreground);
p.draw_string({(SCREEN_W / 2) - 7 * 8, SCREEN_H / 2}, *Theme::getInstance()->fg_red, delay_message);
p.fill_rectangle({0, (screen_height / 2) - 16, screen_width, 64}, Theme::getInstance()->fg_light->foreground);
p.draw_string({(screen_width / 2) - 7 * 8, screen_height / 2}, *Theme::getInstance()->fg_red, delay_message);
// sleep 1 second
chThdSleepMilliseconds(1000);
+7 -7
View File
@@ -237,15 +237,15 @@ class ReconView : public View {
Text file_name{
// show file used
{0, 1 * 16, SCREEN_W, 16},
{0, 1 * 16, screen_width, 16},
};
Text desc_cycle{
{0, 2 * 16, SCREEN_W, 16},
{0, 2 * 16, screen_width, 16},
};
RSSI rssi{
{0 * 16, 3 * 16 + 2, SCREEN_W - 8 * 8 + 4, 12},
{0 * 16, 3 * 16 + 2, screen_width - 8 * 8 + 4, 12},
};
ButtonWithEncoder text_cycle{
@@ -285,15 +285,15 @@ class ReconView : public View {
// Button can be RECON or SCANNER
Button button_scanner_mode{
{SCREEN_W - 7 * 8, 3 * 16, 7 * 8, 28},
{screen_width - 7 * 8, 3 * 16, 7 * 8, 28},
"RECON"};
Button button_loop_config{
{SCREEN_W - 7 * 8, 5 * 16, 7 * 8, 28},
{screen_width - 7 * 8, 5 * 16, 7 * 8, 28},
"[LOOP]"};
Button button_config{
{SCREEN_W - 7 * 8, 7 * 16, 7 * 8, 28},
{screen_width - 7 * 8, 7 * 16, 7 * 8, 28},
"CONFIG"};
ButtonWithEncoder button_manual_start{
@@ -391,7 +391,7 @@ class ReconView : public View {
"<REMOVE>"};
ProgressBar progressbar{
{0 * 8, SCREEN_H / 2 - 16, SCREEN_W, 32}};
{0 * 8, screen_height / 2 - 16, screen_width, 32}};
TransmitterView2 tx_view{
{11 * 8, 2 * 16},
@@ -57,10 +57,6 @@
#define RECON_MIN_LOCK_DURATION 100 // have to be >= and a multiple of STATS_UPDATE_INTERVAL
#define RECON_DEF_WAIT_DURATION 1000 // will be incremented/decremented by STATS_UPDATE_INTERVAL steps
// screen size helper
#define SCREEN_W 240
// #define SCREEN_H 320
// recon settings nb params
#define RECON_SETTINGS_NB_PARAMS 7
@@ -219,7 +215,7 @@ class ReconSetupView : public View {
std::string input_file{"RECON"};
std::string output_file{"RECON_RESULTS"};
Rect view_rect{0, 3 * 8, SCREEN_W, 230};
Rect view_rect{0, 3 * 8, screen_width, 230};
ReconSetupViewMain viewMain{nav_, view_rect, input_file, output_file};
ReconSetupViewMore viewMore{nav_, view_rect};
+5 -1
View File
@@ -56,6 +56,10 @@ SearchView::SearchView(
: nav_(nav) {
baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
if (!gradient.load_file(default_gradient_file)) {
gradient.set_default();
}
add_children({&labels,
&field_frequency_min,
&field_frequency_max,
@@ -290,7 +294,7 @@ void SearchView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
power = spectrum.db[bin - 128];
}
add_spectrum_pixel(spectrum_rgb3_lut[power]);
add_spectrum_pixel(gradient.lut[power]);
mean_acc += power;
if (power > max_power) {
+2 -1
View File
@@ -24,7 +24,7 @@
#include "receiver_model.hpp"
#include "recent_entries.hpp"
#include "radio_state.hpp"
#include "spectrum_color_lut.hpp"
#include "gradient.hpp"
#include "ui_receiver.hpp"
namespace ui {
@@ -88,6 +88,7 @@ class SearchView : public View {
private:
NavigationView& nav_;
Gradient gradient{};
RxRadioState radio_state_{
100'000'000 /* frequency */,
2500000 /* bandwidth */,
+7 -8
View File
@@ -759,7 +759,7 @@ AppSettingsView::AppSettingsView(
add_children({&labels,
&menu_view});
menu_view.set_parent_rect({0, 3 * 8, 240, 33 * 8});
menu_view.set_parent_rect({0, 3 * 8, screen_width, 33 * 8});
ensure_directory(settings_dir);
@@ -810,30 +810,29 @@ SetDisplayView::SetDisplayView(NavigationView& nav) {
&field_fake_brightness,
&button_save,
&button_cancel,
&checkbox_invert_switch,
&checkbox_ips_screen_switch,
&checkbox_brightness_switch});
field_fake_brightness.set_by_value(pmem::fake_brightness_level());
checkbox_brightness_switch.set_value(pmem::apply_fake_brightness());
checkbox_invert_switch.set_value(pmem::config_lcd_inverted_mode());
checkbox_ips_screen_switch.set_value(pmem::config_lcd_normally_black());
button_save.on_select = [&nav, this](Button&) {
pmem::set_apply_fake_brightness(checkbox_brightness_switch.value());
pmem::set_fake_brightness_level(field_fake_brightness.selected_index_value());
if (checkbox_invert_switch.value() != pmem::config_lcd_inverted_mode()) {
display.set_inverted(checkbox_invert_switch.value());
pmem::set_lcd_inverted_mode(checkbox_invert_switch.value());
if (checkbox_ips_screen_switch.value() != pmem::config_lcd_normally_black()) {
pmem::set_lcd_normally_black(checkbox_ips_screen_switch.value());
}
send_system_refresh();
nav.pop();
};
// only enable invert OR fake brightness
checkbox_invert_switch.on_select = [this](Checkbox&, bool v) {
checkbox_ips_screen_switch.on_select = [this](Checkbox&, bool v) {
if (v) checkbox_brightness_switch.set_value(false);
};
checkbox_brightness_switch.on_select = [this](Checkbox&, bool v) {
if (v) checkbox_invert_switch.set_value(false);
if (v) checkbox_ips_screen_switch.set_value(false);
};
button_cancel.on_select = [&nav, this](Button&) {
+8 -5
View File
@@ -713,7 +713,7 @@ class AppSettingsView : public View {
{{0, 4}, "Select file to edit:", Theme::getInstance()->bg_darkest->foreground}};
MenuView menu_view{
{0, 2 * 8, 240, 26 * 8},
{0, 2 * 8, screen_width, 26 * 8},
true};
};
@@ -762,6 +762,8 @@ class SetDisplayView : public View {
{{1 * 8, 2 * 16}, "(has a small performance", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 3 * 16}, "impact when enabled).", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 8 * 16}, "Brightness:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 10 * 16}, "REBOOT TO APPLY SCREEN TYPE", Theme::getInstance()->fg_light->foreground},
};
OptionsField field_fake_brightness{
@@ -776,10 +778,10 @@ class SetDisplayView : public View {
16,
"Enable brightness adjust"};
Checkbox checkbox_invert_switch{
{1 * 8, 10 * 16},
Checkbox checkbox_ips_screen_switch{
{1 * 8, 12 * 16},
23,
"Invert colors (For IPS)"};
"IPS Screen"};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
@@ -945,13 +947,14 @@ class SetThemeView : public View {
OptionsField options{
{0 * 8, 4 * 16},
screen_width / 8,
(size_t)(screen_width / 8),
{
{"Default - Grey", 0},
{"Yellow", 1},
{"Aqua", 2},
{"Green", 3},
{"Red", 4},
{"Dark", 5},
},
true};
+2 -2
View File
@@ -57,7 +57,7 @@ void SIGFRXView::paint(Painter& painter) {
uint8_t i, xp;
// portapack::display.draw_bmp_from_bmp_hex_arr({0, 302-160}, fox_bmp);
portapack::display.fill_rectangle({0, 16, 240, 160 - 16}, Theme::getInstance()->bg_darkest->foreground);
portapack::display.fill_rectangle({0, 16, screen_width, 160 - 16}, Theme::getInstance()->bg_darkest->foreground);
for (i = 0; i < 6; i++) {
xp = sigfrx_marks[i * 3];
painter.draw_string({(ui::Coord)sigfrx_marks[(i * 3) + 1], 144 - 20}, style_white, to_string_dec_uint(sigfrx_marks[(i * 3) + 2]));
@@ -66,7 +66,7 @@ void SIGFRXView::paint(Painter& painter) {
}
void SIGFRXView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
portapack::display.fill_rectangle({0, 144, 240, 4}, Theme::getInstance()->bg_darkest->foreground);
portapack::display.fill_rectangle({0, 144, screen_width, 4}, Theme::getInstance()->bg_darkest->foreground);
uint8_t xmax = 0, imax = 0;
size_t i;
+8 -1
View File
@@ -154,6 +154,13 @@ SigGenView::SigGenView(
};
};
tx_view.on_bandwidth_changed = [this]() {
// we don't protect here with auto_update because other field of tx_view obj isn't protected too
// to remains the design logic same
update_config();
};
tx_view.on_start = [this]() {
start_tx();
tx_view.set_transmitting(true);
@@ -165,4 +172,4 @@ SigGenView::SigGenView(
};
}
} /* namespace ui */
} /* namespace ui */
+1 -1
View File
@@ -120,7 +120,7 @@ class SondeView : public View {
{21 * 8, 0, 6 * 8, 4}};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
{screen_width - 2 * 8, 0 * 16}};
Checkbox check_log{
{22 * 8, 8 * 16},
+2 -2
View File
@@ -58,7 +58,7 @@ void ScreenshotViewer::paint(Painter& painter) {
}
// Screenshots from PNGWriter are all this size.
if (file.size() != 232383) {
if ((file.size() != 232383 && screen_width == 240) || (screen_width == 320 && file.size() != 463743)) {
show_invalid();
return;
}
@@ -66,7 +66,7 @@ void ScreenshotViewer::paint(Painter& painter) {
constexpr size_t read_chunk = 80; // NB: must be a factor of pixel_width.
constexpr size_t buffer_size = sizeof(ColorRGB888) * read_chunk;
uint8_t buffer[buffer_size];
std::array<Color, screen_width> pixel_data;
std::vector<Color> pixel_data(screen_width);
// Seek past all the headers.
file.seek(43);
+1
View File
@@ -84,6 +84,7 @@ void SubGhzDView::focus() {
SubGhzDView::SubGhzDView(NavigationView& nav)
: nav_{nav} {
add_children({&rssi,
&channel,
&field_rf_amp,
&field_lna,
&field_vga,
+4 -1
View File
@@ -129,6 +129,9 @@ class SubGhzDView : public View {
{18 * 8, 0 * 16}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
Channel channel{
{21 * 8, 5, 6 * 8, 4},
};
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
nav_};
@@ -192,7 +195,7 @@ class SubGhzDRecentEntryDetailView : public View {
Text text_id{{6 * 8, 2 * 16, 10 * 8, 16}, "?"};
Console console{
{0, 4 * 16, 240, screen_height - (4 * 16) - 36}};
{0, 4 * 16, screen_width, screen_height - (4 * 16) - 36}};
Labels labels{
{{0 * 8, 0 * 16}, "Type:", Theme::getInstance()->fg_light->foreground},
+2 -2
View File
@@ -99,8 +99,8 @@ void TestView::on_packet(const testapp::Packet& packet) {
display.draw_pixel(Point(cur_x, 4 * 16 + (256 - ((raw_alt - cal_value) / 4))), Color::white());
cur_x++;
if (cur_x >= 240) {
display.fill_rectangle(Rect(0, 5 * 16, 240, 256), Color::black());
if (cur_x >= screen_width) {
display.fill_rectangle(Rect(0, 5 * 16, screen_width, 256), Color::black());
cur_x = 0;
}
+2 -2
View File
@@ -95,10 +95,10 @@ class TestView : public View {
};
Text text_debug_a{
{0 * 8, 4 * 16, 30 * 8, 16},
{0 * 8, 4 * 16, screen_width, 16},
"..."};
Text text_debug_b{
{0 * 8, 5 * 16, 30 * 8, 16},
{0 * 8, 5 * 16, screen_width, 16},
"..."};
Button button_cal{
+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(
@@ -107,6 +107,7 @@ void WeatherView::focus() {
WeatherView::WeatherView(NavigationView& nav)
: nav_{nav} {
add_children({&rssi,
&channel,
&field_rf_amp,
&field_lna,
&field_vga,
@@ -147,9 +147,12 @@ class WeatherView : public View {
{18 * 8, 0 * 16}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
Channel channel{
{21 * 8, 5, 6 * 8, 4},
};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
{screen_width - 2 * 8, 0 * 16}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
+27 -9
View File
@@ -93,9 +93,9 @@ void NBFMConfig::apply(const uint8_t squelch_level) const {
void WFMConfig::apply() const {
const WFMConfigureMessage message{
decim_0, // taps_200k_decim_0 , taps_180k_wfm_decim_0, taps_40k_wfm_decim_0
decim_1, // taps_200k_decim_1 or taps_180k_wfm_decim_1, taps_40k_wfm_decim_1
taps_64_lp_156_198,
decim_0, // Dynamic array 24 taps : taps_200k_decim_0 , taps_180k_wfm_decim_0, taps_80k_wfm_decim_0
decim_1, // Dynamic array 16 taps : taps_200k_decim_1 or taps_180k_wfm_decim_1, taps_80k_wfm_decim_1
taps_64_lp_156_198, // Fixed channel audio filter 15khz
75000,
audio_48k_hpf_30hz_config,
audio_48k_deemph_2122_6_config};
@@ -103,6 +103,18 @@ void WFMConfig::apply() const {
audio::set_rate(audio::Rate::Hz_48000);
}
void WFMAMConfig::apply() const {
const WFMAMConfigureMessage message{
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);
audio::set_rate(audio::Rate::Hz_12000);
}
void set_tone(const uint32_t index, const uint32_t delta, const uint32_t duration) {
shared_memory.bb_data.tones_data.tone_defs[index].delta = delta;
shared_memory.bb_data.tones_data.tone_defs[index].duration = duration;
@@ -174,13 +186,14 @@ void set_nrf(const uint32_t baudrate, const uint32_t word_length, const uint32_t
send_message(&message);
}
void set_fsk(const size_t deviation) {
void set_fsk(const uint8_t samplesPerSymbol, const uint32_t syncWord, const uint8_t syncWordLength, const uint32_t preamble, const uint8_t preambleLength, uint16_t numDataBytes) {
const FSKRxConfigureMessage message{
taps_200k_decim_0,
taps_16k0_decim_1,
taps_11k0_channel,
2,
deviation};
samplesPerSymbol,
syncWord,
syncWordLength,
preamble,
preambleLength,
numDataBytes};
send_message(&message);
}
@@ -310,6 +323,11 @@ void set_wefax_config(uint8_t lpm = 120, uint8_t ioc = 0) {
send_message(&message);
}
void set_noaaapt_config() {
const NoaaAptRxConfigureMessage message{};
send_message(&message);
}
void set_siggen_tone(const uint32_t tone) {
const SigGenToneMessage message{
TONES_F2D(tone, TONES_SAMPLERATE)};
+11 -2
View File
@@ -56,12 +56,20 @@ struct NBFMConfig {
};
struct WFMConfig {
const fir_taps_real<24> decim_0; // To handle both WFM filters , 200k and 40K for NOAA APT
const fir_taps_real<24> decim_0; // To handle all 3 WFM filters , 200k, 180k and 80K-
const fir_taps_real<16> decim_1;
void apply() const;
};
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;
};
void set_tone(const uint32_t index, const uint32_t delta, const uint32_t duration);
void set_tones_config(const uint32_t bw, const uint32_t pre_silence, const uint16_t tone_count, const bool dual_tone, const bool audio_out);
void kill_tone();
@@ -72,7 +80,7 @@ void set_pitch_rssi(int32_t avg, bool enabled);
void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phase_inc_mark, const uint32_t afsk_phase_inc_space, const uint8_t afsk_repeat, const uint32_t afsk_bw, const uint8_t symbol_count);
void kill_afsk();
void set_afsk(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word);
void set_fsk(const size_t deviation);
void set_fsk(const uint32_t samplesPerSymbol, const uint32_t syncWord, const uint8_t syncWordLength, const uint32_t preamble, const uint8_t preambleLength, uint16_t numDataBytes);
void set_aprs(const uint32_t baudrate);
void set_btlerx(uint8_t channel_number);
@@ -93,6 +101,7 @@ void set_siggen_config(const uint32_t bw, const uint32_t shape, const uint32_t d
void set_spectrum_painter_config(const uint16_t width, const uint16_t height, bool update, int32_t bw);
void set_subghzd_config(uint8_t modulation, uint32_t sampling_rate);
void set_wefax_config(uint8_t lpm, uint8_t ioc);
void set_noaaapt_config();
void request_roger_beep();
void request_rssi_beep();
File diff suppressed because it is too large Load Diff
+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__*/
+2 -2
View File
@@ -87,10 +87,10 @@ class ACARSAppView : public View {
true};
Console console{
{0, 3 * 16, 240, 256}};
{0, 3 * 16, screen_width, 256}};
AudioVolumeField field_volume{
{28 * 8, 1 * 16}};
{screen_width - 2 * 8, 1 * 16}};
std::unique_ptr<ACARSLogger> logger{};
+1 -1
View File
@@ -218,7 +218,7 @@ class ADSBTxView : public View {
void start_tx();
void generate_frames();
Rect view_rect = {0, 7 * 8, 240, 192};
Rect view_rect = {0, 7 * 8, screen_width, 192};
ADSBPositionView view_position{nav_, view_rect};
ADSBCallsignView view_callsign{nav_, view_rect};
+2 -2
View File
@@ -84,7 +84,7 @@ class AFSKRxView : public View {
{21 * 8, 5, 6 * 8, 4}};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
{screen_width - 2 * 8, 0 * 16}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
@@ -105,7 +105,7 @@ class AFSKRxView : public View {
LanguageHelper::currentMessages[LANG_MODEM_SETUP]};
Console console{
{0, 4 * 16, 240, screen_width}};
{0, 4 * 16, screen_width, screen_width}};
void on_data_afsk(const AFSKDataMessage& message);
+1 -1
View File
@@ -58,7 +58,7 @@ class AnalogTvView : public View {
app_settings::SettingsManager settings_{
"rx_tv", app_settings::Mode::RX};
const Rect options_view_rect{0 * 8, 1 * 16, 30 * 8, 1 * 16};
const Rect options_view_rect{0 * 8, 1 * 16, screen_width, 1 * 16};
const Rect nbfm_view_rect{0 * 8, 1 * 16, 18 * 8, 1 * 16};
RSSI rssi{
@@ -86,7 +86,7 @@ class WhipCalcView : public View {
{13 * 8, 4 * 16, 10 * 16, 16},
"-"};
Console console{
{0, 6 * 16, 240, 160}};
{0, 6 * 16, screen_width, 160}};
Button button_exit{
{72, 17 * 16, 96, 32},
+55
View File
@@ -0,0 +1,55 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
#include "ui.hpp"
#include "ui_battleship.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::battleship {
void initialize_app(ui::NavigationView& nav) {
nav.push<BattleshipView>();
}
} // namespace ui::external_app::battleship
extern "C" {
__attribute__((section(".external_app.app_battleship.application_information"), used)) application_information_t _application_information_battleship = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::battleship::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "Battleship",
/*.bitmap_data = */ {
// Pirate galleon - 16x16
0x80, 0x00, // ........#.......
0x80, 0x00, // ........#.......
0x80, 0x00, // ........#.......
0xC0, 0x01, // .......###......
0xE0, 0x03, // ......#####.....
0xF0, 0x07, // .....#######....
0xF8, 0x0F, // ....#########...
0xFC, 0x1F, // ...###########..
0xFE, 0x3F, // ..#############.
0x00, 0x01, // .......#........
0x00, 0x01, // .......#........
0x00, 0x01, // .......#........
0xFC, 0x3F, // ..############..
0xFE, 0x7F, // .##############.
0xFF, 0xFF, // ################
0xFC, 0x3F, // ..############..
},
/*.icon_color = */ ui::Color::blue().v,
/*.menu_location = */ app_location_t::GAMES,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = */ {'P', 'P', 'O', '2'}, // Use POCSAG2 baseband (larger than FSKTX)
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,250 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
#ifndef __UI_BATTLESHIP_H__
#define __UI_BATTLESHIP_H__
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_transmitter.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "audio.hpp"
#include "message.hpp"
#include "pocsag.hpp"
#include <array>
namespace ui::external_app::battleship {
constexpr uint8_t GRID_SIZE = 10;
constexpr uint8_t CELL_SIZE = 24;
constexpr uint8_t GRID_OFFSET_X = 0;
constexpr uint8_t GRID_OFFSET_Y = 32;
enum class ShipType : uint8_t {
CARRIER = 5,
BATTLESHIP = 4,
CRUISER = 3,
SUBMARINE = 3,
DESTROYER = 2
};
enum class GameState : uint8_t {
MENU,
PLACING_SHIPS,
WAITING_FOR_OPPONENT,
MY_TURN,
OPPONENT_TURN,
GAME_OVER
};
enum class CellState : uint8_t {
EMPTY,
SHIP,
HIT,
MISS,
SUNK
};
enum class MessageType : uint8_t {
READY,
SHOT,
HIT,
MISS,
SUNK,
WIN
};
struct Ship {
ShipType type;
uint8_t x;
uint8_t y;
bool horizontal;
uint8_t hits;
bool placed;
uint8_t size() const {
return static_cast<uint8_t>(type);
}
bool is_sunk() const {
return hits >= size();
}
};
struct GameMessage {
MessageType type;
uint8_t x;
uint8_t y;
};
class BattleshipView : public View {
public:
BattleshipView(NavigationView& nav);
~BattleshipView();
void focus() override;
void paint(Painter& painter) override;
bool on_touch(const TouchEvent event) override;
bool on_encoder(const EncoderEvent delta) override;
bool on_key(const KeyEvent key) override;
std::string title() const override { return "Battleship"; }
private:
NavigationView& nav_;
RxRadioState rx_radio_state_{
433920000 /* frequency */,
1750000 /* bandwidth */,
2280000 /* sampling rate */
};
TxRadioState tx_radio_state_{
433920000 /* frequency */,
1750000 /* bandwidth */,
2280000 /* sampling rate */
};
// Settings
bool sound_enabled{true};
bool rf_amp_enabled{false};
uint8_t lna_gain{24};
uint8_t vga_gain{24};
uint8_t tx_gain{35};
app_settings::SettingsManager settings_{
"battleship",
app_settings::Mode::RX_TX,
{{"rx_freq"sv, &rx_frequency},
{"tx_freq"sv, &tx_frequency},
{"rf_amp"sv, &rf_amp_enabled}}};
GameState game_state{GameState::MENU};
bool is_red_team{false};
bool opponent_ready{false};
uint8_t wins{0};
uint8_t losses{0};
std::array<std::array<CellState, GRID_SIZE>, GRID_SIZE> my_grid{};
std::array<std::array<CellState, GRID_SIZE>, GRID_SIZE> enemy_grid{};
std::string current_status{"Choose your team!"};
std::string current_score{"W:0 L:0"};
std::array<Ship, 5> my_ships{};
uint8_t current_ship_index{0};
bool placing_horizontal{true};
uint8_t ships_remaining{5};
uint8_t enemy_ships_remaining{5};
uint8_t cursor_x{0};
uint8_t cursor_y{0};
uint8_t target_x{0};
uint8_t target_y{0};
bool touch_enabled{true};
uint32_t tx_frequency{433920000};
uint32_t rx_frequency{433920000};
bool is_transmitting{false};
// POCSAG decoding state
pocsag::EccContainer ecc{};
pocsag::POCSAGState pocsag_state{&ecc};
uint32_t last_address{0};
// UI Elements - Menu/Settings Screen
Text text_title{{60, 2, 120, 24}, "BATTLESHIP"};
Text text_subtitle{{40, 20, 160, 16}, "Naval Combat Game"};
Rectangle rect_radio_settings{{12, 40, 216, 118}, Color::dark_grey()};
Text label_radio{{17, 45, 100, 16}, "RADIO SETUP"};
ButtonWithEncoder button_frequency{{17, 65, 11 * 8, 20}, ""};
// Radio controls
Text label_rf_amp{{17, 90, 35, 16}, "AMP:"};
Checkbox checkbox_rf_amp{{55, 90}, 3, "", false};
Text label_lna{{17, 118, 30, 16}, "LNA:"};
NumberField field_lna{{50, 118}, 2, {0, 40}, 8, ' '};
Text label_vga{{90, 118, 30, 16}, "VGA:"};
NumberField field_vga{{125, 118}, 2, {0, 62}, 8, ' '};
Text label_tx_gain{{155, 118, 25, 16}, "TX:"};
NumberField field_tx_gain{{185, 118}, 2, {0, 47}, 8, ' '};
Rectangle rect_audio_settings{{12, 164, 216, 45}, Color::dark_grey()};
Text label_audio{{17, 169, 80, 16}, "AUDIO"};
Checkbox checkbox_sound{{17, 187}, 8, "Sound On", true};
Text label_volume{{110, 187, 50, 16}, "Volume:"};
AudioVolumeField field_volume{{165, 187}};
Rectangle rect_team_selection{{12, 217, 216, 75}, Color::dark_grey()};
Text label_team{{17, 222, 110, 16}, "SELECT TEAM"};
Button button_red_team{{25, 242, 85, 45}, "RED\nTEAM"};
Button button_blue_team{{130, 242, 85, 45}, "BLUE\nTEAM"};
// In-game UI elements
RSSI rssi{{21 * 8, 0, 6 * 8, 4}};
Text text_status{{10, 16, 220, 16}, ""};
Text text_score{{170, 16, 60, 16}, ""};
Button button_rotate{{10, 265, 65, 32}, "Rotate"};
Button button_place{{82, 265, 65, 32}, "Place"};
Button button_fire{{82, 265, 65, 32}, "Fire!"};
Button button_menu{{155, 265, 65, 32}, "Menu"};
// Methods
void init_game();
void reset_game();
void start_team(bool red);
void setup_ships();
void draw_menu_screen(Painter& painter);
void draw_grid(Painter& painter, uint8_t grid_x, uint8_t grid_y, const std::array<std::array<CellState, GRID_SIZE>, GRID_SIZE>& grid, bool show_ships, bool is_offense_grid = false);
void draw_cell(Painter& painter, uint8_t cell_x, uint8_t cell_y, CellState state, bool show_ships, bool is_offense_grid, bool is_cursor);
void draw_ship_preview(Painter& painter);
void place_ship();
bool can_place_ship(uint8_t x, uint8_t y, uint8_t size, bool horizontal);
void fire_at_position();
void process_shot(uint8_t x, uint8_t y);
void update_score();
bool is_cursor_at(uint8_t x, uint8_t y, bool is_offense_grid);
void send_message(const GameMessage& msg);
void process_message(const std::string& message);
bool parse_coords(const std::string& coords, uint8_t& x, uint8_t& y);
void mark_ship_sunk(uint8_t x, uint8_t y, std::array<std::array<CellState, GRID_SIZE>, GRID_SIZE>& grid);
void configure_radio_tx();
void configure_radio_rx();
MessageHandlerRegistration message_handler_packet{
Message::ID::POCSAGPacket,
[this](Message* const p) {
const auto message = static_cast<const POCSAGPacketMessage*>(p);
on_pocsag_packet(message);
}};
MessageHandlerRegistration message_handler_tx_progress{
Message::ID::TXProgress,
[this](const Message* const p) {
const auto message = static_cast<const TXProgressMessage*>(p);
on_tx_progress(message->progress, message->done);
}};
void on_pocsag_packet(const POCSAGPacketMessage* message);
void on_tx_progress(const uint32_t progress, const bool done);
};
} // namespace ui::external_app::battleship
#endif
+53
View File
@@ -0,0 +1,53 @@
/*
* Blackjack Game for Portapack Mayhem
* Ported / Enhanced / Graphically made awesome by RocketGod (https://betaskynet.com)
* Based on BlackJack 83 for TI Calculator by Harper Maddox (was written in Assembly)
*/
#include "ui.hpp"
#include "ui_blackjack.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::blackjack {
void initialize_app(ui::NavigationView& nav) {
nav.push<BlackjackView>();
}
} // namespace ui::external_app::blackjack
extern "C" {
__attribute__((section(".external_app.app_blackjack.application_information"), used)) application_information_t _application_information_blackjack = {
(uint8_t*)0x00000000,
ui::external_app::blackjack::initialize_app,
CURRENT_HEADER_VERSION,
VERSION_MD5,
"Blackjack",
{
// Diamond icon 16x16
0x00, 0x00, // ................
0x80, 0x01, // .......##.......
0xC0, 0x03, // ......####......
0xE0, 0x07, // .....######.....
0xF0, 0x0F, // ....########....
0xF8, 0x1F, // ...##########...
0xFC, 0x3F, // ..############..
0xFE, 0x7F, // .##############.
0xFE, 0x7F, // .##############.
0xFC, 0x3F, // ..############..
0xF8, 0x1F, // ...##########...
0xF0, 0x0F, // ....########....
0xE0, 0x07, // .....######.....
0xC0, 0x03, // ......####......
0x80, 0x01, // .......##.......
0x00, 0x00, // ................
},
ui::Color::red().v, // Red color for diamonds
app_location_t::GAMES,
-1,
{0, 0, 0, 0},
0x00000000,
};
}
+727
View File
@@ -0,0 +1,727 @@
/*
* Blackjack Game for Portapack Mayhem
* Ported / Enhanced / Graphically made awesome by RocketGod (https://betaskynet.com)
* Based on BlackJack 83 for TI Calculator by Harper Maddox (was written in Assembly)
*/
#include "ui_blackjack.hpp"
namespace ui::external_app::blackjack {
// Global variables
static BlackjackView* current_instance = nullptr;
static Callback game_update_callback = nullptr;
static uint32_t game_update_timeout = 0;
static uint32_t game_update_counter = 0;
static Painter painter;
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;
}
void game_timer_check() {
if (current_instance) {
current_instance->update_game();
}
}
BlackjackView::BlackjackView(NavigationView& nav)
: nav_{nav}, game_timer{} {
add_children({&dummy});
current_instance = this;
game_timer.attach(&game_timer_check, 1.0 / 60.0);
}
BlackjackView::~BlackjackView() {
current_instance = nullptr;
}
void BlackjackView::on_show() {
draw_menu_static();
}
void BlackjackView::paint(Painter& painter) {
(void)painter;
if (!initialized) {
initialized = true;
std::srand(LPC_RTC->CTIME0);
init_deck();
}
}
void BlackjackView::frame_sync() {
check_game_timer();
set_dirty();
}
void BlackjackView::clear_screen() {
painter.fill_rectangle({0, 0, 240, 320}, Color::black());
}
void BlackjackView::init_deck() {
// Initialize deck with card values 0-51
// 0-12 = Spades (A-K), 13-25 = Hearts, 26-38 = Diamonds, 39-51 = Clubs
for (int i = 0; i < 52; i++) {
deck[i] = i;
}
shuffle_deck();
}
void BlackjackView::shuffle_deck() {
// Simple shuffle using random swaps
for (int i = 51; i > 0; i--) {
int j = rand() % (i + 1);
uint8_t temp = deck[i];
deck[i] = deck[j];
deck[j] = temp;
}
deck_position = 0;
}
uint8_t BlackjackView::draw_card() {
if (deck_position >= 52) {
shuffle_deck();
}
return deck[deck_position++];
}
int BlackjackView::get_card_value(uint8_t card) {
int value = (card % 13) + 1; // 1-13
if (value > 10) value = 10; // Face cards worth 10
return value;
}
uint8_t BlackjackView::get_card_suit(uint8_t card) {
return card / 13; // 0=Spades, 1=Hearts, 2=Diamonds, 3=Clubs
}
std::string BlackjackView::get_card_string(uint8_t card) {
int value = (card % 13) + 1;
std::string result;
if (value == 1)
result = "A";
else if (value == 11)
result = "J";
else if (value == 12)
result = "Q";
else if (value == 13)
result = "K";
else if (value == 10)
result = "10"; // Special case for 10
else
result = std::to_string(value);
return result;
}
int BlackjackView::calculate_hand_value(uint8_t* cards, uint8_t count) {
int value = 0;
int aces = 0;
for (uint8_t i = 0; i < count; i++) {
int card_val = get_card_value(cards[i]);
if (card_val == 1) {
aces++;
value += 1;
} else {
value += card_val;
}
}
// Convert aces from 1 to 11 if beneficial
while (aces > 0 && value + 10 <= 21) {
value += 10;
aces--;
}
return value;
}
void BlackjackView::deal_cards() {
// Clear hands
player_card_count = 0;
dealer_card_count = 0;
// Deal initial cards
player_cards[player_card_count++] = draw_card();
dealer_cards[dealer_card_count++] = draw_card();
player_cards[player_card_count++] = draw_card();
dealer_cards[dealer_card_count++] = draw_card();
dealer_hidden = true;
game_state = GameState::PLAYING;
}
void BlackjackView::player_hit() {
if (player_card_count < MAX_CARDS_IN_HAND) {
player_cards[player_card_count++] = draw_card();
int value = calculate_hand_value(player_cards, player_card_count);
if (value > 21) {
// Bust!
cash = (cash >= bet) ? cash - bet : 0;
losses++;
game_state = GameState::GAME_OVER;
}
}
}
void BlackjackView::player_stay() {
dealer_hidden = false;
game_state = GameState::DEALER_TURN;
dealer_timer = 0;
}
void BlackjackView::dealer_turn() {
int dealer_value = calculate_hand_value(dealer_cards, dealer_card_count);
if (dealer_value < 17 && dealer_card_count < MAX_CARDS_IN_HAND) {
dealer_cards[dealer_card_count++] = draw_card();
} else {
check_game_over();
}
}
void BlackjackView::check_game_over() {
int player_value = calculate_hand_value(player_cards, player_card_count);
int dealer_value = calculate_hand_value(dealer_cards, dealer_card_count);
if (player_value > 21) {
// Player bust (already handled)
} else if (dealer_value > 21) {
// Dealer bust - player wins
cash += bet;
wins++;
} else if (player_value > dealer_value) {
// Player wins
cash += bet;
wins++;
} else if (player_value < dealer_value) {
// Dealer wins
cash = (cash >= bet) ? cash - bet : 0;
losses++;
}
// Else it's a tie - no money changes hands
if (cash > high_score) {
high_score = cash;
}
game_state = GameState::GAME_OVER;
}
void BlackjackView::update_game() {
switch (game_state) {
case GameState::MENU:
draw_menu();
break;
case GameState::BETTING:
draw_betting();
break;
case GameState::PLAYING:
draw_game();
break;
case GameState::DEALER_TURN:
if (++dealer_timer >= 60) { // 1 second delay
dealer_timer = 0;
dealer_turn();
}
draw_game();
break;
case GameState::GAME_OVER:
draw_game();
break;
case GameState::STATS:
draw_stats();
break;
}
}
void BlackjackView::draw_menu_static() {
clear_screen();
auto style_title = *ui::Theme::getInstance()->fg_green;
auto style_text = *ui::Theme::getInstance()->fg_light;
auto style_rules = *ui::Theme::getInstance()->fg_cyan;
painter.draw_string({84, 20}, style_title, "BLACKJACK");
// Draw rules
painter.draw_string({70, 55}, style_rules, "-- RULES --");
painter.draw_string({61, 75}, style_text, "Get close to 21");
painter.draw_string({61, 90}, style_text, "without going over");
painter.draw_string({61, 110}, style_text, "Dealer hits on 16");
painter.draw_string({61, 125}, style_text, "Dealer stays on 17");
painter.draw_string({61, 145}, style_text, "Blackjack pays 1:1");
// Controls
painter.draw_string({65, 175}, style_rules, "-- CONTROLS --");
painter.draw_string({61, 195}, style_text, "SELECT: Start/Hit");
painter.draw_string({61, 210}, style_text, "LEFT: Stats");
painter.draw_string({61, 225}, style_text, "RIGHT: Exit/Stay");
// Draw high score
painter.draw_string({61, 250}, style_text, "High Score: $" + std::to_string(high_score));
}
void BlackjackView::draw_menu() {
// Only handle the flashing text animation
if (++blink_counter >= 30) {
blink_counter = 0;
blink_state = !blink_state;
if (blink_state) {
auto style = *ui::Theme::getInstance()->fg_yellow;
painter.draw_string({55, 280}, style, "* PRESS SELECT *");
} else {
// Clear just the text area
painter.fill_rectangle({55, 278, 130, 20}, Color::black());
}
}
}
void BlackjackView::draw_stats() {
clear_screen();
auto style_title = *ui::Theme::getInstance()->fg_green;
auto style_text = *ui::Theme::getInstance()->fg_light;
auto style_value = *ui::Theme::getInstance()->fg_yellow;
painter.draw_string({75, 30}, style_title, "STATISTICS");
painter.draw_string({30, 80}, style_text, "Wins:");
painter.draw_string({150, 80}, style_value, std::to_string(wins));
painter.draw_string({30, 100}, style_text, "Losses:");
painter.draw_string({150, 100}, style_value, std::to_string(losses));
// Win percentage
uint32_t total = wins + losses;
if (total > 0) {
uint32_t win_pct = (wins * 100) / total;
painter.draw_string({30, 120}, style_text, "Win %:");
painter.draw_string({150, 120}, style_value, std::to_string(win_pct) + "%");
}
painter.draw_string({30, 160}, style_text, "High Score:");
painter.draw_string({150, 160}, style_value, "$" + std::to_string(high_score));
painter.draw_string({30, 180}, style_text, "Cash:");
painter.draw_string({150, 180}, style_value, "$" + std::to_string(cash));
painter.draw_string({40, 250}, style_text, "SELECT: Back");
}
void BlackjackView::draw_betting() {
static uint32_t last_bet = 0;
static bool betting_drawn = false;
if (!betting_drawn) {
clear_screen();
auto style_title = *ui::Theme::getInstance()->fg_green;
auto style_text = *ui::Theme::getInstance()->fg_light;
painter.draw_string({70, 40}, style_title, "PLACE BET");
painter.draw_string({30, 80}, style_text, "Cash: $" + std::to_string(cash));
painter.draw_string({30, 140}, style_text, "ENCODER: +/- $10");
painter.draw_string({30, 160}, style_text, "SELECT: Deal");
betting_drawn = true;
last_bet = 0;
}
// Update bet display
if (bet != last_bet) {
painter.fill_rectangle({30, 110, 150, 20}, Color::black());
painter.draw_string({30, 110}, *ui::Theme::getInstance()->fg_yellow, "Bet: $" + std::to_string(bet));
last_bet = bet;
}
}
void BlackjackView::draw_game() {
static int last_player_count = -1;
static int last_dealer_count = -1;
static GameState last_game_state = GameState::MENU;
static uint32_t last_bet = 0;
// Clear and redraw if hands changed or game state changed
if (player_card_count != last_player_count || dealer_card_count != last_dealer_count || game_state != last_game_state) {
clear_screen();
auto style = *ui::Theme::getInstance()->fg_green;
painter.draw_string({10, 10}, style, "Cash: $" + std::to_string(cash));
painter.draw_string({140, 10}, style, "Bet: $" + std::to_string(bet));
// Draw dealer hand with value next to label
auto style_value = *ui::Theme::getInstance()->fg_yellow;
painter.draw_string({10, 45}, *ui::Theme::getInstance()->fg_light, "Dealer:");
if (!dealer_hidden || game_state == GameState::GAME_OVER) {
int dealer_value = calculate_hand_value(dealer_cards, dealer_card_count);
painter.draw_string({70, 45}, style_value, "(" + std::to_string(dealer_value) + ")");
}
draw_hand(10, 65, dealer_cards, dealer_card_count, true);
// Draw player hand with value next to label
painter.draw_string({10, 165}, *ui::Theme::getInstance()->fg_light, "You:");
int player_value = calculate_hand_value(player_cards, player_card_count);
painter.draw_string({50, 165}, style_value, "(" + std::to_string(player_value) + ")");
draw_hand(10, 185, player_cards, player_card_count, false);
// Draw controls or result
if (game_state == GameState::PLAYING) {
auto style_text = *ui::Theme::getInstance()->fg_light;
painter.draw_string({30, 290}, style_text, "SELECT: Hit");
painter.draw_string({130, 290}, style_text, "RIGHT: Stay");
} else if (game_state == GameState::GAME_OVER) {
const Style* style_result = ui::Theme::getInstance()->fg_yellow;
std::string result;
if (player_value > 21) {
result = "BUST! You Lose";
style_result = ui::Theme::getInstance()->fg_red;
} else if (calculate_hand_value(dealer_cards, dealer_card_count) > 21) {
result = "Dealer Bust! Win!";
style_result = ui::Theme::getInstance()->fg_green;
} else if (player_value > calculate_hand_value(dealer_cards, dealer_card_count)) {
result = "You Win!";
style_result = ui::Theme::getInstance()->fg_green;
} else if (player_value < calculate_hand_value(dealer_cards, dealer_card_count)) {
result = "You Lose";
style_result = ui::Theme::getInstance()->fg_red;
} else {
result = "Push (Tie)";
}
// Draw result
painter.draw_string({60, 270}, *style_result, result);
// Draw compact bet selector in top right area
auto style_bet = *ui::Theme::getInstance()->fg_cyan;
painter.draw_string({140, 25}, style_bet, "Next: $" + std::to_string(bet));
// Show controls
painter.draw_string({10, 290}, *ui::Theme::getInstance()->fg_light, "SELECT: Deal ENC: +/-");
}
last_player_count = player_card_count;
last_dealer_count = dealer_card_count;
last_game_state = game_state;
last_bet = bet;
} else if (game_state == GameState::GAME_OVER && bet != last_bet) {
// Only update the bet display when it changes
painter.fill_rectangle({140, 25, 90, 16}, Color::black());
painter.draw_string({140, 25}, *ui::Theme::getInstance()->fg_cyan, "Next: $" + std::to_string(bet));
last_bet = bet;
}
}
void BlackjackView::draw_suit_symbol(int x, int y, uint8_t suit, Color color, bool large) {
if (large) {
// Large suit symbols for center of card
switch (suit) {
case 0: // Spades
// Top curve
painter.fill_rectangle({x + 9, y + 4, 2, 2}, color);
painter.fill_rectangle({x + 8, y + 5, 4, 2}, color);
painter.fill_rectangle({x + 7, y + 6, 6, 2}, color);
painter.fill_rectangle({x + 6, y + 7, 8, 2}, color);
painter.fill_rectangle({x + 5, y + 8, 10, 2}, color);
painter.fill_rectangle({x + 4, y + 9, 12, 2}, color);
painter.fill_rectangle({x + 3, y + 10, 14, 2}, color);
painter.fill_rectangle({x + 2, y + 11, 16, 2}, color);
painter.fill_rectangle({x + 1, y + 12, 18, 2}, color);
painter.fill_rectangle({x + 0, y + 13, 20, 3}, color);
painter.fill_rectangle({x + 1, y + 16, 18, 2}, color);
painter.fill_rectangle({x + 2, y + 17, 16, 1}, color);
painter.fill_rectangle({x + 3, y + 18, 14, 1}, color);
// Stem
painter.fill_rectangle({x + 9, y + 19, 2, 4}, color);
painter.fill_rectangle({x + 8, y + 22, 4, 1}, color);
painter.fill_rectangle({x + 7, y + 23, 6, 1}, color);
painter.fill_rectangle({x + 6, y + 24, 8, 1}, color);
break;
case 1: // Hearts
// Left bump
painter.fill_rectangle({x + 3, y + 5, 4, 2}, color);
painter.fill_rectangle({x + 2, y + 6, 6, 2}, color);
painter.fill_rectangle({x + 1, y + 7, 8, 3}, color);
painter.fill_rectangle({x + 0, y + 9, 9, 3}, color);
// Right bump
painter.fill_rectangle({x + 13, y + 5, 4, 2}, color);
painter.fill_rectangle({x + 12, y + 6, 6, 2}, color);
painter.fill_rectangle({x + 11, y + 7, 8, 3}, color);
painter.fill_rectangle({x + 11, y + 9, 9, 3}, color);
// Body
painter.fill_rectangle({x + 1, y + 11, 18, 3}, color);
painter.fill_rectangle({x + 2, y + 14, 16, 2}, color);
painter.fill_rectangle({x + 3, y + 16, 14, 2}, color);
painter.fill_rectangle({x + 4, y + 18, 12, 1}, color);
painter.fill_rectangle({x + 5, y + 19, 10, 1}, color);
painter.fill_rectangle({x + 6, y + 20, 8, 1}, color);
painter.fill_rectangle({x + 7, y + 21, 6, 1}, color);
painter.fill_rectangle({x + 8, y + 22, 4, 1}, color);
painter.fill_rectangle({x + 9, y + 23, 2, 1}, color);
break;
case 2: // Diamonds
painter.fill_rectangle({x + 10, y + 3, 1, 1}, color);
painter.fill_rectangle({x + 9, y + 4, 3, 1}, color);
painter.fill_rectangle({x + 8, y + 5, 5, 1}, color);
painter.fill_rectangle({x + 7, y + 6, 7, 1}, color);
painter.fill_rectangle({x + 6, y + 7, 9, 1}, color);
painter.fill_rectangle({x + 5, y + 8, 11, 1}, color);
painter.fill_rectangle({x + 4, y + 9, 13, 1}, color);
painter.fill_rectangle({x + 3, y + 10, 15, 1}, color);
painter.fill_rectangle({x + 2, y + 11, 17, 1}, color);
painter.fill_rectangle({x + 1, y + 12, 19, 1}, color);
painter.fill_rectangle({x + 0, y + 13, 21, 1}, color);
painter.fill_rectangle({x + 1, y + 14, 19, 1}, color);
painter.fill_rectangle({x + 2, y + 15, 17, 1}, color);
painter.fill_rectangle({x + 3, y + 16, 15, 1}, color);
painter.fill_rectangle({x + 4, y + 17, 13, 1}, color);
painter.fill_rectangle({x + 5, y + 18, 11, 1}, color);
painter.fill_rectangle({x + 6, y + 19, 9, 1}, color);
painter.fill_rectangle({x + 7, y + 20, 7, 1}, color);
painter.fill_rectangle({x + 8, y + 21, 5, 1}, color);
painter.fill_rectangle({x + 9, y + 22, 3, 1}, color);
painter.fill_rectangle({x + 10, y + 23, 1, 1}, color);
break;
case 3: // Clubs
// Center circle
painter.fill_rectangle({x + 8, y + 8, 5, 1}, color);
painter.fill_rectangle({x + 7, y + 9, 7, 3}, color);
painter.fill_rectangle({x + 8, y + 12, 5, 1}, color);
// Left circle
painter.fill_rectangle({x + 3, y + 11, 4, 1}, color);
painter.fill_rectangle({x + 2, y + 12, 6, 3}, color);
painter.fill_rectangle({x + 3, y + 15, 4, 1}, color);
// Right circle
painter.fill_rectangle({x + 14, y + 11, 4, 1}, color);
painter.fill_rectangle({x + 13, y + 12, 6, 3}, color);
painter.fill_rectangle({x + 14, y + 15, 4, 1}, color);
// Connect circles
painter.fill_rectangle({x + 6, y + 13, 9, 2}, color);
// Stem
painter.fill_rectangle({x + 9, y + 16, 3, 4}, color);
painter.fill_rectangle({x + 8, y + 19, 5, 1}, color);
painter.fill_rectangle({x + 7, y + 20, 7, 1}, color);
painter.fill_rectangle({x + 6, y + 21, 9, 1}, color);
break;
}
} else {
// Small suit symbols
switch (suit) {
case 0: // Spades - small
painter.fill_rectangle({x + 2, y + 1, 3, 3}, color);
painter.fill_rectangle({x + 1, y + 3, 5, 2}, color);
painter.fill_rectangle({x + 3, y + 5, 1, 2}, color);
break;
case 1: // Hearts - small
painter.fill_rectangle({x + 1, y + 1, 2, 2}, color);
painter.fill_rectangle({x + 4, y + 1, 2, 2}, color);
painter.fill_rectangle({x + 1, y + 2, 5, 2}, color);
painter.fill_rectangle({x + 2, y + 4, 3, 1}, color);
painter.fill_rectangle({x + 3, y + 5, 1, 1}, color);
break;
case 2: // Diamonds - small
painter.fill_rectangle({x + 3, y + 1, 1, 1}, color);
painter.fill_rectangle({x + 2, y + 2, 3, 1}, color);
painter.fill_rectangle({x + 1, y + 3, 5, 1}, color);
painter.fill_rectangle({x + 2, y + 4, 3, 1}, color);
painter.fill_rectangle({x + 3, y + 5, 1, 1}, color);
break;
case 3: // Clubs - small
painter.fill_rectangle({x + 3, y + 1, 2, 2}, color);
painter.fill_rectangle({x + 1, y + 3, 2, 2}, color);
painter.fill_rectangle({x + 4, y + 3, 2, 2}, color);
painter.fill_rectangle({x + 3, y + 5, 2, 2}, color);
break;
}
}
}
void BlackjackView::draw_card(int x, int y, uint8_t card, bool hidden) {
const int width = 60;
const int height = 80;
// Draw card background
painter.fill_rectangle({x, y, width, height}, Color::white());
painter.draw_rectangle({x, y, width, height}, Color::black());
painter.draw_rectangle({x + 1, y + 1, width - 2, height - 2}, Color::grey()); // Inner border
if (hidden) {
// Draw card back pattern - diagonal lines
for (int i = 4; i < width - 4; i += 6) {
for (int j = 4; j < height - 4; j += 6) {
painter.fill_rectangle({x + i, y + j, 3, 3}, Color::blue());
painter.fill_rectangle({x + i + 3, y + j + 3, 3, 3}, Color::red());
}
}
} else {
// Draw card value
uint8_t suit = get_card_suit(card);
Color suit_color = (suit == 1 || suit == 2) ? Color::red() : Color::black();
const auto* base_style = ui::Theme::getInstance()->fg_light;
Style card_style{
.font = base_style->font,
.background = Color::white(),
.foreground = suit_color};
std::string value_str = get_card_string(card);
// Draw value in top-left corner
painter.draw_string({x + 4, y + 4}, card_style, value_str);
// Draw small suit symbol next to value
int suit_x = (value_str == "10") ? x + 20 : x + 12;
draw_suit_symbol(suit_x, y + 4, suit, suit_color, false);
// Draw value in bottom-right corner
int bottom_x = (value_str == "10") ? x + width - 24 : x + width - 16;
painter.draw_string({bottom_x, y + height - 18}, card_style, value_str);
// Draw small suit symbol in bottom-right
draw_suit_symbol(x + width - 10, y + height - 16, suit, suit_color, false);
// Draw large suit symbol in center
draw_suit_symbol(x + 20, y + 28, suit, suit_color, true);
}
}
void BlackjackView::draw_hand(int x, int y, uint8_t* cards, uint8_t count, bool is_dealer) {
// Calculate total width needed
const int card_width = 60;
const int overlap = 40; // Amount of overlap when cards need to fit
const int max_width = 230 - x; // Available width on screen
int spacing;
if (count == 1) {
spacing = 0;
} else if (count == 2) {
spacing = card_width + 5; // Small gap for 2 cards
} else {
// Calculate spacing to fit all cards
int total_overlap_width = card_width + (count - 1) * overlap;
if (total_overlap_width <= max_width) {
spacing = overlap;
} else {
// Need more overlap to fit
spacing = (max_width - card_width) / (count - 1);
}
}
for (uint8_t i = 0; i < count; i++) {
bool hide = is_dealer && dealer_hidden && i == 1;
int card_x = x + (i * spacing);
draw_card(card_x, y, cards[i], hide);
}
}
bool BlackjackView::on_key(const KeyEvent key) {
if (key == KeyEvent::Select) {
switch (game_state) {
case GameState::MENU:
if (cash < 10) {
cash = 100; // Reset if broke - maybe should provide https://gamblersanonymous.org/ link if they also lost their wife and house
wins = 0;
losses = 0;
}
game_state = GameState::BETTING;
break;
case GameState::BETTING:
deal_cards();
break;
case GameState::PLAYING:
player_hit();
break;
case GameState::GAME_OVER:
// Deal new hand with current bet
if (cash >= bet) {
deal_cards();
} else if (cash >= 10) {
// Not enough for current bet, reduce to what they can afford
bet = 10;
deal_cards();
} else {
// Broke, reset game
cash = 100;
wins = 0;
losses = 0;
game_state = GameState::MENU;
draw_menu_static();
}
break;
case GameState::STATS:
game_state = GameState::MENU;
draw_menu_static();
break;
default:
break;
}
return true;
} else if (key == KeyEvent::Left) {
if (game_state == GameState::MENU) {
game_state = GameState::STATS;
}
return true;
} else if (key == KeyEvent::Right) {
if (game_state == GameState::MENU) {
nav_.pop();
return true;
} else if (game_state == GameState::PLAYING) {
player_stay();
return true;
}
}
return false;
}
bool BlackjackView::on_encoder(const EncoderEvent delta) {
if (game_state == GameState::BETTING || game_state == GameState::GAME_OVER) {
if (delta > 0 && bet + 10 <= cash) {
bet += 10;
} else if (delta < 0 && bet >= 20) {
bet -= 10;
}
return true;
}
return false;
}
} // namespace ui::external_app::blackjack
+156
View File
@@ -0,0 +1,156 @@
/*
* Blackjack Game for Portapack Mayhem
* Ported / Enhanced / Graphically made awesome by RocketGod (https://betaskynet.com)
* Based on BlackJack 83 for TI Calculator by Harper Maddox (was written in Assembly)
*/
#ifndef __UI_BLACKJACK_H__
#define __UI_BLACKJACK_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"
#include <string>
#include <vector>
namespace ui::external_app::blackjack {
// Game states
enum class GameState {
MENU,
BETTING,
PLAYING,
DEALER_TURN,
GAME_OVER,
STATS
};
// Card structure
struct Card {
uint8_t value; // 1-13 (Ace=1, Jack=11, Queen=12, King=13)
uint8_t suit; // 0=Spades, 1=Hearts, 2=Diamonds, 3=Clubs
Card()
: value(0), suit(0) {}
Card(uint8_t v, uint8_t s)
: value(v), suit(s) {}
};
// Timer class
using Callback = void (*)(void);
class Ticker {
public:
Ticker() = default;
void attach(Callback func, double delay_sec);
void detach();
};
// Function declarations
void check_game_timer();
void game_timer_check();
class BlackjackView : public View {
public:
BlackjackView(NavigationView& nav);
~BlackjackView();
void on_show() override;
std::string title() const override { return "Blackjack"; }
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 update_game();
private:
NavigationView& nav_;
bool initialized = false;
// Game variables
static constexpr uint8_t MAX_CARDS_IN_HAND = 11; // Maximum possible cards before bust
uint8_t deck[52];
uint8_t deck_position = 0;
uint8_t player_cards[MAX_CARDS_IN_HAND];
uint8_t player_card_count = 0;
uint8_t dealer_cards[MAX_CARDS_IN_HAND];
uint8_t dealer_card_count = 0;
// Game state variables
uint32_t cash = 100;
uint32_t bet = 10;
uint32_t wins = 0;
uint32_t losses = 0;
uint32_t high_score = 100;
bool dealer_hidden = true;
// UI state
uint8_t menu_selection = 0;
bool blink_state = true;
uint32_t blink_counter = 0;
uint32_t dealer_timer = 0;
// Timer
Ticker game_timer;
// Methods
void init_deck();
void shuffle_deck();
uint8_t draw_card();
void deal_cards();
int calculate_hand_value(uint8_t* cards, uint8_t count);
int get_card_value(uint8_t card);
uint8_t get_card_suit(uint8_t card);
std::string get_card_string(uint8_t card);
void player_hit();
void player_stay();
void dealer_turn();
void check_game_over();
void reset_game();
// Drawing methods
void draw_menu();
void draw_menu_static();
void draw_stats();
void draw_game();
void draw_betting();
void draw_card(int x, int y, uint8_t card, bool hidden = false);
void draw_hand(int x, int y, uint8_t* cards, uint8_t count, bool is_dealer = false);
void draw_suit_symbol(int x, int y, uint8_t suit, Color color, bool large);
void clear_screen();
// Settings
app_settings::SettingsManager settings_{
"blackjack",
app_settings::Mode::NO_RF,
{{"cash"sv, &cash},
{"wins"sv, &wins},
{"losses"sv, &losses},
{"highscore"sv, &high_score}}};
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::blackjack
#endif /* __UI_BLACKJACK_H__ */
+1 -1
View File
@@ -119,7 +119,7 @@ class BLESpamView : public View {
#ifdef BLESPMUSECONSOLE
Console console{
{0, 70, 240, 220}};
{0, 70, screen_width, 220}};
#endif
OptionsField options_atkmode{
{0 * 8, 2 * 8},
+12 -10
View File
@@ -12,8 +12,8 @@ namespace ui::external_app::breakout {
Ticker game_timer;
int paddle_x = (SCREEN_WIDTH - PADDLE_WIDTH) / 2;
float ball_x = SCREEN_WIDTH / 2;
int paddle_x = 0;
float ball_x = 0;
float ball_y = GAME_AREA_BOTTOM - PADDLE_HEIGHT - BALL_SIZE - 1;
float ball_dx = 1.5f;
float ball_dy = -2.0f;
@@ -105,7 +105,7 @@ void game_timer_check() {
}
void init_game() {
paddle_x = (SCREEN_WIDTH - PADDLE_WIDTH) / 2;
paddle_x = (screen_width - PADDLE_WIDTH) / 2;
score = 0;
lives = 3;
level = 1;
@@ -157,7 +157,7 @@ void draw_screen() {
}
void draw_borders() {
rect(0, GAME_AREA_TOP - 1, SCREEN_WIDTH, GAME_AREA_TOP, COLOR_BORDER);
rect(0, GAME_AREA_TOP - 1, screen_width, GAME_AREA_TOP, COLOR_BORDER);
}
void draw_bricks() {
@@ -218,14 +218,14 @@ void move_paddle_left() {
}
void move_paddle_right() {
if (paddle_x < SCREEN_WIDTH - PADDLE_WIDTH) {
if (paddle_x < screen_width - PADDLE_WIDTH) {
fillrect(paddle_x, PADDLE_Y, paddle_x + PADDLE_WIDTH, PADDLE_Y + PADDLE_HEIGHT, COLOR_BACKGROUND);
if (ball_attached) {
fillrect(ball_x, ball_y, ball_x + BALL_SIZE, ball_y + BALL_SIZE, COLOR_BACKGROUND);
}
paddle_x += 10;
if (paddle_x > SCREEN_WIDTH - PADDLE_WIDTH) paddle_x = SCREEN_WIDTH - PADDLE_WIDTH;
if (paddle_x > screen_width - PADDLE_WIDTH) paddle_x = screen_width - PADDLE_WIDTH;
if (ball_attached) {
ball_x = paddle_x + (PADDLE_WIDTH / 2) - (BALL_SIZE / 2);
@@ -277,8 +277,8 @@ void update_game() {
if (ball_x < 0) {
ball_x = 0;
ball_dx = -ball_dx;
} else if (ball_x > SCREEN_WIDTH - BALL_SIZE) {
ball_x = SCREEN_WIDTH - BALL_SIZE;
} else if (ball_x > screen_width - BALL_SIZE) {
ball_x = screen_width - BALL_SIZE;
ball_dx = -ball_dx;
}
@@ -381,7 +381,7 @@ void init_menu() {
auto style_blue = *ui::Theme::getInstance()->fg_blue;
auto style_cyan = *ui::Theme::getInstance()->fg_cyan;
int16_t screen_width = 240;
int16_t screen_width = ui::screen_width;
int16_t title_x = (screen_width - 17 * 8) / 2;
int16_t divider_width = 24 * 8;
int16_t divider_x = (screen_width - divider_width) / 2;
@@ -428,7 +428,7 @@ void init_game_over() {
auto style_red = *ui::Theme::getInstance()->fg_red;
auto style_yellow = *ui::Theme::getInstance()->fg_yellow;
int16_t screen_width = 240;
// int16_t screen_width = screen_width;
int16_t title_width = 9 * 8;
int16_t title_x = (screen_width - title_width) / 2;
int16_t score_text_width = (16 + std::to_string(score).length()) * 8;
@@ -484,6 +484,8 @@ void reset_game() {
BreakoutView::BreakoutView(NavigationView& nav)
: nav_{nav} {
paddle_x = (screen_width - PADDLE_WIDTH) / 2;
ball_x = screen_width / 2;
add_children({&dummy});
game_timer.attach(&game_timer_check, 1.0 / 60.0);
}
+1 -3
View File
@@ -56,8 +56,6 @@ class Ticker {
void detach();
};
#define SCREEN_WIDTH 240
#define SCREEN_HEIGHT 320
#define PADDLE_WIDTH 40
#define PADDLE_HEIGHT 10
#define BALL_SIZE 8
@@ -141,7 +139,7 @@ class BreakoutView : public View {
NavigationView& nav_;
Button dummy{
{240, 0, 0, 0},
{screen_width, 0, 0, 0},
""};
MessageHandlerRegistration message_handler_frame_sync{
+2 -2
View File
@@ -53,7 +53,7 @@ class CVSSpamView : public View {
std::vector<std::filesystem::path> file_list{};
MenuView menu_view{
{0, 0, 240, 180},
{0, 0, screen_width, 180},
true};
Text text_empty{
@@ -84,7 +84,7 @@ class CVSSpamView : public View {
LanguageHelper::currentMessages[LANG_STOP]};
ProgressBar progressbar{
{0, 256, 240, 44}};
{0, 256, screen_width, 44}};
MessageHandlerRegistration message_handler_fifo_signal{
Message::ID::RequestSignal,
+4 -1
View File
@@ -80,7 +80,7 @@ bool DebugDumpView::debug_dump_func() {
pmem_dump_file.write_line("updown_converter: " + to_string_dec_int(config_updown_converter()));
pmem_dump_file.write_line("updown_frequency_rx_correction: " + to_string_dec_int(config_freq_rx_correction_updown()));
pmem_dump_file.write_line("updown_frequency_tx_correction: " + to_string_dec_int(config_freq_tx_correction_updown()));
pmem_dump_file.write_line("lcd_inverted_mode: " + to_string_dec_uint(config_lcd_inverted_mode()));
pmem_dump_file.write_line("lcd_normally_black: " + to_string_dec_uint(config_lcd_normally_black()));
pmem_dump_file.write_line("converter_frequency_offset: " + to_string_dec_int(config_converter_freq()));
pmem_dump_file.write_line("frequency_rx_correction: " + to_string_dec_uint(config_freq_rx_correction()));
pmem_dump_file.write_line("frequency_tx_correction: " + to_string_dec_uint(config_freq_tx_correction()));
@@ -157,6 +157,9 @@ bool DebugDumpView::debug_dump_func() {
case ReceiverModel::Mode::WidebandFMAudio:
pmem_dump_file.write_line("modulation: Mode::WidebandFMAudio");
break;
case ReceiverModel::Mode::WFMAudioAMApt:
pmem_dump_file.write_line("modulation: Mode::WFMAudioAMApt");
break;
case ReceiverModel::Mode::SpectrumAnalysis:
pmem_dump_file.write_line("modulation: Mode::SpectrumAnalysis");
break;
+1 -1
View File
@@ -44,7 +44,7 @@ class DebugDumpView : public View {
NavigationView& nav_;
Text dump_output{
{0 * 8, 19 * 8, 30 * 8, 16},
{0 * 8, 19 * 8, screen_width, 16},
""};
Button button_exit{
+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
+58
View File
@@ -0,0 +1,58 @@
/*
* ------------------------------------------------------------
* | 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",
{
// Cactus icon 16x16
0x00, 0x00, // ................
0x80, 0x01, // .......##.......
0x80, 0x01, // .......##.......
0x80, 0x01, // .......##.......
0x98, 0x19, // ...##..##..##...
0x98, 0x19, // ...##..##..##...
0x98, 0x19, // ...##..##..##...
0x98, 0x19, // ...##..##..##...
0xF8, 0x1F, // ...##########...
0xF0, 0x0F, // ....########....
0x80, 0x01, // .......##.......
0x80, 0x01, // .......##.......
0x80, 0x01, // .......##.......
0x80, 0x01, // .......##.......
0x80, 0x01, // .......##.......
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__ */

Some files were not shown because too many files have changed in this diff Show More