From b8dd75884cc073453c1278d863aa1abf716bc655 Mon Sep 17 00:00:00 2001 From: Jack Webb-Heller Date: Thu, 5 Jan 2023 19:39:42 +0000 Subject: [PATCH 1/7] Add Dyson Purifier Hot+Cool support to IR AC Library (#2252) --- assets/resources/infrared/assets/ac.ir | 38 +++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/assets/resources/infrared/assets/ac.ir b/assets/resources/infrared/assets/ac.ir index d9410a300..1f9c2145f 100644 --- a/assets/resources/infrared/assets/ac.ir +++ b/assets/resources/infrared/assets/ac.ir @@ -260,4 +260,40 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 5043 2132 361 1770 356 723 335 715 332 718 329 1774 363 715 332 719 328 722 336 714 333 1770 356 722 336 1767 360 1772 354 724 334 1769 357 1774 363 1768 358 1773 364 1767 359 720 327 723 335 1768 359 720 327 723 335 716 331 719 328 722 336 714 333 1770 356 1774 363 1769 357 1773 364 1767 360 720 327 1775 362 1769 357 721 326 725 333 717 330 720 327 723 335 716 331 719 328 722 336 714 333 717 330 720 327 723 335 1768 359 1773 364 1767 360 1772 354 724 334 717 330 720 327 723 335 29451 5041 2134 359 1772 354 724 334 717 330 720 327 1775 362 717 330 720 327 723 335 715 332 1771 355 723 335 1768 358 1773 364 715 332 1770 357 1775 362 1769 357 1774 363 1768 359 720 327 723 335 1768 359 720 327 724 334 716 331 719 328 722 336 715 332 718 329 720 327 723 335 716 331 1771 355 1776 361 718 329 721 326 1776 361 718 329 1773 364 1767 360 720 327 723 335 715 332 718 329 1774 363 1768 359 720 327 723 335 1768 358 721 326 724 334 716 331 719 328 722 336 1767 360 719 328 722 336 715 332 718 329 721 326 724 334 717 330 720 327 723 335 715 332 719 328 722 325 725 333 717 330 720 327 723 335 716 331 719 328 1774 363 716 331 1771 355 1776 361 718 329 721 326 724 334 717 330 1772 365 714 333 1770 356 722 336 715 332 718 329 721 326 724 334 717 330 719 328 1775 362 717 330 720 327 723 335 715 332 718 329 1774 363 715 332 718 329 721 326 725 333 717 330 1772 365 - +# +# Model: Dyson Purifier Hot+Cool +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2309 665 781 672 803 672 803 695 832 643 833 1355 804 694 836 640 781 695 804 1409 778 697 777 702 797 678 798 677 799 678 799 701 803 674 802 1412 801 674 801 674 801 674 802 674 801 51317 2284 670 775 1413 802 51252 2283 670 801 1412 775 51275 2258 673 798 1414 802 51248 2284 670 802 1412 774 51246 2259 695 775 1413 801 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2316 610 806 671 781 695 806 695 781 695 782 1405 782 694 808 694 780 693 808 1381 779 697 802 1412 776 1438 800 1437 776 1438 775 700 775 1412 776 700 801 701 775 700 776 1438 776 700 776 51695 2258 695 776 1437 776 51248 2258 672 798 1439 776 51240 2258 670 801 1436 776 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2342 612 781 695 805 668 810 666 810 665 811 1402 811 666 811 692 781 670 781 1432 781 696 778 1436 802 1412 802 1412 802 1413 801 1412 802 1412 801 1412 777 1411 776 1463 776 1412 800 1414 801 51041 2257 697 802 1411 777 51240 2283 671 776 1437 801 51209 2255 672 799 1412 801 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2317 637 832 644 830 669 805 670 805 672 803 1411 803 673 802 674 802 673 803 1411 803 674 801 1411 802 675 775 1415 800 701 774 1440 801 1412 775 702 799 1414 774 1413 801 701 801 675 800 51681 2257 695 803 1411 801 51226 2283 671 799 1412 803 51246 2257 696 803 1411 775 51255 2282 668 803 1410 802 51243 2258 695 802 1387 798 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2315 618 853 643 832 644 834 641 833 643 833 1356 805 695 835 640 808 667 809 1404 808 668 806 1409 803 674 801 1412 802 1388 799 677 799 701 775 701 801 1389 799 677 799 676 800 1439 802 51426 2283 671 800 1412 802 51251 2258 697 801 1387 800 51248 2283 669 802 1411 802 51230 2258 696 799 1387 801 51225 2283 670 801 1411 801 51200 2280 695 775 1411 802 51227 2258 696 802 1411 775 51204 2281 669 801 1411 800 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2320 634 837 637 838 637 838 640 835 642 832 1378 836 645 826 670 809 667 808 1406 806 672 803 674 802 1412 802 1412 800 676 801 675 802 1412 802 674 802 1413 801 1412 801 1413 802 1412 802 50937 2285 671 801 1411 802 51225 2280 696 775 1412 801 51212 2283 671 775 1412 802 From 41c43f480512899b8ee0010f87669ced9a3088f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Fri, 6 Jan 2023 15:31:17 +0900 Subject: [PATCH 2/7] Various improvements: Toolbox, Updater and Unit Tests. (#2250) * Toolbox: add seek to character stream method. UpdateUtils: reverse manifest iterator. UnitTests: more unit tests. * Target: bump API version. Updater: delete empty folders from manifest before resource deployment. * UnitTests: use manifest from unit_tests folder instead of global one * Make PVS happy * sector cache: allocate always * Better PVS config for manifest.c * PVS: Move exception outside of condition * PVS: remove confusing condition Co-authored-by: SG --- .../debug/unit_tests/manifest/manifest.c | 75 +++++++++++++++++ .../debug/unit_tests/stream/stream_test.c | 24 ++++++ applications/debug/unit_tests/test_index.c | 2 + .../updater/util/update_task_worker_backup.c | 44 +++++++++- assets/unit_tests/Manifest | 76 +++++++++++++++++ firmware/targets/f7/api_symbols.csv | 3 +- firmware/targets/f7/fatfs/sector_cache.c | 8 +- lib/toolbox/stream/stream.c | 83 ++++++++++++++++++- lib/toolbox/stream/stream.h | 39 ++++++--- lib/update_util/resources/manifest.c | 54 +++++++++++- lib/update_util/resources/manifest.h | 14 ++++ 11 files changed, 398 insertions(+), 24 deletions(-) create mode 100644 applications/debug/unit_tests/manifest/manifest.c create mode 100644 assets/unit_tests/Manifest diff --git a/applications/debug/unit_tests/manifest/manifest.c b/applications/debug/unit_tests/manifest/manifest.c new file mode 100644 index 000000000..0b24ad1ed --- /dev/null +++ b/applications/debug/unit_tests/manifest/manifest.c @@ -0,0 +1,75 @@ +#include +#include "../minunit.h" +#include + +#define TAG "Manifest" + +MU_TEST(manifest_type_test) { + mu_assert(ResourceManifestEntryTypeUnknown == 0, "ResourceManifestEntryTypeUnknown != 0\r\n"); + mu_assert(ResourceManifestEntryTypeVersion == 1, "ResourceManifestEntryTypeVersion != 1\r\n"); + mu_assert( + ResourceManifestEntryTypeTimestamp == 2, "ResourceManifestEntryTypeTimestamp != 2\r\n"); + mu_assert( + ResourceManifestEntryTypeDirectory == 3, "ResourceManifestEntryTypeDirectory != 3\r\n"); + mu_assert(ResourceManifestEntryTypeFile == 4, "ResourceManifestEntryTypeFile != 4\r\n"); +} + +MU_TEST(manifest_iteration_test) { + bool result = true; + size_t counters[5] = {0}; + + Storage* storage = furi_record_open(RECORD_STORAGE); + ResourceManifestReader* manifest_reader = resource_manifest_reader_alloc(storage); + do { + // Open manifest file + if(!resource_manifest_reader_open(manifest_reader, EXT_PATH("unit_tests/Manifest"))) { + result = false; + break; + } + + // Iterate forward + ResourceManifestEntry* entry_ptr = NULL; + while((entry_ptr = resource_manifest_reader_next(manifest_reader))) { + FURI_LOG_D(TAG, "F:%u:%s", entry_ptr->type, furi_string_get_cstr(entry_ptr->name)); + if(entry_ptr->type > 4) { + mu_fail("entry_ptr->type > 4\r\n"); + result = false; + break; + } + counters[entry_ptr->type]++; + } + if(!result) break; + + // Iterate backward + while((entry_ptr = resource_manifest_reader_previous(manifest_reader))) { + FURI_LOG_D(TAG, "B:%u:%s", entry_ptr->type, furi_string_get_cstr(entry_ptr->name)); + if(entry_ptr->type > 4) { + mu_fail("entry_ptr->type > 4\r\n"); + result = false; + break; + } + counters[entry_ptr->type]--; + } + } while(false); + + resource_manifest_reader_free(manifest_reader); + furi_record_close(RECORD_STORAGE); + + mu_assert(counters[ResourceManifestEntryTypeUnknown] == 0, "Unknown counter != 0\r\n"); + mu_assert(counters[ResourceManifestEntryTypeVersion] == 0, "Version counter != 0\r\n"); + mu_assert(counters[ResourceManifestEntryTypeTimestamp] == 0, "Timestamp counter != 0\r\n"); + mu_assert(counters[ResourceManifestEntryTypeDirectory] == 0, "Directory counter != 0\r\n"); + mu_assert(counters[ResourceManifestEntryTypeFile] == 0, "File counter != 0\r\n"); + + mu_assert(result, "Manifest forward iterate failed\r\n"); +} + +MU_TEST_SUITE(manifest_suite) { + MU_RUN_TEST(manifest_type_test); + MU_RUN_TEST(manifest_iteration_test); +} + +int run_minunit_test_manifest() { + MU_RUN_SUITE(manifest_suite); + return MU_EXIT_CODE; +} \ No newline at end of file diff --git a/applications/debug/unit_tests/stream/stream_test.c b/applications/debug/unit_tests/stream/stream_test.c index 802e34025..c28696570 100644 --- a/applications/debug/unit_tests/stream/stream_test.c +++ b/applications/debug/unit_tests/stream/stream_test.c @@ -72,8 +72,32 @@ MU_TEST_1(stream_composite_subtest, Stream* stream) { mu_check(stream_seek(stream, -3, StreamOffsetFromEnd)); mu_check(stream_tell(stream) == 4); + // test seeks to char. content: '1337_69' + stream_rewind(stream); + mu_check(stream_seek_to_char(stream, '3', StreamDirectionForward)); + mu_check(stream_tell(stream) == 1); + mu_check(stream_seek_to_char(stream, '3', StreamDirectionForward)); + mu_check(stream_tell(stream) == 2); + mu_check(stream_seek_to_char(stream, '_', StreamDirectionForward)); + mu_check(stream_tell(stream) == 4); + mu_check(stream_seek_to_char(stream, '9', StreamDirectionForward)); + mu_check(stream_tell(stream) == 6); + mu_check(!stream_seek_to_char(stream, '9', StreamDirectionForward)); + mu_check(stream_tell(stream) == 6); + mu_check(stream_seek_to_char(stream, '_', StreamDirectionBackward)); + mu_check(stream_tell(stream) == 4); + mu_check(stream_seek_to_char(stream, '3', StreamDirectionBackward)); + mu_check(stream_tell(stream) == 2); + mu_check(stream_seek_to_char(stream, '3', StreamDirectionBackward)); + mu_check(stream_tell(stream) == 1); + mu_check(!stream_seek_to_char(stream, '3', StreamDirectionBackward)); + mu_check(stream_tell(stream) == 1); + mu_check(stream_seek_to_char(stream, '1', StreamDirectionBackward)); + mu_check(stream_tell(stream) == 0); + // write string with replacemet // "1337_69" -> "1337lee" + mu_check(stream_seek(stream, 4, StreamOffsetFromStart)); mu_check(stream_write_string(stream, string_lee) == 3); mu_check(stream_size(stream) == 7); mu_check(stream_tell(stream) == 7); diff --git a/applications/debug/unit_tests/test_index.c b/applications/debug/unit_tests/test_index.c index ccf471531..2bb9c423f 100644 --- a/applications/debug/unit_tests/test_index.c +++ b/applications/debug/unit_tests/test_index.c @@ -13,6 +13,7 @@ int run_minunit_test_furi_hal(); int run_minunit_test_furi_string(); int run_minunit_test_infrared(); int run_minunit_test_rpc(); +int run_minunit_test_manifest(); int run_minunit_test_flipper_format(); int run_minunit_test_flipper_format_string(); int run_minunit_test_stream(); @@ -41,6 +42,7 @@ const UnitTest unit_tests[] = { {.name = "storage", .entry = run_minunit_test_storage}, {.name = "stream", .entry = run_minunit_test_stream}, {.name = "dirwalk", .entry = run_minunit_test_dirwalk}, + {.name = "manifest", .entry = run_minunit_test_manifest}, {.name = "flipper_format", .entry = run_minunit_test_flipper_format}, {.name = "flipper_format_string", .entry = run_minunit_test_flipper_format_string}, {.name = "rpc", .entry = run_minunit_test_rpc}, diff --git a/applications/system/updater/util/update_task_worker_backup.c b/applications/system/updater/util/update_task_worker_backup.c index 1f88d4f44..780401068 100644 --- a/applications/system/updater/util/update_task_worker_backup.c +++ b/applications/system/updater/util/update_task_worker_backup.c @@ -79,8 +79,8 @@ static void update_task_set_progress( update_task, UpdateTaskStageProgress, - /* For this stage, first 30% of progress = cleanup */ - (n_processed_files++ * 30) / (n_approx_file_entries + 1)); + /* For this stage, first 20% of progress = cleanup files */ + (n_processed_files++ * 20) / (n_approx_file_entries + 1)); FuriString* file_path = furi_string_alloc(); path_concat( @@ -90,6 +90,46 @@ static void furi_string_free(file_path); } } + + while((entry_ptr = resource_manifest_reader_previous(manifest_reader))) { + if(entry_ptr->type == ResourceManifestEntryTypeDirectory) { + update_task_set_progress( + update_task, + UpdateTaskStageProgress, + /* For this stage, second 10% of progress = cleanup directories */ + (n_processed_files++ * 10) / (n_approx_file_entries + 1)); + + FuriString* folder_path = furi_string_alloc(); + File* folder_file = storage_file_alloc(update_task->storage); + + do { + path_concat( + STORAGE_EXT_PATH_PREFIX, + furi_string_get_cstr(entry_ptr->name), + folder_path); + + FURI_LOG_D(TAG, "Removing folder %s", furi_string_get_cstr(folder_path)); + if(!storage_dir_open(folder_file, furi_string_get_cstr(folder_path))) { + FURI_LOG_W( + TAG, + "%s can't be opened, skipping", + furi_string_get_cstr(folder_path)); + break; + } + + if(storage_dir_read(folder_file, NULL, NULL, 0)) { + FURI_LOG_I( + TAG, "%s is not empty, skipping", furi_string_get_cstr(folder_path)); + break; + } + + storage_simply_remove(update_task->storage, furi_string_get_cstr(folder_path)); + } while(false); + + storage_file_free(folder_file); + furi_string_free(folder_path); + } + } } while(false); resource_manifest_reader_free(manifest_reader); } diff --git a/assets/unit_tests/Manifest b/assets/unit_tests/Manifest new file mode 100644 index 000000000..db2979ee6 --- /dev/null +++ b/assets/unit_tests/Manifest @@ -0,0 +1,76 @@ +V:0 +T:1672935435 +D:infrared +D:nfc +D:subghz +F:4bff70f2a2ae771f81de5cfb090b3d74:3952:infrared/test_kaseikyo.irtest +F:8556d32d7c54e66771d9da78d007d379:21463:infrared/test_nec.irtest +F:860c0c475573878842180a6cb50c85c7:2012:infrared/test_nec42.irtest +F:2b3cbf3fe7d3642190dfb8362dcc0ed6:3522:infrared/test_nec42ext.irtest +F:c74bbd7f885ab8fbc3b3363598041bc1:18976:infrared/test_necext.irtest +F:cab5e604abcb233bcb27903baec24462:7460:infrared/test_rc5.irtest +F:3d22b3ec2531bb8f4842c9c0c6a8d97c:547:infrared/test_rc5x.irtest +F:c9cb9fa4decbdd077741acb845f21343:8608:infrared/test_rc6.irtest +F:97de943385bc6ad1c4a58fc4fedb5244:16975:infrared/test_samsung32.irtest +F:4eb36c62d4f2e737a3e4a64b5ff0a8e7:41623:infrared/test_sirc.irtest +F:e4ec3299cbe1f528fb1b9b45aac53556:4182:nfc/nfc_nfca_signal_long.nfc +F:af4d10974834c2703ad29e859eea78c2:1020:nfc/nfc_nfca_signal_short.nfc +F:224d12457a26774d8d2aa0d4b3a15652:160:subghz/ansonic.sub +F:ce9fc98dc01230387a340332316774f1:13642:subghz/ansonic_raw.sub +F:f958927b656d0804036c28b4a31ff856:157:subghz/bett.sub +F:b4b17b2603fa3a144dbea4d9ede9f61d:5913:subghz/bett_raw.sub +F:370a0c62be967b420da5e60ffcdc078b:157:subghz/came.sub +F:0156915c656d8c038c6d555d34349a36:6877:subghz/came_atomo_raw.sub +F:111a8b796661f3cbd6f49f756cf91107:8614:subghz/came_raw.sub +F:2101b0a5a72c87f9dce77223b2885aa7:162:subghz/came_twee.sub +F:c608b78b8e4646eeb94db37644623254:10924:subghz/came_twee_raw.sub +F:c4a55acddb68fc3111d592c9292022a8:21703:subghz/cenmax_raw.sub +F:51d6bd600345954b9c84a5bc6e999313:159:subghz/clemsa.sub +F:14fa0d5931a32674bfb2ddf288f3842b:21499:subghz/clemsa_raw.sub +F:f38b6dfa0920199200887b2cd5c0a385:161:subghz/doitrand.sub +F:c7e53da8e3588a2c0721aa794699ccd4:24292:subghz/doitrand_raw.sub +F:cc73b6f4d05bfe30c67a0d18b63e58d9:159:subghz/doorhan.sub +F:22fec89c5cc43504ad4391e61e12c7e0:10457:subghz/doorhan_raw.sub +F:3a97d8bd32ddaff42932b4c3033ee2d2:12732:subghz/faac_slh_raw.sub +F:06d3226f5330665f48d41c49e34fed15:159:subghz/gate_tx.sub +F:8b150a8d38ac7c4f7063ee0d42050399:13827:subghz/gate_tx_raw.sub +F:a7904e17b0c18c083ae1acbefc330c7a:159:subghz/holtek.sub +F:72bb528255ef1c135cb3f436414897d3:173:subghz/holtek_ht12x.sub +F:54ceacb8c156f9534fc7ee0a0911f4da:11380:subghz/holtek_ht12x_raw.sub +F:4a9567c1543cf3e7bb5350b635d9076f:31238:subghz/holtek_raw.sub +F:ca86c0d78364d704ff62b0698093d396:162:subghz/honeywell_wdb.sub +F:f606548c935adc8d8bc804326ef67543:38415:subghz/honeywell_wdb_raw.sub +F:20bba4b0aec006ced7e82513f9459e31:15532:subghz/hormann_hsm_raw.sub +F:3392f2db6aa7777e937db619b86203bb:10637:subghz/ido_117_111_raw.sub +F:cc5c7968527cc233ef11a08986e31bf2:167:subghz/intertechno_v3.sub +F:70bceb941739260ab9f6162cfdeb0347:18211:subghz/intertechno_v3_raw.sub +F:bc9a4622f3e22fd7f82eb3f26e61f59b:44952:subghz/kia_seed_raw.sub +F:6b6e95fc70ea481dc6184d291466d16a:159:subghz/linear.sub +F:77aaa9005db54c0357451ced081857b2:14619:subghz/linear_raw.sub +F:1a618e21e6ffa9984d465012e704c450:161:subghz/magellan.sub +F:bf43cb85d79e20644323d6acad87e028:5808:subghz/magellan_raw.sub +F:4ef17320f936ee88e92582a9308b2faa:161:subghz/marantec.sub +F:507a8413a1603ad348eea945123fb7cc:21155:subghz/marantec_raw.sub +F:22b69dc490d5425488342b5c5a838d55:161:subghz/megacode.sub +F:4f8fe9bef8bdd9c52f3f77e829f8986f:6205:subghz/megacode_raw.sub +F:b39f62cb108c2fa9916e0a466596ab87:18655:subghz/nero_radio_raw.sub +F:d0d70f8183032096805a41e1808c093b:26436:subghz/nero_sketch_raw.sub +F:c6999bd0eefd0fccf34820e17bcbc8ba:161:subghz/nice_flo.sub +F:9b1200600b9ec2a73166797ff243fbfc:3375:subghz/nice_flo_raw.sub +F:b52bafb098282676d1c7163bfb0d6e73:8773:subghz/nice_flor_s_raw.sub +F:e4df94dfdee2efadf2ed9a1e9664f8b2:163:subghz/phoenix_v2.sub +F:8ec066976df93fba6335b3f6dc47014c:8548:subghz/phoenix_v2_raw.sub +F:2b1192e4898aaf274caebbb493b9f96e:164:subghz/power_smart.sub +F:8b8195cab1d9022fe38e802383fb923a:3648:subghz/power_smart_raw.sub +F:1ccf1289533e0486a1d010d934ad7b06:170:subghz/princeton.sub +F:8bccc506a61705ec429aecb879e5d7ce:7344:subghz/princeton_raw.sub +F:0bda91d783e464165190c3b3d16666a7:38724:subghz/scher_khan_magic_code.sub +F:116d7e1a532a0c9e00ffeee105f7138b:166:subghz/security_pls_1_0.sub +F:441fc7fc6fa11ce0068fde3f6145177b:69413:subghz/security_pls_1_0_raw.sub +F:e5e33c24c5e55f592ca892b5aa8fa31f:208:subghz/security_pls_2_0.sub +F:2614f0aef367042f8623719d765bf2c0:62287:subghz/security_pls_2_0_raw.sub +F:8eb533544c4c02986800c90e935184ff:168:subghz/smc5326.sub +F:fc67a4fe7e0b3bc81a1c8da8caca7658:4750:subghz/smc5326_raw.sub +F:24196a4c4af1eb03404a2ee434c864bf:4096:subghz/somfy_keytis_raw.sub +F:6a5ece145a5694e543d99bf1b970baf0:9741:subghz/somfy_telis_raw.sub +F:0ad046bfa9ec872e92141a69bbf03d92:382605:subghz/test_random_raw.sub diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 9e1fa85d7..ac7d1a0c5 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,11.5,, +Version,+,11.6,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -2480,6 +2480,7 @@ Function,+,stream_read_line,_Bool,"Stream*, FuriString*" Function,+,stream_rewind,_Bool,Stream* Function,+,stream_save_to_file,size_t,"Stream*, Storage*, const char*, FS_OpenMode" Function,+,stream_seek,_Bool,"Stream*, int32_t, StreamOffset" +Function,+,stream_seek_to_char,_Bool,"Stream*, char, StreamDirection" Function,+,stream_size,size_t,Stream* Function,+,stream_split,_Bool,"Stream*, Stream*, Stream*" Function,+,stream_tell,size_t,Stream* diff --git a/firmware/targets/f7/fatfs/sector_cache.c b/firmware/targets/f7/fatfs/sector_cache.c index 5a4f1b978..d23c1d5ad 100644 --- a/firmware/targets/f7/fatfs/sector_cache.c +++ b/firmware/targets/f7/fatfs/sector_cache.c @@ -8,6 +8,7 @@ #define SECTOR_SIZE 512 #define N_SECTORS 8 +#define TAG "SDCache" typedef struct { uint32_t itr; @@ -19,14 +20,15 @@ static SectorCache* cache = NULL; void sector_cache_init() { if(cache == NULL) { - cache = furi_hal_memory_alloc(sizeof(SectorCache)); + // TODO: tuneup allocation order, to place cache in mem pool (MEM2) + cache = memmgr_alloc_from_pool(sizeof(SectorCache)); } if(cache != NULL) { - FURI_LOG_I("SectorCache", "Initializing sector cache"); + FURI_LOG_I(TAG, "Init"); memset(cache, 0, sizeof(SectorCache)); } else { - FURI_LOG_E("SectorCache", "Cannot enable sector cache"); + FURI_LOG_E(TAG, "Init failed"); } } diff --git a/lib/toolbox/stream/stream.c b/lib/toolbox/stream/stream.c index 055bab5bf..407da0f2c 100644 --- a/lib/toolbox/stream/stream.c +++ b/lib/toolbox/stream/stream.c @@ -4,6 +4,8 @@ #include #include +#define STREAM_BUFFER_SIZE (32U) + void stream_free(Stream* stream) { furi_assert(stream); stream->vtable->free(stream); @@ -24,6 +26,82 @@ bool stream_seek(Stream* stream, int32_t offset, StreamOffset offset_type) { return stream->vtable->seek(stream, offset, offset_type); } +static bool stream_seek_to_char_forward(Stream* stream, char c) { + // Search is starting from seconds character + if(!stream_seek(stream, 1, StreamOffsetFromCurrent)) { + return false; + } + + // Search character in a stream + bool result = false; + while(!result) { + uint8_t buffer[STREAM_BUFFER_SIZE] = {0}; + size_t ret = stream_read(stream, buffer, STREAM_BUFFER_SIZE); + for(size_t i = 0; i < ret; i++) { + if(buffer[i] == c) { + stream_seek(stream, (int32_t)i - ret, StreamOffsetFromCurrent); + result = true; + break; + } + } + if(ret != STREAM_BUFFER_SIZE) break; + } + return result; +} + +static bool stream_seek_to_char_backward(Stream* stream, char c) { + size_t anchor = stream_tell(stream); + + // Special case, no previous characters + if(anchor == 0) { + return false; + } + + bool result = false; + while(!result) { + // Seek back + uint8_t buffer[STREAM_BUFFER_SIZE] = {0}; + size_t to_read = STREAM_BUFFER_SIZE; + if(to_read > anchor) { + to_read = anchor; + } + + anchor -= to_read; + furi_check(stream_seek(stream, anchor, StreamOffsetFromStart)); + + size_t ret = stream_read(stream, buffer, to_read); + for(size_t i = 0; i < ret; i++) { + size_t cursor = ret - i - 1; + if(buffer[cursor] == c) { + result = true; + furi_check(stream_seek(stream, anchor + cursor, StreamOffsetFromStart)); + break; + } else { + } + } + if(ret != STREAM_BUFFER_SIZE) break; + } + return result; +} + +bool stream_seek_to_char(Stream* stream, char c, StreamDirection direction) { + const size_t old_position = stream_tell(stream); + + bool result = false; + if(direction == StreamDirectionForward) { + result = stream_seek_to_char_forward(stream, c); + } else if(direction == StreamDirectionBackward) { + result = stream_seek_to_char_backward(stream, c); + } + + // Rollback + if(!result) { + stream_seek(stream, old_position, StreamOffsetFromStart); + } + + return result; +} + size_t stream_tell(Stream* stream) { furi_assert(stream); return stream->vtable->tell(stream); @@ -69,11 +147,10 @@ static bool stream_write_struct(Stream* stream, const void* context) { bool stream_read_line(Stream* stream, FuriString* str_result) { furi_string_reset(str_result); - const uint8_t buffer_size = 32; - uint8_t buffer[buffer_size]; + uint8_t buffer[STREAM_BUFFER_SIZE]; do { - uint16_t bytes_were_read = stream_read(stream, buffer, buffer_size); + uint16_t bytes_were_read = stream_read(stream, buffer, STREAM_BUFFER_SIZE); if(bytes_were_read == 0) break; bool result = false; diff --git a/lib/toolbox/stream/stream.h b/lib/toolbox/stream/stream.h index fc3855102..84b4f0eb2 100644 --- a/lib/toolbox/stream/stream.h +++ b/lib/toolbox/stream/stream.h @@ -16,6 +16,11 @@ typedef enum { StreamOffsetFromEnd, } StreamOffset; +typedef enum { + StreamDirectionForward, + StreamDirectionBackward, +} StreamDirection; + typedef bool (*StreamWriteCB)(Stream* stream, const void* context); /** @@ -31,15 +36,15 @@ void stream_free(Stream* stream); void stream_clean(Stream* stream); /** - * Indicates that the rw pointer is at the end of the stream + * Indicates that the RW pointer is at the end of the stream * @param stream Stream instance - * @return true if rw pointer is at the end of the stream - * @return false if rw pointer is not at the end of the stream + * @return true if RW pointer is at the end of the stream + * @return false if RW pointer is not at the end of the stream */ bool stream_eof(Stream* stream); /** - * Moves the rw pointer. + * Moves the RW pointer. * @param stream Stream instance * @param offset how much to move the pointer * @param offset_type starting from what @@ -48,10 +53,20 @@ bool stream_eof(Stream* stream); */ bool stream_seek(Stream* stream, int32_t offset, StreamOffset offset_type); +/** Seek to next occurrence of the character + * + * @param stream Pointer to the stream instance + * @param[in] c The Character + * @param[in] direction The Direction + * + * @return true on success + */ +bool stream_seek_to_char(Stream* stream, char c, StreamDirection direction); + /** - * Gets the value of the rw pointer + * Gets the value of the RW pointer * @param stream Stream instance - * @return size_t value of the rw pointer + * @return size_t value of the RW pointer */ size_t stream_tell(Stream* stream); @@ -101,13 +116,13 @@ bool stream_delete_and_insert( * Read line from a stream (supports LF and CRLF line endings) * @param stream * @param str_result - * @return true if line lenght is not zero + * @return true if line length is not zero * @return false otherwise */ bool stream_read_line(Stream* stream, FuriString* str_result); /** - * Moves the rw pointer to the start + * Moves the RW pointer to the start * @param stream Stream instance */ bool stream_rewind(Stream* stream); @@ -157,7 +172,7 @@ size_t stream_write_vaformat(Stream* stream, const char* format, va_list args); /** * Insert N chars to the stream, starting at the current pointer. - * Data will be inserted, not overwritteт, so the stream will be increased in size. + * Data will be inserted, not overwritten, so the stream will be increased in size. * @param stream Stream instance * @param data data to be inserted * @param size size of data to be inserted @@ -273,7 +288,7 @@ bool stream_delete_and_insert_vaformat( /** * Remove N chars from the stream, starting at the current pointer. - * The size may be larger than stream size, the stream will be cleared from current rw pointer to the end. + * The size may be larger than stream size, the stream will be cleared from current RW pointer to the end. * @param stream Stream instance * @param size how many chars need to be deleted * @return true if the operation was successful @@ -282,7 +297,7 @@ bool stream_delete_and_insert_vaformat( bool stream_delete(Stream* stream, size_t size); /** - * Copy data from one stream to another. Data will be copied from current rw pointer and to current rw pointer. + * Copy data from one stream to another. Data will be copied from current RW pointer and to current RW pointer. * @param stream_from * @param stream_to * @param size @@ -328,7 +343,7 @@ size_t stream_load_from_file(Stream* stream, Storage* storage, const char* path) size_t stream_save_to_file(Stream* stream, Storage* storage, const char* path, FS_OpenMode mode); /** - * Dump stream inner data (size, RW positiot, content) + * Dump stream inner data (size, RW position, content) * @param stream Stream instance */ void stream_dump_data(Stream* stream); diff --git a/lib/update_util/resources/manifest.c b/lib/update_util/resources/manifest.c index baa7acebd..5a818a0a4 100644 --- a/lib/update_util/resources/manifest.c +++ b/lib/update_util/resources/manifest.c @@ -60,6 +60,12 @@ ResourceManifestEntry* resource_manifest_reader_next(ResourceManifestReader* res char type_code = furi_string_get_char(resource_manifest->linebuf, 0); switch(type_code) { + case 'V': + resource_manifest->entry.type = ResourceManifestEntryTypeVersion; + break; + case 'T': + resource_manifest->entry.type = ResourceManifestEntryTypeTimestamp; + break; case 'F': resource_manifest->entry.type = ResourceManifestEntryTypeFile; break; @@ -98,9 +104,9 @@ ResourceManifestEntry* resource_manifest_reader_next(ResourceManifestReader* res furi_string_right(resource_manifest->linebuf, offs + 1); furi_string_set(resource_manifest->entry.name, resource_manifest->linebuf); - } else if(resource_manifest->entry.type == ResourceManifestEntryTypeDirectory) { //-V547 - /* Parse directory entry - D: */ + } else { //-V547 + /* Everything else is plain key value. Parse version, timestamp or directory entry + : */ /* Remove entry type code */ furi_string_right(resource_manifest->linebuf, 2); @@ -113,3 +119,45 @@ ResourceManifestEntry* resource_manifest_reader_next(ResourceManifestReader* res return NULL; } + +ResourceManifestEntry* + resource_manifest_reader_previous(ResourceManifestReader* resource_manifest) { + furi_assert(resource_manifest); + + // Snapshot position for rollback + const size_t previous_position = stream_tell(resource_manifest->stream); + + // We need to jump 2 lines back + size_t jumps = 2; + // Special case: end of the file. + const bool was_eof = stream_eof(resource_manifest->stream); + if(was_eof) { + jumps = 1; + } + while(jumps) { + if(!stream_seek_to_char(resource_manifest->stream, '\n', StreamDirectionBackward)) { + break; + } + if(stream_tell(resource_manifest->stream) < (previous_position - 1)) { + jumps--; + } + } + + // Special case: first line. Force seek to zero + if(jumps == 1) { + jumps = 0; + stream_seek(resource_manifest->stream, 0, StreamOffsetFromStart); + } + + if(jumps == 0) { + ResourceManifestEntry* entry = resource_manifest_reader_next(resource_manifest); + // Special case: was end of the file, prevent loop + if(was_eof) { + stream_seek(resource_manifest->stream, -1, StreamOffsetFromCurrent); + } + return entry; + } else { + stream_seek(resource_manifest->stream, previous_position, StreamOffsetFromStart); + return NULL; + } +} diff --git a/lib/update_util/resources/manifest.h b/lib/update_util/resources/manifest.h index 8baa1613e..ddceb5ffa 100644 --- a/lib/update_util/resources/manifest.h +++ b/lib/update_util/resources/manifest.h @@ -11,6 +11,8 @@ extern "C" { typedef enum { ResourceManifestEntryTypeUnknown = 0, + ResourceManifestEntryTypeVersion, + ResourceManifestEntryTypeTimestamp, ResourceManifestEntryTypeDirectory, ResourceManifestEntryTypeFile, } ResourceManifestEntryType; @@ -52,6 +54,18 @@ bool resource_manifest_reader_open(ResourceManifestReader* resource_manifest, co */ ResourceManifestEntry* resource_manifest_reader_next(ResourceManifestReader* resource_manifest); +/** Read previous file/dir entry from manifest + * + * You must be at the end of the manifest to use this function. + * Intended to be used after reaching end with resource_manifest_reader_next + * + * @param resource_manifest Pointer to the ResourceManifestReader instance + * + * @return entry or NULL if end of file + */ +ResourceManifestEntry* + resource_manifest_reader_previous(ResourceManifestReader* resource_manifest); + #ifdef __cplusplus } // extern "C" #endif \ No newline at end of file From 9740dd8c7579fa07fe509664893e770ed27da187 Mon Sep 17 00:00:00 2001 From: yan0f <31446439+yan0f@users.noreply.github.com> Date: Fri, 6 Jan 2023 19:06:50 +0300 Subject: [PATCH 3/7] Fix typos in source code (#2258) --- applications/debug/unit_tests/stream/stream_test.c | 2 +- lib/ST25RFAL002/include/rfal_isoDep.h | 2 +- lib/ST25RFAL002/include/rfal_t4t.h | 2 +- lib/fatfs/ff.c | 4 ++-- lib/infrared/encoder_decoder/infrared.h | 2 +- lib/lfrfid/protocols/protocol_pyramid.c | 2 +- lib/nfc/nfc_device.c | 4 ++-- lib/nfc/parsers/plantain_4k_parser.c | 2 +- lib/nfc/parsers/plantain_parser.c | 2 +- lib/nfc/parsers/two_cities.c | 2 +- lib/one_wire/ibutton/protocols/protocol_cyfral.c | 2 +- lib/subghz/environment.h | 2 +- scripts/flipper/assets/icon.py | 2 +- 13 files changed, 15 insertions(+), 15 deletions(-) diff --git a/applications/debug/unit_tests/stream/stream_test.c b/applications/debug/unit_tests/stream/stream_test.c index c28696570..2fa3b21a2 100644 --- a/applications/debug/unit_tests/stream/stream_test.c +++ b/applications/debug/unit_tests/stream/stream_test.c @@ -95,7 +95,7 @@ MU_TEST_1(stream_composite_subtest, Stream* stream) { mu_check(stream_seek_to_char(stream, '1', StreamDirectionBackward)); mu_check(stream_tell(stream) == 0); - // write string with replacemet + // write string with replacement // "1337_69" -> "1337lee" mu_check(stream_seek(stream, 4, StreamOffsetFromStart)); mu_check(stream_write_string(stream, string_lee) == 3); diff --git a/lib/ST25RFAL002/include/rfal_isoDep.h b/lib/ST25RFAL002/include/rfal_isoDep.h index f4ebdac59..34bd6172a 100644 --- a/lib/ST25RFAL002/include/rfal_isoDep.h +++ b/lib/ST25RFAL002/include/rfal_isoDep.h @@ -857,7 +857,7 @@ ReturnCode rfalIsoDepATTRIB( * \brief Deselects PICC * * This function sends a deselect command to PICC and waits for it`s - * responce in a blocking way + * response in a blocking way * * \return ERR_NONE : Deselect successfully sent and acknowledged by PICC * \return ERR_TIMEOUT: No response rcvd from PICC diff --git a/lib/ST25RFAL002/include/rfal_t4t.h b/lib/ST25RFAL002/include/rfal_t4t.h index ff026e1a9..edee1cd8c 100644 --- a/lib/ST25RFAL002/include/rfal_t4t.h +++ b/lib/ST25RFAL002/include/rfal_t4t.h @@ -88,7 +88,7 @@ #define RFAL_T4T_ISO7816_P2_SELECT_RETURN_FCI_TEMPLATE \ 0x00U /*!< b4b3 P2 value for Return FCI template */ #define RFAL_T4T_ISO7816_P2_SELECT_NO_RESPONSE_DATA \ - 0x0CU /*!< b4b3 P2 value for No responce data */ + 0x0CU /*!< b4b3 P2 value for No response data */ #define RFAL_T4T_ISO7816_STATUS_COMPLETE \ 0x9000U /*!< Command completed \ Normal processing - No further qualification*/ diff --git a/lib/fatfs/ff.c b/lib/fatfs/ff.c index 85ab9736e..d39089578 100644 --- a/lib/fatfs/ff.c +++ b/lib/fatfs/ff.c @@ -3975,7 +3975,7 @@ FRESULT f_getcwd ( #endif if (i == len) { /* Root-directory */ *tp++ = '/'; - } else { /* Sub-directroy */ + } else { /* Sub-directory */ do /* Add stacked path str */ *tp++ = buff[i++]; while (i < len); @@ -4673,7 +4673,7 @@ FRESULT f_mkdir ( } } if (res == FR_OK) { - res = dir_register(&dj); /* Register the object to the directoy */ + res = dir_register(&dj); /* Register the object to the directory */ } if (res == FR_OK) { #if _FS_EXFAT diff --git a/lib/infrared/encoder_decoder/infrared.h b/lib/infrared/encoder_decoder/infrared.h index 086950f1e..2c76645ff 100644 --- a/lib/infrared/encoder_decoder/infrared.h +++ b/lib/infrared/encoder_decoder/infrared.h @@ -10,7 +10,7 @@ extern "C" { #define INFRARED_COMMON_CARRIER_FREQUENCY ((uint32_t)38000) #define INFRARED_COMMON_DUTY_CYCLE ((float)0.33) -/* if we want to see splitted raw signals during brutforce, +/* if we want to see split raw signals during bruteforce, * we have to have RX raw timing delay less than TX */ #define INFRARED_RAW_RX_TIMING_DELAY_US 150000 #define INFRARED_RAW_TX_TIMING_DELAY_US 180000 diff --git a/lib/lfrfid/protocols/protocol_pyramid.c b/lib/lfrfid/protocols/protocol_pyramid.c index 974bb6da6..d794bb46e 100644 --- a/lib/lfrfid/protocols/protocol_pyramid.c +++ b/lib/lfrfid/protocols/protocol_pyramid.c @@ -88,7 +88,7 @@ static bool protocol_pyramid_can_be_decoded(uint8_t* data) { } uint8_t fmt_len = 105 - j; - // Only suppport 26bit format for now + // Only support 26bit format for now if(fmt_len != 26) return false; return true; diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c index 52bff24e3..d10eaa0e5 100644 --- a/lib/nfc/nfc_device.c +++ b/lib/nfc/nfc_device.c @@ -856,7 +856,7 @@ static bool nfc_device_load_mifare_classic_data(FlipperFormat* file, NfcDevice* bool old_format = false; // Read Mifare Classic format version if(!flipper_format_read_uint32(file, "Data format version", &data_format_version, 1)) { - // Load unread sectors with zero keys access for backward compatability + // Load unread sectors with zero keys access for backward compatibility if(!flipper_format_rewind(file)) break; old_format = true; } else { @@ -1125,7 +1125,7 @@ static bool nfc_device_load_data(NfcDevice* dev, FuriString* path, bool show_dia } do { - // Check existance of shadow file + // Check existence of shadow file nfc_device_get_shadow_path(path, temp_str); dev->shadow_file_exist = storage_common_stat(dev->storage, furi_string_get_cstr(temp_str), NULL) == FSE_OK; diff --git a/lib/nfc/parsers/plantain_4k_parser.c b/lib/nfc/parsers/plantain_4k_parser.c index 9a51cdeaf..e636bee00 100644 --- a/lib/nfc/parsers/plantain_4k_parser.c +++ b/lib/nfc/parsers/plantain_4k_parser.c @@ -106,7 +106,7 @@ bool plantain_4k_parser_parse(NfcDeviceData* dev_data) { // Point to block 0 of sector 0, value 0 temp_ptr = &data->block[0 * 4].value[0]; // Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t - // 80 5C 23 8A 16 31 04 becomes 04 31 16 8A 23 5C 80, and equals to 36130104729284868 decimal + // 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal uint8_t card_number_arr[7]; for(size_t i = 0; i < 7; i++) { card_number_arr[i] = temp_ptr[6 - i]; diff --git a/lib/nfc/parsers/plantain_parser.c b/lib/nfc/parsers/plantain_parser.c index 799262171..c0e2a0947 100644 --- a/lib/nfc/parsers/plantain_parser.c +++ b/lib/nfc/parsers/plantain_parser.c @@ -79,7 +79,7 @@ bool plantain_parser_parse(NfcDeviceData* dev_data) { // Point to block 0 of sector 0, value 0 temp_ptr = &data->block[0 * 4].value[0]; // Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t - // 80 5C 23 8A 16 31 04 becomes 04 31 16 8A 23 5C 80, and equals to 36130104729284868 decimal + // 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal uint8_t card_number_arr[7]; for(size_t i = 0; i < 7; i++) { card_number_arr[i] = temp_ptr[6 - i]; diff --git a/lib/nfc/parsers/two_cities.c b/lib/nfc/parsers/two_cities.c index 2f4b7dd0d..335248b2a 100644 --- a/lib/nfc/parsers/two_cities.c +++ b/lib/nfc/parsers/two_cities.c @@ -107,7 +107,7 @@ bool two_cities_parser_parse(NfcDeviceData* dev_data) { // Point to block 0 of sector 0, value 0 temp_ptr = &data->block[0 * 4].value[0]; // Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t - // 80 5C 23 8A 16 31 04 becomes 04 31 16 8A 23 5C 80, and equals to 36130104729284868 decimal + // 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal uint8_t card_number_arr[7]; for(size_t i = 0; i < 7; i++) { card_number_arr[i] = temp_ptr[6 - i]; diff --git a/lib/one_wire/ibutton/protocols/protocol_cyfral.c b/lib/one_wire/ibutton/protocols/protocol_cyfral.c index 0c44c2b45..a5f459bc0 100644 --- a/lib/one_wire/ibutton/protocols/protocol_cyfral.c +++ b/lib/one_wire/ibutton/protocols/protocol_cyfral.c @@ -181,7 +181,7 @@ static bool protocol_cyfral_decoder_feed(ProtocolCyfral* proto, bool level, uint cyfral->index++; } - // succefully read 8 nibbles + // successfully read 8 nibbles if(cyfral->index == 8) { cyfral->state = CYFRAL_READ_STOP_NIBBLE; } diff --git a/lib/subghz/environment.h b/lib/subghz/environment.h index 5f8fcf1f5..e994f7c97 100644 --- a/lib/subghz/environment.h +++ b/lib/subghz/environment.h @@ -26,7 +26,7 @@ void subghz_environment_free(SubGhzEnvironment* instance); * Downloading the manufacture key file. * @param instance Pointer to a SubGhzEnvironment instance * @param filename Full path to the file - * @return true On succes + * @return true On success */ bool subghz_environment_load_keystore(SubGhzEnvironment* instance, const char* filename); diff --git a/scripts/flipper/assets/icon.py b/scripts/flipper/assets/icon.py index 235af7b05..ed85b024e 100644 --- a/scripts/flipper/assets/icon.py +++ b/scripts/flipper/assets/icon.py @@ -104,7 +104,7 @@ def file2image(file): data_enc = bytearray(data_encoded_str) data_enc = bytearray([len(data_enc) & 0xFF, len(data_enc) >> 8]) + data_enc - # Use encoded data only if its lenght less than original, including header + # Use encoded data only if its length less than original, including header if len(data_enc) < len(data_bin) + 1: data = b"\x01\x00" + data_enc else: From 5e74622b2a7bd47749bcc3968d2312e0f4af7cd1 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Fri, 6 Jan 2023 18:16:58 +0200 Subject: [PATCH 4/7] [FL-3072] Add the sleigh ride animation (#2224) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add sleigh animation * Fix manifest Co-authored-by: あく --- .../L1_Sleigh_ride_128x64/frame_0.png | Bin 0 -> 1656 bytes .../L1_Sleigh_ride_128x64/frame_1.png | Bin 0 -> 1754 bytes .../L1_Sleigh_ride_128x64/frame_10.png | Bin 0 -> 1494 bytes .../L1_Sleigh_ride_128x64/frame_11.png | Bin 0 -> 1637 bytes .../L1_Sleigh_ride_128x64/frame_12.png | Bin 0 -> 1713 bytes .../L1_Sleigh_ride_128x64/frame_13.png | Bin 0 -> 1585 bytes .../L1_Sleigh_ride_128x64/frame_14.png | Bin 0 -> 1634 bytes .../L1_Sleigh_ride_128x64/frame_15.png | Bin 0 -> 1771 bytes .../L1_Sleigh_ride_128x64/frame_16.png | Bin 0 -> 1681 bytes .../L1_Sleigh_ride_128x64/frame_17.png | Bin 0 -> 1503 bytes .../L1_Sleigh_ride_128x64/frame_18.png | Bin 0 -> 1663 bytes .../L1_Sleigh_ride_128x64/frame_19.png | Bin 0 -> 1661 bytes .../L1_Sleigh_ride_128x64/frame_2.png | Bin 0 -> 1681 bytes .../L1_Sleigh_ride_128x64/frame_20.png | Bin 0 -> 1559 bytes .../L1_Sleigh_ride_128x64/frame_21.png | Bin 0 -> 1542 bytes .../L1_Sleigh_ride_128x64/frame_22.png | Bin 0 -> 1736 bytes .../L1_Sleigh_ride_128x64/frame_23.png | Bin 0 -> 1621 bytes .../L1_Sleigh_ride_128x64/frame_24.png | Bin 0 -> 1628 bytes .../L1_Sleigh_ride_128x64/frame_25.png | Bin 0 -> 1671 bytes .../L1_Sleigh_ride_128x64/frame_26.png | Bin 0 -> 1636 bytes .../L1_Sleigh_ride_128x64/frame_27.png | Bin 0 -> 1621 bytes .../L1_Sleigh_ride_128x64/frame_28.png | Bin 0 -> 1099 bytes .../L1_Sleigh_ride_128x64/frame_29.png | Bin 0 -> 812 bytes .../L1_Sleigh_ride_128x64/frame_3.png | Bin 0 -> 1651 bytes .../L1_Sleigh_ride_128x64/frame_30.png | Bin 0 -> 536 bytes .../L1_Sleigh_ride_128x64/frame_31.png | Bin 0 -> 492 bytes .../L1_Sleigh_ride_128x64/frame_32.png | Bin 0 -> 503 bytes .../L1_Sleigh_ride_128x64/frame_33.png | Bin 0 -> 897 bytes .../L1_Sleigh_ride_128x64/frame_34.png | Bin 0 -> 1490 bytes .../L1_Sleigh_ride_128x64/frame_35.png | Bin 0 -> 1741 bytes .../L1_Sleigh_ride_128x64/frame_36.png | Bin 0 -> 1538 bytes .../L1_Sleigh_ride_128x64/frame_4.png | Bin 0 -> 1668 bytes .../L1_Sleigh_ride_128x64/frame_5.png | Bin 0 -> 1555 bytes .../L1_Sleigh_ride_128x64/frame_6.png | Bin 0 -> 1521 bytes .../L1_Sleigh_ride_128x64/frame_7.png | Bin 0 -> 1642 bytes .../L1_Sleigh_ride_128x64/frame_8.png | Bin 0 -> 1694 bytes .../L1_Sleigh_ride_128x64/frame_9.png | Bin 0 -> 1605 bytes .../external/L1_Sleigh_ride_128x64/meta.txt | 23 ++++++++++++++++++ assets/dolphin/external/manifest.txt | 7 ++++++ 39 files changed, 30 insertions(+) create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_0.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_1.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_11.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_12.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_13.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_14.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_16.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_17.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_18.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_19.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_2.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_20.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_21.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_22.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_23.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_24.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_25.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_26.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_27.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_28.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_29.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_3.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_30.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_31.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_32.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_33.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_35.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_36.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_5.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_9.png create mode 100644 assets/dolphin/external/L1_Sleigh_ride_128x64/meta.txt diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_0.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_0.png new file mode 100644 index 0000000000000000000000000000000000000000..340ceb47f99ee859720fa8cd89aa51f234799bca GIT binary patch literal 1656 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png new file mode 100644 index 0000000000000000000000000000000000000000..890ea5d70da8bab182cd7d25c234a2e2a1e9d610 GIT binary patch literal 1494 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_13.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_13.png new file mode 100644 index 0000000000000000000000000000000000000000..6eda42b5d814555b5e795572face69b78fcfb0fd GIT binary patch literal 1585 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png new file mode 100644 index 0000000000000000000000000000000000000000..554a177a93d8203134179860bc35ac3899407026 GIT binary patch literal 1771 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 b4o3I~m)4F_96F#z00000NkvXXu0mjf;*=8M literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_17.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_17.png new file mode 100644 index 0000000000000000000000000000000000000000..971e8f55b5c36b7f5e24c68d4adcd62512986417 GIT binary patch literal 1503 zcmV<51t9u~P)jd3cN7lpBDK~TTc%nL2xyv)oMky*1pDDh#Ld! zR{rk!BY4vG;xfQb!#6O@Nq83~QyCz`2v3TLD)bnChaUehz;#`*#NiD*`;#!jHh&m^ z4rG;CPu+dAd@@7f+IrV`wbojXQ2~4=(8l~SNG*mLAR76+%s34d@W~Om10w;I$^oqU ztbxBQa0XI2fK{*U^W4y1!=Hr<02;vGsO6FFJ$Fxjs589F8NLEP9|l>G?mg2Ik$pZ1 zt7JPN7rNyD0KbCGW`dKQ;K`r?{Nfc@Im!g1r-f2C2O@&zC9Y z;bm3QD9oG=vZo2@HfzSc0j`J6_vtJX{xlHao+C*0GfxvEOt9*1;@wUVCIfKr905R% zY_DE>rX^zJGYBd-A5VII^3hhq1?)?qsm{)+A@i3H}G--ZN45%iSV5a0B|?3wl`;@hAO^i(HWT| z^&{(AYU~{h5TV3~@o9dL%>}Xy;I;E|3@)Rzhg03`EdRNhpM+?{^T^`w@cm3B(K#jK z@n@}nWP5rX1MtOD1<-n}AGXInQrp=|LK%1&HLA4c6O7;5f*gQCiVXfgLaMjMrTsFI zEy<$N#{mYVz=M>0S(!nO7u8$_Id_KO&ux0m`$bD!W0 z+ZiBA%cEmUiqDJ@VUFFApDELF&vbyue%7-MSp73j5SeFpq|YvweHfJ80{A!@ysaUM z9{N17)bwC7Ky)wUii-4+q9!s=6jsE0b7^bYZN#3kSN;pw4?{c()jZ8o@(h~JjF*w& z9hm@S)z)J5zg+}zkBZcLeIkk5ccI4KoVU_MTph_T}DB6~o(RoZ=Kl;5w4t_c-f6I$#!buo3ha zpDSZFs1snh(&*9}dm{c;wgT-@C*TfY)zLP5QPy0Mc6?k3BTJ9&u|0cv<+xtC-2o&e zWJS^p9dV{g_UbiuMApjr(+m)aI?aF)29U;z&gIE9I#*_#462!98Ut_x;$#`cWF5e2 z%NNlrmp4x@X6GiA#&1oy2e8oE{Y#nI{!-uT(tiNL+i1zqh+LIGS$-nyivQ$2Qw}A5 z*&M*y*QIBxFI@;#npLm0iM;br%>ddQ05wS`j#HHghQ1ZdDVGao)obI8)bsgAQDGnp ze5=BDEB1J=41X=03ZzBeifGbgLpiuy(Z00(3P<2HjwPr0*=6c?0owkH0`CBilxPwr zIcr5y8G*$J{CRdc<@*3!fisw4bj(Qq*^VI1vnukt7$CC{X<>*R+e8m`F~UkGD06@a zrP8*v{&cL8Q_2yhmz7(9XIO2|>k(mO3)q#cX0akC_zyu3=q+V8lFR@A002ovPDHLk FV1g}Xz1si) literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_18.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_18.png new file mode 100644 index 0000000000000000000000000000000000000000..4026dc37364f9ff38c20c0ece6b94777fa4c8810 GIT binary patch literal 1663 zcmV-_27vjAP)Fbt%F{{Jtl4+Xaj!(*t+$7X=QYg-a29wSO= zdJs`-tr6om4*KHv;3b_;MATX%{p^l-!W?$s#Toy!%zxT^dVnOs(UMlX4cS`zI&ULB z7~pK1s8$|YR!U)^E zF@PK>x>h6g?5*XK8H(rDJIAB7)_R;ulrw=h=9fX{VweHY%-7dS&`1TJoRMc>B%w+* zfcM^dj*Rui`6$%@-h0c@+*UjK>-cNoAfiU}PRrV<(S4NZcXjq%_V6?CM`kMjUAcO= z^Oka#PFD!Dj~al8-0ZgQWqYq3q{syW;i-6A%B`bd=~#pbxGneX*&SFx^u>CF-vyvs zH|?wn^=ECrfv^^@=%qZtL(T)x%;!GdmQsFZf@Mm5fBTsP{B0mnp0!9;duIlWRXd7Z z1?d$EI~1)o`cv&dl-CFz&;z3tneWNd3^pqJF3x9^7~rFGVHc1+iwuXfiTYJ>Hz)R@ zwHDU^$N>7p4>ca@e12c~F2SoU(EHy|7w{a10zeL?w}jH5*-2tmY5lj=M(d#4SMQ&S zDFMLHfq-N-I;I}i+I{FY(RUl34o_wRlNm>9fDG2LvBwe`l@rJ6F?G*8?hfnFj%YFi zL_&!^mx7GOWu{wgkrT>l=11oocY|ie5(A(obqG8ruf>j6Tk8bbP0g?03-9Yb^j;Kao)bconzR&VpQ{dklfFfozLp{zz?*)dZfr9*kx;ZE z7@x;G&3Hi#;MF9IUC!4w*E%gd36z)}XOd#kjGfIG)tUndmYvQgqW`}Q^bFjKY@M71 zgju}I)4FAuKa)|k%~v~L>i;&Ef9?FqA_n!^tPX(cm^+t^UZ+`@k@@nOY#S~CmQviA zKat`Hdio#7`%y3osdF`BcLV{;@oa=FZ+eW(PA~2#KiXKCQG0`+*ZHhlFM2X+HzTl~ z@+$S0^RR5`CCNunTl{aWPu?K9kER)~y_SPGG>hhpM!oSWPKo;UjytkkAkQQFb#DVJ zPK_e58U&s!6pge$tg)mXa_!=_DD6i&@pw{BM!#n}cZTzcDE?uHN8Oo0EdxhmWjc8BP=-NumE*~A z?{G!zU?Kb`VHq7~5ob}R1QHNd8{`=>hYT|KuHfXz`KYZlNFy zHA~6S=wXrGK4)aQoM&cdOLyI`YYE64X;D8@sct)xvAyGX0g3eAxhzDwS&5*7WoZo{ zm68V0?{)y0Xv8`ZqgYrG1X4aq`+8219o(9rX9@6tP!PjgE)W@BaTB3e9-@zw(L?7_ zzY+Oz9BX;7?w9>Iw3-XBERPs2P!jB0`R?<|Hn0*1S-TNA6^E{L{*0iv#^W&~=cCDx+Op>y0G;@#f$l=* zs{+9*2t8RI8+Z)aTJgV%jf6+-dJSatzapKF&NCaYXI!)n_zPhwl15tgi)R1;002ov JPDHLkV1gE38lnII literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_19.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_19.png new file mode 100644 index 0000000000000000000000000000000000000000..b8cfa50ce63ac29e44091e6a3257d7fa093d7e81 GIT binary patch literal 1661 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 JPDHLkV1f!6*p~nR literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_21.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_21.png new file mode 100644 index 0000000000000000000000000000000000000000..d0e44937c1dc542699af72070b551bd9e9017462 GIT binary patch literal 1542 zcmV+h2Ko7kP)bG@H6lRj=2)NJ9B%jhyW{`3I8MgXhfa5rPjf1f{ZT=my z;jXB#%?AU}g{BU!9f)H(ndQYAgn9t$KJECA;|Mts$4BSAsN(>D0q{xPJacF7)x{l*!e!6K z?!aB1;XCj}vlg#B(`WDL6=4|=Gd*%nu(Olj<@_r=o&4$n0Q4FjWdiBGHs`No+!a6* z_?zDV_zZ-urvqG93J5COgEv@ep!PGr3y?Z{ZJfpoJTPYSX5c~kqJ}T)ChzREtJNcD z;6b7tD)GMzV@T`q{ry$AzYPSK)3wF~GlrN!V~z+5?0V9;(-XuP!)5?!0&cK_3A~1? zo)1`M##`pRxQp`~<#{OZ1*jxh#&SL&+wseyuLoc9Z*s9us>?<4J< zXgrG_-&yAXQCRb>GX`m%<$x9S-y`=)5CfnK+=$#^8tY^pJ3YtJl0Ke$+L*gSvip(% zpwNsbJq37G$$mG%$9fp=$<7C0CxKn4&~v7-)DD_7c@Y_W&Yj@u$ySwsYL_0FYn?Vm zVKLGjg1~b;|6x0C07M(a*@5Gnyyos~YX zbn6jlOaqYrk!n_MSDC0}xR?F`0t{eA(aH5ulkI6TD_1JFqY@n?9lNPpb7Ib zK*eYg$9v^WjTYFa=V_+f*^wY9_W;$b&&=n20sr&Ji)8lfzV#kJuHkq+)A;~R2SF>c zN*fcAHkIJ6~Q)C@)hFbHo>vjhN|D5YZkz#Cux&&y{vNJfiEN|JcYG|ZZpY3dKFj#2&MUZBo)yS3> z)UO&0SG{`C_I0piNHjElewDI;K*pjvUcq_Uo*?RcfF=g8>Wx;LvzfaJT;D4=HR61_ z2ZSdeS{Q)SDZeKL$61UpGs@i1mclJ_uYJDuvq_c61f6f{fzFMujRm7;<*$5(3_A1~=7$(`X0MzHkMSd{MB zW3T1^8AO!F>LA^@`TRzKif zOS(_dz5S@pne}JR=ek(-I6VLmQZtxs+GgxRv2y#F@{ph14Vke#N4usjBJV8R|W%Ry$03c>`-nor4 z8G*mgI6o2$w+R5mj!vG-+Q|sshLFznIA8s|D*$#G>kM42N3e8?K0UQFz>@=E7c@qo s@MO8wEaGRUd>i<}2Df3xmfk+#A2tH&C4KmzjQ{`u07*qoM6N<$f}r2zivR!s literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_22.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_22.png new file mode 100644 index 0000000000000000000000000000000000000000..fd800b8b936fe6d4b2dad012c12f0ddb54903236 GIT binary patch literal 1736 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 Tw_x!v00000NkvXXu0mjf>uC`M literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_24.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_24.png new file mode 100644 index 0000000000000000000000000000000000000000..05c5e969d7c10cb70d949d7d28c0fdf9b2c51c1e GIT binary patch literal 1628 zcmV-i2BZ0jP)e#l8e)t}|xbcaIT5BZB?uaMoumZ0h@lT8Vr|DBcBnYbg~R+*)g`VpJlX31nZg{~v?Q#V`XzBVS&T(`aQ}z$ZuK85jwu zR1RR(cOk6$9}8R$sT{zn_Z+8R``-`Oz(z!kh<4GLxwp#ixU;y+9)1S?UItl_?%ktu zUKT;$DO@2xSY`Z6=qhsnqW^-8FhR%NqW>Gf1NAK{h&~;GM|Sq=A(B7Hwm*#HgQcx^ z0ntd-n89O+ECv3YU_RbfC1-?L3=mm1j)Jy)eY0bQ-v$!Z_WEAVP+^0kUbM_`ggfmZ zT5CB6=orZnWIx%%p-R+dx47SevIBHP4}p*aL@3xhYw(O!enIjjPS9GTSLH!@n#WK0 z&}m=A_-LFibj9%U8SDNE`}3pGbu^Pegvk4XK7ePaXryKH1bH}xo<;ZH68Rh{2k@em z=yPIcXW6J9&naYNTWkJVyOUKwPW~p!t>vhF=ExTr1PL02H@3I*MDv%sgX{4zC&a!P zWbM&M4|FW2fr1&6E+e_(fDz`%o@`zXk=@vC7Z5$~_v*CvT(JJXte559xYd(IO+Kx! zJ2*k)aE|M?%K@w-+$bW+AIc9Z)X%S@he{(SoM$2+2iLi2q(M%tWBl8JMUQ5mqSo1TAs*$sd_~HZdKTD{L0=#ct=RvYgFGEy}l=Tc< zjeNvFNRA3dBiv&|I1kOJjYd#pN$}cQGIUruN7UmZ<42swVj7SAIF2xO#tB}YfHP5v zjtDF9zCGj1|09v@)m!&mmRW20Oa?HMz{+}tsQYNhXtNZqPM&2vuU}r@Wa30W3; z8;I6otxc5ry#6xJddq}tUzx;g4xrVK$Wf8oTB?YG=KxwXXda$%f{O7o2A9uNBG+qI zVHEDTD~ylkqqP?Mi5RrbO3%`OoCO)i6PUM+7(L=xE7#x(U03ClbA$r3w+($FNIHQW zKz6FJf%ObmHUn+qGR_oQVm{WSrm%SFW>V z11kXK{Dwagh6b}HziJrEvEd(MW&fdaFuHWX$R=a6V%|kROr#OI|IDYR5dR0h$u3&6L z_sGzhpeubf%tCAX{ke38Rr$dTbbW}EqUnt2}eC;Q?snPD{p z>;wRjuSJWcAVS38M>E1~Wah?W)on|>4n7)ESnWOoYf`K>vrt(cqRR)WFUd%>|EbtB z5%F47Z&AFq_O8--#*XI*;}a47W{}n4s_k3U5jbYFJrW}cqt$LmCbR@ZjC=)0SV`wI z5!ET;B>cIG{-IwAy_zFjC89@xj4kgg#?LrI_(P#xsF5}^@D$^7pu?Qm#-DN!lyL;F z-4ig|=`(A7G#PetsmeAms`x5aI2%Vff|d|!V`*%l?T}h)V;>2NwtKoG%+^!(SLrvJ a2mAw+lNpPBDxqfp0000(^U_4#5#o7iWbZG?H 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 RwzmKP002ovPDHLkV1h+$`q%&f literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_29.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_29.png new file mode 100644 index 0000000000000000000000000000000000000000..c6caf88d94b0d9536098767bffc4c66c4b9dd2a3 GIT binary patch literal 812 zcmV+{1JnG8P)?aEn`=V06RJ#06u%2);$!TqlGheHXR&I21tN1jb6=91hC-v zo+yBAPcvtl0$LG4iBuHWEC&0GB!1olSRogEyf)evfZhoDlLcrsVian7HICmc3$Vfr zXK9S2M;Z<_8Zg4lnW}9J4*@%Zi+@;UvBY>WKxa2IVJo>49hn{pIocVJq{DNH~d&cdy39L27_9Y z@YYT&sUlykKvvz0APp~AC>W{7Y*A{fDFBHxZf6n9F3HQsPF)0u>?nd6v%W++c2xmL zoc+7PHP&h)0Ki^RfFr#IP%Z%HJ&yv=68<&xQ8FF@jN<|@jtjszj{@+u)dt;%L0Z99 zHd$j^0n`ps{CN4xmI7#{u-1>4Q?}a#Vw}3uo5WE>M}V4hddi)8uvTM`BTfLlWlxk9 z#MI8m3YQZx0>Ey}BvlY&z7xUS*PQ0h#4YIrQ3zfg%HKFsiV)ZZpmqJ#D%u>@EP&hi zQ7HS(or$msK&$fs=)*Rr2)26<;NV~pfacjh3$7^O5x_Vu06}m>0T+OAmJ7hwe_410 zFpdjA7_0XIE&$`W01OjCZ~<7y1z?nzjky2}lBVM9^#IN~N<}DQO2V8rBUMDL+DR9H qB5J?()q3U$7l1-2S8d+}8s`u2Q7b?iK{5mY0000%G@RH6aA}XbjzIR33VGc9!;*7sr=HG2TEr=w+Rw}J#8?u!0IBz1p z7+|&WSC1dTotBp>1MCei;FvSvDoh3$VB5BjkB2*D#3-~le}x@C7+~ABJ#w`&^4=99 z`ND!4&8O5#wQi^I#*QLiFjdTK1ZP zfa9%pXMdacr2yTFKq4Z|1d-Z+Xl&v5OE^QIy;%{WHkw9z22u{{_v=_?1L@nhen9j| z=bnGK6^H!HG?WWn>1 z>gjy#%UFT5xA3V7t8Z&4|GeO-ct!S+$)) z>vskjCa{(VeYR1nF|6-*V*+c*4gV(40%C&Nz_OiJpjkk&&^C6Ye+_qF0(8*#_Burn ztpj9xuU)jiY->3ku?{-p@Pg>7g<)hKX$HtFH2~^>qJ1u04NJ8hVOJ+wie2rY!f9y!BQpQX z`CH;NFv0*{AM{*Lv=u13MrEuVV`VUCM4WH258pfVEx?o{iq90h*j1 zK;N?ruN9jOIj7l~yn;D+u827#K7JMKIovaB>vM?_)L(-P1itpr^J-^DgTccab+9u0 zXlMs9B6?(kO)~=j`wUK->3kxw6Uc@@owI_&3aDkrf;X;3eYHMF4)v@xo zBLZL-W*8PJ-y+L;F44Mv&kBSQ?8S0MIG<>60PI4IV$h=h-ar^t7@nCCBJI0$IwAme z;RETsR#=Xh#u*qP8ziz{i{6I@z&^-8IzP(!))*@oL4R*?ekM4k1b|e8=vX=*i4}|x zEeK`bnzOwC=>9KatVtlqnt;F0$mmvB`py_9;{2#i=yiL@K7co1rofXpi}P0kF@l8m x%B{b9tT2oLv|<@Xc<>2}5#;>{+;v|B#vk{1sswN{smTBU002ovPDHLkV1h3f3a9`8 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_30.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_30.png new file mode 100644 index 0000000000000000000000000000000000000000..8710e1a0a96e97627d7f0938515217a35defa493 GIT binary patch literal 536 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!3HERU8}DLQk(@Ik;M!Q+`=Ht$S`Y;1Oo%( zDNh&2kcv5PXWi~QtiZ#Xc=iAP)c3-nBBkcM7RL(G?yZ)pws;bD_Ka6j*4oY9H)hSh z$r^L1k$G|)3fnD0Scemq{ls_4=-(5G6>5ajkV|uSO zR9|r1$o$d!KksJV{qAxZ?YO@+F%7cq|2rp|%GFEP_I`XJxZ(GQ+qZ14-?(?F_Va!@ z8?^)P4(Ho^J@H*y(8lAxZG%<1;GU%wE(}xe-tVaR`1xPM;h*()SOcW%OzsI4uviFc z?p@BZj%ma1#~&s&utZ4kbKYS|_WHoUJvTVg<}8D!Pglo`ylFvNzjmy>`|2M*fb&SbRsVP^tleqWl& z5Vv&u=6$AiuS;`<8r*BEUtiz!Y}?Kw*BL9W#3%og+TQbUHG|AQ+igiJ<~o-$@cozk z#FOQ`g6qITd;eUHcIJW~=gUl5<}1&?^YFW}(z1>pKc-)M#2`~Q-_ErlgL#F&Wz$-L zE~XDJ-rv8cd61W3bKiqx2D!hFXSzRNYxr%>(EqRSsVAgBp9kDNL6Br*LY(r+3NZkcv5P=bX+vY{0`Z-Sz+f=~tZ3qOI3bHq_YK zGJe((W9VaTNM?Kx!C*luGeSja-n>J)Ae{+~9RXMGp9e7?G@QDsuPV0S)v`DHJ+&RO z-7X_r&4Qg+o&y}`a@-h_B>^2Rh9|=oT}B26!PR2 zN4hnf3v0P*xaEQM%kK>CC5qqH)t*&f`*-dprk=S8VxQi4Em^}bckhMm--R{x4+I{) zZ0%Ud7o literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_33.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_33.png new file mode 100644 index 0000000000000000000000000000000000000000..94b79d2ed9aac30fb2e73993906569b17f80079f GIT binary patch literal 897 zcmV-{1AhF8P)cfKZZMOCo}i?TbIa*qCpZXK-ys{c{8fAxT&PwBKpa{;3xIuUfjSa9;#ss3kyp#qCG0HA!nr2mYzGx=lVn za0kHe;j87dIf8_cB&-AAnM#SEcR%-n5`{5;?@B)L6s!aVz(gaFB&c#KoZo^4E^3`RRd^5;4rKt?gXHn zNmPM|*XA8H0yUNhxDtT0>TdMz#nri$Bj9n~unTY_0O?H91idE&%bD*ad_QyiTftNy zM^2zE4tqkFae~N&-zUSD7JgYCZeYn0Ag$Khy;R~QZF|qvw&^W5t*=`PK!8m(0GC*c^`5KtrR_&s z%GTd)H9)8$P}-ilheNxiOb4(YL}x3=F>;VqL_hh%xDY@?o6M6oe2WXX`ZO?7b=|g+ zQO4WDl>;=SHGr3)b0Y9E#vaFyf+>0*z~FmMBUuJKj{A#C0RZ&>8%S@qNvzl+_}U_n z+TmD@#R9hiXy8E292(68-|{%xvcFke)&O5dpY2=pDY<`Dm`>L+?i~QY{_sUQ3wO-Y z)2E6d1kkV!5+{V6Njd2N?w0_7eaX1(oL_>ggP_yAmVAH|z<=Cux~Cz80CvyFmqhRn X`cZE4+77BK00000NkvXXu0mjf+t!%6 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png new file mode 100644 index 0000000000000000000000000000000000000000..ce0e210f4f2202902eb5e63bb6ea0de06f4d7e92 GIT binary patch literal 1490 zcmV;@1ugoCP)(^Xn1&= zKJWX!<3sQ~&(Znj)@0}XgsuH|djVcVo$v{p-vR9j@H|iI1fEulu4}LWCqUL=Rd%vi z6=X+Ex|84sZIH&z$mVt5tcmx1Lu-AWr)@}r6JTODf$9v;Gf|RsB|-Zf;08EHMV-fy zq~l@uBfw#FBf${@yzhHe{vva0_oAMm6A6wgz%3$pH{7$fZzI7G0??AKV={CD9Y}CA z1ax>{2`eeBbs`CTyWep<1au^U=1^(9XcfCfglknmmoPw2{#vik@26uD(?M zYGNt^`p`U;9|)lvDQ&D3SF4pGy9FH-Y$E#%$EyJE^@?O`stW2=Zzp8Q*y3G)=jF?; zqYy86PT0)a_y`5?fHHY4!Lkpjm3nA&3JV^T5g@zC(#e-Ha?4iLDXp+|V-0N;K>9jH zESFgAa-@__tfWNM$Pj%8EeOEX+}6pLHfX({I!&wfEu45woWbtgB?Q=sv6cdGO29dN zZR2v~sJ2Gh0;cnR6Iv+1gva?(S5Er6+Kz^S2r9Dw6UqW_1xHVRq&qn8r}uJB*P2eA z)1O`YNu@v#1gZcT=g+U4#B*4iIg&z1*}ru9wS2+~1Jy5vs#V}>2w0sJ`9Q`xetlVD zy9`I?)5eSD%z@?^R$hnPG55b8YU2+s(^Bihdk&+GfjZXp1PFHOG%j3 z2bkB;Yn0ZVIn@#8UsV8l(yFl9mz=28|B~~o$~10%Z& z=WAr}K9d!Wq{@JEvZZQsT-AQ3KM_{8F!hu1kA9Yn|HIc8#D6 zqy)|zVl3x-a}_PKkB;HiBbA`sSjnb!PX=3`3EWYENOBgDKTl&uQiG@}WOEa3tcHrj z_V~xbwZ(fRQnG0^bgZ$KUF~E_G20?R6r8f`Vv7LL2eM)!Vq;dj*6H`Iy@CYN`aEdS z2iOIz-`5ko3y(d^`JPOiI#bK&5ehKpL1LQ#G|}Py%xjiRuZ}~2Bf^doJZt0fk6jX= zl#&DdWe}IaQxc$AY=IKNl5)>I(zP*ohgpfH_IFKPb^%KQBP9)osz%2OPmbWJ8a#1a z>Qh*@HPEoxh5(4zHtmd&>(E8!=H`j6-J=jz;^!?prNs3k%RmW~(O&0Bm4!FE_l(OB zTyuW(86aA!6-1Ew2=vu_Mk{wb^{&UZWN#PQ6Y3eqm8yvK zbLC)oU(p2M8X+944x0D=F3^bJsoS%p*d;+pA3$n&>_{LYhE~Pl)h4HUl{vtiQ)?Ef zV=EMZ`w#0L-;RFHJd&a1hb0j(up{i>NCBPW*xPIb9@8UKQDqH3d0??9Xj2IDC sbaVa zMNY9&gk-#G{ zYzvVABnR^vt78t{aej&ez1IgCp}eY&Mjk36sjSS4p(`u-tBk+H?&NEI;JbmS60G_G zIl&cg1b2?)yXHpq^8Y9pMQ7w}KU+9ThZvdNhSYv%C%7A5{6X|o$x^uG7-%1F$=2Fj z#V+3khZJBPs!(_fM)NQNqz})bk0p~Ib4!l%lNsV|HISUZGQM_=&`UTT}ALTHaYwrz;h7PMW^UJ1eM9#(y^lH z0F7a_{|PWtz=DSzyhp`|qR~qYDJ?{cJkR@rZYFpF=A3vJkclqOV7!l()1{pOy3fFp zx%(MH6>EKN2B4i5v@IO(x7yPpO_R@Sfhv~lt80Rtz;{Js1X2JO!k9xkE$kGGf70g6gb1@8f0INEj4`zJAh zE7FDS^I;dzy-;aCkG|(Da)!dK!jXi<5=!ozz17wvGiO4LyOEn0U?Uh64 zQMvM30_E5Ux*|Ny0F`K_i_mrgkujolt3_ml2`nR8I~BxS60V4J7XxHGm*;RQ96hIt z_Q>Y3X|!c`E}%DJ&B~T{VT(#yfr_Qas~W{kc=z75N7S zh(^EFKAQt6duX7nqNN-395IV0Vj@f+_0Krp=?w5!FR>G{OuDe-jZt%)NPphm8~L4) zC#9sT8Q@R^b{f`$t}#*0_4e6;juDg5^>mMOy90Ql)=~;;j~1!P_eM&j{h7{y^w7ot zROx3rf=01Xa5;!(EhoD>@}o{+IZK)WBt>?@%7a#4PkWT(t@a&E5S4ePGkDjN`aM7v z-1zJBoO)HiK$WaH3zz4S?^Sx``_AiHCx1@Y>ROM)0BbRpvUyv{AiNE71a1szoG3GS z7=b>kGQktBYpsXZ!4wizYi*I@k>NAQrX}udS_<01I33Kub(Ug)4&{60B4f}t{8?o@ zBrC!IJ6@^YBK=eb=v2H1C^EYpPf7#)`3^>n(m7wFwT>~sFM^PAJSm_G?#UVFi}c}X z0X^TrxZcR8Yl$$z$5Aj+NJhC4XybXuk{mYDUus*S*DL7WT9bL5^YecMSqk({zzd{& zkMTY8@G_TY9aJZGM|$Tx4zGiyMQe(!ID*AE5+n+bj7#N2`>noL89mBoAqL=`EoD_F zi6)#ur7x8+M&1#2k8n2wtVLOQg-@=c%&Gx99DvFi;~GXls02ZDe0mN2gNQlt{|!dp zd-HD=oyH%-{?VzY-&(VEUH#}a(qEESW`HF$62W{C=zG*AYQU~^lkFQFV+ j=ym|Kh+pJz1ta_c)_1ni5SD6$00000NkvXXu0mjfMD{rX literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_36.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_36.png new file mode 100644 index 0000000000000000000000000000000000000000..1aac855834c50765ba9674b304504ad9a2ed2d19 GIT binary patch literal 1538 zcmV+d2L1VoP)pZ zz;5I3Uf+W!9WPx5cn`dRW3Gg|Fqz5#$8ntgS$I-LR3YR19dA0nkJeh_*{unSMp>1dA8d>;z+W%(5#G$#HbUp$ zr9iq1Mr`>}0|0zxZie>xrX-m^?8gEBe+!^Y006EdK@?~N5#q-H*TDe*4dCpsUiOND z6vs>BuKqOfO#%8Y0s#Ox6L@L^fZD?Gn{b6d`@@X@^S&LsY(Rbc$2$Pd zbZ%|3_ri$Y%WN_`_Xiu)rthZ!DQm4v9(h}6jgc7R7XxTrWKudILmqLiUZL*U0G)BsX*72wptv5z-et?qu#T&EPax zaE|rG5ywicwT{(qWqwrtQ_x!TeGA}y8WUJg58f9_+WOXbt`zMbapsem0R8t-18@WK ztmWSfD5wLetlpbKx(1i!VFKxCf$y_dV@TIeV*+lc69Y(Z0eepbR!tx+Org~nUM09J z?KQS%eh*Jz0?j@jKL`Pc(KQP9qIb+>*Oi>^2^Q_XCz!5|o<45|hz5XEFpi9k$m8=o zPUqvZxI+-QoKD(TU*s84BhY$4dhF8*Mz3sMcUb$5)C5v{)tW#&+r6#C003)_0j>mh z(x|~_7IeGWAmwb00i-j|V=!Ln_rNor_hCzAMhz`JRalvyZBgzDXiqyOEs$RXjb`2m zO-kACWdu)QC>4IAunNghlsegB0L)@;_|>2r;~8tQjFstDHDA$M>t$`gZ85!VY`3FX zGn!>mR&fLM_S$Plb@QfF&5fMqmFu4oooc2#98U1Mi3aEz`K{uU5{r3^K0YyY18Bg$Lr@?afx#%e}DJ!nhym63cwZnO+$F@Q7<;2DfS_tUdWx0Q~7p0OmOWQT3Ik-%^%WUCiYwO$mt%lTAW&S* o`8%+b5oR3$tFq?FSiuPY0nQUmgswmI@Bjb+07*qoM6N<$f>dYDyZ`_I literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png new file mode 100644 index 0000000000000000000000000000000000000000..ef5e2fc53b5ac0d5110ccf4c1b96219f6c65516c GIT binary patch literal 1668 zcmV-~27CF5P)e^kD*EFS~Lv9sEdDqpe=U;_ir zZSQKW_1=3MxUQ>xkw@?SQG579o!{EO1@L=M#3!ub4*ZO0eNxmS*s-3|vL8u;Uj{gZ z3BH+I2P7EZ7+`k<@79mtlY;c>;A)=Wn4K8gIQHqZa-czFfCwXeQbtrEb>`jQ(Kdxoy(DAe;R}A zUf(nWpqbA{>FB5#M-BY;47HdExKIyZeeQkssd)ODLp^}?xrXta_wS26xLRwywbw}I z+3a0M?zsjXkU3~g_11R6T0H!|3br!cyB^>_N1Q+UgcF#d>fMJo=LF_^>j7GOJ=12G zU?B2N;c3}~I`1^QTYF`=AN~RD+Otd$*&Hp?hYkD$aRMUWpEx1QIbN9B`dt9(REZg) z&!GBt2ZPn;Jn`Ck~>`s0`H!+ zNi*-;t(R62-PB&x))ULXz>h!WKX{MOn*W}_LnITk{eYE)k=}*&M*P0^PF{KwdJ^F8 z1Bed?YrS=fh-^^RIbB1fu|qQaROh!AWB?A25eAUVMs*?1*G?7awdGK<k<~0ljyu z2kk%Y}RWx3bEUPEc3sr~NuSf?jrrC)11Z?F zo+Qfzv(7&uc%H%n0M>m7BhgA+}ogeqD zcM04k=^P%-d^%Pi;qGPuZC@Y;Ksx*kBu1e3r3D~%X>w=mMBZUY_r1Vf3Deoa=$3#L zfsm1DJ!6#_boBIiPR*>P3Nz~t|G$9Bv$Z7)>AQexy&T2!JW4lMy-w z4u#bCNMn{kG&m|YgX-1nuP856Uu6D@G5jKkH+9}z3(+7MjTzS=WuZs+(#V@#K1Z@A z!Ytfw3s3`BFs9!GA`9Rf8I>_0Ii7wdWwMv)+DL8JbXn`AbEW4k+~xFC50FK)re@?1 zdC`W?v)Sq;Bw3a|OJ>ymBN!ly>UfhjcwF-lVE`*wc!`i~L4s^m50e?;Ca?;Tx{1Pb z4Ao6-@XCay%d>^sB|Wn{^S3d;3V6N70G;DGzbZ(15NHANF$|F1zzj2Fby-1>k%#Kz zh%nNs@ht!>`a>2(Hy1@@uB0mq&-p9Lm82K#U6zcUtuWfecpE0wI#iyeAF1^cyn3wZ z@UEk_dOZM&*&q;)fe|X?bFIzP0)#I*TM8o3?BG93*V;YHzDy8kLE#0<6az>GQo-mV zwQ{f`!y}Ak&DR27S{pJCou3)$T9Er82GHO!0}=!kS;N@`k)35VZB0>DSP=x$7?m43 z1$eYbqfpuiYvVk4ay6SY`z=V!<;k}KgUaQfS2{m4ssX^WK>Jw=0%=36x}^ogOkcp| zWuDKl*X|(Tc24yG7BbGsI>M4=1yi)_c>1U;UV@NvU_B!xRz*+XjZ?o3)Tr`qP+JBl z^Hl1%F6@8#NWsY=B~jM-bgk12AW=RyRGWVnq$ZFeVoSz;lmXGKr=M43zILCF<{yZ# z7LN1-(k4_vlNr$^Ie=bgY_OVK?fyW<>7H%NdH_wSW|-8h%<4imU$arOlh3g*8-P?7 z>U65RqH$dEg%B^3DoTdM02Zvq4b@BKL~OI>dQPv9Cyh(K51>)1R|6|`TbN;?NV!1t zP-oLo3Wkb|zl8xfyk4QA_D1cgTDDc+?^sWy;giN&z7W#8{Y_%}Ia+_SsBTC#+oQb* zeLgx;8Ambz2ihY@bFxjACa)UNE?v9!o~v+v`6(a@@6;e6+PQl*yP6Rsn=G59AV38cS7A*m7&a@-dN}xTC6^tvK|H1$f>OvNiLFK6-!Y+3OKYDT)bCx;X z>j9#MRs+pqgwC}M9oWHqXmGA_I@|+1B@if1qJ9rvAm1^D|5E78oGebH^GbL<&^87@ z4Atg%^D;P%&Z~o7>w&g2Kz8A)Sm6ZcQ>+YxqZpuKL7$9OfiPsQ2!g+^GYZtf-cH{D O00005Az&DLY{*`% zwcdMg*LAhtduy$=>$(~Sz7PM$zv;WU4F}r)5BK2#WC4+sK#5Eb(YP&!_q_^MjPVV4 z069QthH!8m+?IQPC4MU73xYQV@OEHDKXnN1p5{zHjDaTz-V^|?9KlkGpwFxM|Aff> zCMUf*Gn+_$S34&~;tAxA!YT7*6=0NyQ&sq;$gz+i|Bm%}Q-G1=vx71HpAaRxqoOt+ zy8yswVzk;}HojSTs_OdSiZ{y{yw0&pbnjxv4A2xF#nVH6*1J4A18@J|Okx$4wRxQb zXj?qbV44xyJ6`9~4yk^~;|b(&i>saedx6_WzZp-S9 zK0LLDT59m@8nD!iP=o51L@BlXXNOT0;G?lYW_4?)9YSTdp7@^Eo(fL+3J7>a8sj34 zn`CzKBmPDJY_rJMj z&_(BJ3F$p(Zygp{AGAg*!3pTSfBroHJjlTNzIMb+9Z0GJB8ODjd401Iz&bzZ&5HL^ zMTj2xO7e>d#5P*T)7%HH|EvT!4ZhY}+67eA!0N}U_&FtzCS;B0Eh4QsoD5E3uj`6! z11s=uA+r%!ElBCO++t~V?dT-ZJn&UL2pVlnYrfM(4?O$)NMS0e5%;@d_iT7b$Jb>J zfDLS|zGxXjyt|Xg3FZ#uM@hd$0W`xUWKR~kF(SJLcrP-}YE($a*U(AetO;2Ad<)u+ zgUjKWa}RD;v1*q<#~Veyx(mRVxiyluX`{x`>Htq+3h)${+EzA-p(Ixn07vPl$ypPh z(H|wp+G5g3$M0$VMbj2%>niel@9`8c+X*TQD1poWcnmro*N!8{`W>k?R+1i40BA_% zaEC%D0vhc!pHpxV4_7Jjx!-u~skwY+4L}!#POJhck6P`bV^CPcd*ocJ0koQf_J8N~ zNsZGcJX)H)$f%7~z-gGh=wRR7gTwk=tol{PrLsJ$Tt z$U>8EDN?lv=7^G%ghs_I+#UH*CxNx`v`;%gCYm(jX}ijEjBM1?K2}Eh%B#x(Bq{e` zW@LInYXN}QIqoJYlP2&SOA6qLSgyS#@v9tzrW9}5fOb2O>bZi=QKbr{04X;&R&4^{?fk+mWGmYamBQ%s$>>( zHR)Cg^YpjuPr_av$B+%Fz}tQ{Xb#1Sb$5=Z>x1u0wZIk%fFY$6DX&>6i0glrdaP6g zjOg6AQveKzDlNkCb5HwGu$*U>B0zuC25gHK@mYIrQ2+`Y;T!>yBH(+tZDdN>NaNnm zM0R%E%J^^B9UK5)Nj-HG4j$?Dw2vaHOfw3?V-j(f&CZm|)2s4&;B>CGZ0EOlV zbRw0$wC9zAaz?>8y1%U*0KkzWElQ>@?m0$viL5areLz3M3eXcA0KgT4ocvcEp;{PP zV?v*IMEsEoKrvFzt-^9v9+&!FLHe-@KrvFzSCKD`xhmqnLIEgd$@z%f9UW%{=~EP7 z)na*V7#Y#49RbEVQUP{y1dNk}ZmA;7N&!1K!s%F{2!G|LaRgS1$SeQ=002ovPDHLk FV1oG?)rtTB literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png new file mode 100644 index 0000000000000000000000000000000000000000..789eebdfaa25f10335d19e85b2162518cd947a34 GIT binary patch literal 1521 zcmVGwk@m0LO8}3Wpm+_BUaMZ9W)) z4rI02NIm;#`DBK|wN=-6j4?*VsQ^9`Xk*fTT;|6vc}z1vH1m0z2^z`iQvWAs>nhA7 zO{xa4o?BTxGu97lk*Wc#=h{Bco#{Cj=kY#2^xy!10q~i%JUhMX(!|0Tm2p?F#a+(u z9eDp3WM#T{OiUhB7jXl0*e21!0UM^wLGFxYrm4{5e#P6yf3M1;<@f|2WJ zF#(tVohTB&1_Jn*nynck%;DX$GMvlos2lEff-o6?gVzWEbEgN1DLh1-kIyrwlMXhx zlk)+R4DhFOFten75f-r+B4cO_w5XnUbK(qA48Y+rg4TNP5h9r#)s4>;d2jJ8b-@^8 zjsZBbHG(w`&ssWmWZ$>+$>{M^pdF_eKpK=X0?)YUdjsxd?l8@H{4}6d`Bv@mWG0}e zpSPVm89;)Rh0*r}8WTu$fL1p|b&AN}8InEQ89x@jBvb;yI@3$zdPer@22Zw=)y@utDW}sKKsw876Gr4DCHZ%4T!6Y^X+0pZ zjaKbfTby$T=w6O(hx5f6AetqaJv1_AlwvE}K(&8ld~N%fMXQr729PF>7%5}O$e2_y zO*Rv7niFO-pPvq->uQiK$?z-#oDC_f0QxdF;vi2(%Y5lN!f4x^9btg0p*uc#9fRNL z2k&dGmif{(UBlA7{4NaOWwe)FyYz_2ST%sO9q^zy-TAv2fbQ`fg;Iwe7O1Y-8bEu_ zbvV2IZ-s#)pvI|Wc!e3N^U?jh!{|6xBzPXh0JvCc*a{m|*dv;@IA^*MsSuDGZ<)1g|}Lsef0w!YLcSca}pt zw*@*M|L*o5ovRL@nq{*NqWvWnsIW-2Pq*`>akcxrJ?-aU%c0sciQ;DG%Z$aHh^Y4O zaJskN(`x_?R&8K8c_s+7u{;?~H_0g;X7~8naW2CE6d6k8R6bK!(z!M8xmGwK5>(lB zUY-Fc_}*M`@a)_ccvX$}x}9wmjN1K2FaU?e2o(nCEFd*q0pg4xp{n03BVF>xpfU76 zL8pew?!Ogy!GpS@Y^240w9WMItO+j303048P;i-S_E+~_ss9Sow$~ze2mpi@2o?)O zs9Tb^=E@XgbllFuu&o9Fi2ZBWl^_+1cr6)8g6C*Hm(;haZv`uy|G@xJ>grfb7c`^r z=g~q@lbzibJbia&e5eM9819OJ;AL@y&NVhcbgVZTY^$6et^uAJ2ox)+--ESXZE=3| z6!4@#=%VvZaMsz{Mgl-m)RXb#c3?G~cL)7t8KAoGtFXdqI$sqCmt%mA1w9#eWQ6|# XYhRhvxX$)N00000NkvXXu0mjf-Ye1b literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png new file mode 100644 index 0000000000000000000000000000000000000000..cc4f5cbfff2c3c96ad0a63df6c9a20631c04b5ab GIT binary patch literal 1642 zcmV-w29^1VP)=Kfb^KBn|WR$3dY?YOH_4hAF8(nusn zDW%q03*k79^5Xa4!;N1`DYe!j-`x>U*ux5ZDB_qRAN3FHC7_}5XTD3XpJFovm4BZS6jePmcI1OK* z^&QRY{k-^(o`6Z^09Jq0z}8$XO!TrvA5uAh)vraSG!Spg-?NN@*y2SFM=7P2a*kSQ z46ofRTsTLj0`bQ7FvDGH_!0R2G02K^ug#x?Y&!xT@u-jg^k9|oufSM8a)46)FKjjw zWEeop8N6?QOC)E&1Nb95sI!*|MphA4!pIpxvcPM6j8pDq{VgC{x@qmmcukI1Em%g5 zjQQ>OSe-Juv}cwX(D*2##;1PiTzpS&<&gMmU@5U1yxwX;8dw{jiE47VnU#Z-bqCa1 zZ3aLe@EoC(zUl!nh5Q{MqF^5FT!vMR%qVTIj-!Zw3Qc_+tjW=LksJXRITva_jS1ilYI6b*1d6t74q)X3 z9NHBzlJ6lq{_5bt#C7;*g4>(glrYz zeV?sNqH<;D@TPYn!K$POF?<3TY zoS}s+D8gk96WxcFGiW)0)-U(B()dVk)31UNP*fp@;KuS2GfNiDA6b|6ek2{s<7ai` zf5)~S0j==PCUgX#wsky2)_{<4rddYQ)-@vXxgKfk6b6WRpw=gyLvBjjpy%xo*&ay# zD*Be|w~irCH=2VDzX_DIjg+s^IajXy#$pX?gVLF_b!9Ny_qy^Bd0<7lJif! z3f9hV`Hnk{HBO$N{6|7lfVHkDJfqEa3dsOmH}bvIrF=j92tZVXgh!JV&4D;j6wFL* zRxm)1laMYn=HM9W04Uj@w&ny8c;hn0?xB7ubGNah&R}7v185$nB{egn)PlFJ2zDEH zgz-z6d=H=*dY9ozw#+|xp9a2{-Nr>vT4PqXQyn0>81JaqwJ!AC6^xCH-@31*H-T1; zAls`Fx(u#h?5OdzAd>HwIsg}JWvN%o83?R_9Y%~H947onib^Zt4I8lnf&J?{$Q1V(7`BIeH_0M0tB_ z2Ox~3b1$(Rp{}U2MV!RiOK2@mZ~($cI-g~HO-3f-qa~i!KCR(N4nSBz=OcmcJw_Q{ zdq)^{6F4H#83mpbP literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png new file mode 100644 index 0000000000000000000000000000000000000000..1a1fe5d2d4a034e644e717e621cc315e92baa085 GIT binary patch literal 1694 zcmV;P24VS$P)4r{O-HzUS&p5R;KO>M# z+Ns(`ZLk{?OfvwQ`TQ9P8p-JuXYD`c0y0zsSmRb!&&WCp{5ss*!)`2)MW_a_#v z8_F}>SjYRRgctPDckYCvwbom+Gr;>hwXh%~BX>3vr7yzhmp~)vY4AVk%d;2V?0E7_ z_x`5O0VMP3XPKHpVv7i(K|R)}a{3M|@>K)0_MHKAypjpHAtQGX6=$@R)jaEGH38Y%#$(fD-{x+~RKU1SM1Hv5MXI2*SIn|wvw0=dASXpR#Fd2Y@ z*9fi6bq^9#ctD*$#-(m#qotd#! z7&=cefFzVM0%r#F-hdmFJ4$l~?*_Cx1JC)I4tIx?jHLlU7vk+%gb6g~NH=$MjII^A z#j9{`1!Ze{s(@4nl{0|0FlPnyp1?Z}tU#0AK{|lQu^_d8J3&e+{#0v#Y=!SB!-D`( zk_QLXPg*0g56j_PNA#W|15byD9LtXUsRn>`i82E&7g3VV<8m}udTUPeoGBeoS|Fc^ zsM9~5P56HR3Dtoh$+c+TTN|Nct1GbNYb?O^wb;o5VpC~4VdShEyfMRREIq44&Sg+%*wEDJOVs+|A906QR4-2V#9$ghOd-5~Xh_MX;#R0hHFdw^L~JBxP~){h2nj5C0ARM7P` z8IsP@I+roP3{6`yrD(4P!YtMtO)sfX<6|7iepht?=`VvUMKz;VSL0crLB(lY%3w*3 zXLLSKT2qsW$WL_vm9S{7s*A|HKPWdEe3H)E8IKrUJKIrRwx|K#Z3H_rQ3uZiLDu=y z_!(#!8x4Zl^-G^KH2{Z4XHvv-dbO%wX&pp;FVifiSjU{Fs}}?r(^uC5r?-I-r+P-^ z^CLP$;W<~*7df*t;BqBhFJXYp2CUCfBSkaZlZnok^yTxdAVA?Ur&VAss{s(Dd3`{Y z)6UKeth1FGkFJZ-zNHhF7by(6j@I9iOs6^_&yxQMcov>WmXlCS|d{&BJ~r(0H<~V9t~=d zEoDwrZL3I&%F8eXcLsky6M$CLuYGfDusiQ9G0z6Oy z@=cr>kBIXvs;xNFXTaHHi!LAoFO#Iqsbl~P(mCzvs#WRMdoz%Z(XAPPBSWdtqRCs^ zg4B^{x_ZT!Br^l6U5q?03jisrHG^cSmabKud1%mTZCh2V*?a8fZwm(S44|F6vqfA6 z$eqQQNB@q>%j}~f2rjDukp7s@jnoKqEYq%O@*?kBEv@uk#uZw72?xOrMIZ{xh+04) zK_kuA)|zb_z26a;T7rzVLN&n3>a-e20VWwED~pa->B}iwT6<=_@G&qL0KKHxDPXJ& zlg1;mq_KhQ-lMj&Dt$}#bPa$gx^p0);})H2>`}4bXkbbm1fu(N4e-=Jps1LeMYMB3 z+R11TKt3oMk oNVkHS0El9jKv<2P0s#$zUs0JT=-M&wV*mgE07*qoM6N<$f;eRor2qf` literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_9.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_9.png new file mode 100644 index 0000000000000000000000000000000000000000..a19da7382eadaf0bc885f76da8dec3899034528c GIT binary patch literal 1605 zcmV-L2DVtUtjJl073;Y!fgJw#SBa zU#+#?dvC3^_IkY<|C>YV!@uKL-bVt;&q9(vEAubn%@>jksTm&;F*NTRky(Y+@}Gh? z8HU>4`;fuOd3axGd<85Ws^_{hB0=!O0N7c(PVk~19YVS$jsF+9b%+GPF9zVP6)hbUmI%4KoKdpTq^YZW62|a^E z zY0QwpUt*AtRs^;-7NH|fK=M88%K5vDe}bchY5>ppS|zSs%NB;34&HkO>~k8f(v3Ub z2XD0Y?FgegfXLWP#UF9XlZ|~6zP8cYy8zxXQ`kv;v^0X_Glq_&j*5EV>G)VTz0?rd zHCQzU9}`8?_|koA2l7{8GQh9S$%v22jBX7_jTUjjtjx$@Nmb0V~=x7SPTIyj_4c2i}Vok(vId%tZ0OzT&r)SPgJMX=Lw?-8J)G} zN#H64Ta%B038Om_4O+1n-Kj(ml5nnF$Qpe~4Ul009ET5LVI&PmGNig<6t^=vYf~qI z5rsy^q2qY_d=K6ZM5_F=y3g!%vcN;DD&sHN1yr0R%oczd`FPKoOJXZ)7c+xWBky1U zu6U(SRqs-Nwm-sogSExv_i+*V{h5@qB*LAx+ySalk)sh@9?~oRjXI!_t?$tS6UXgr z%N<~6Zk&O{1d;S$MZWZ%=F-|LjJ~V}&~oAm88gfvIj3d#=sj(brFoAq{t5=*s49pM zBQiNN5k2!ybk0cRTQvfgceU|*@3jX(lGo3`lNFhZ43IIhcD-uRNmjMok#@<;AYAl} zQ{W=uI4j3y^+RNwX@xN~9A5)y#_)`($(kAC>4!RIvDyi;Y4h?L0COrj1E;!GuojIw z2iI{%rycZ6^YE6>kIy}tPL>=5p%c&t!TU)D(F{ulDmHJd-2LsGF39Ut>f5|QY4LLF%(Uxrk zwWyTtY2#Mc3OFaumznUb3`2#Q`KoL>8RBV%Nf9e zRv%y__CEHneFj61>DlJTW=ZS5ikuyQ#!E-H^vCOy!VIKkMG@g@BT-uUG|3)f&p zxt0cIRGnp&6O5e+jHtZJeh={DfeKUL!&&`TL(8#O&WemU}6&X;p;tIr-dH#E) zmLuSU&B(IuXHv?Fg0WiOtsH Date: Fri, 6 Jan 2023 21:05:58 +0300 Subject: [PATCH 5/7] ReadMe: edit text, move Links to the end (#2219) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Edit text, move Links to the end * ReadMe: can to must Co-authored-by: あく --- ReadMe.md | 64 +++++++++++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 5cf2ff71c..411f5b41d 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -8,33 +8,33 @@ # Flipper Zero Firmware -- [Flipper Zero Official Website](https://flipperzero.one). A simple way to explain to your friends what the Flipper Zero can do -- [Flipper Zero Firmware Update](https://update.flipperzero.one). Improvements for your dolphin: latest firmware releases, upgrade tools for PC and Mobile devices -- [User Documentation](https://docs.flipperzero.one). Learn more about your dolphin: specs, usage guides, and everything that you wanted to ask +- [Flipper Zero Official Website](https://flipperzero.one). A simple way to explain to your friends what Flipper Zero can do. +- [Flipper Zero Firmware Update](https://update.flipperzero.one). Improvements for your dolphin: latest firmware releases, upgrade tools for PC and mobile devices. +- [User Documentation](https://docs.flipperzero.one). Learn more about your dolphin: specs, usage guides, and anything you want to ask. # Contributing -Our main goal is to build a healthy, sustainable community around the Flipper and be open to any new ideas and contributions. We also have some rules and taboos here, so please read this page and our [Code Of Conduct](/CODE_OF_CONDUCT.md) carefully. +Our main goal is to build a healthy and sustainable community around Flipper, so we're open to any new ideas and contributions. We also have some rules and taboos here, so please read this page and our [Code of Conduct](/CODE_OF_CONDUCT.md) carefully. ## I need help -The best place to search for answers is our [User Documentation](https://docs.flipperzero.one). If you can't find the answer there, you can check our [Discord Server](https://flipp.dev/discord) or our [Forum](https://forum.flipperzero.one/). +The best place to search for answers is our [User Documentation](https://docs.flipperzero.one). If you can't find the answer there, check our [Discord Server](https://flipp.dev/discord) or our [Forum](https://forum.flipperzero.one/). ## I want to report an issue -If you've found an issue and want to report it, please check our [Issues](https://github.com/flipperdevices/flipperzero-firmware/issues) page. Make sure that the description contains information about the firmware version you're using, your platform, and the proper steps to reproduce the issue. +If you've found an issue and want to report it, please check our [Issues](https://github.com/flipperdevices/flipperzero-firmware/issues) page. Make sure the description contains information about the firmware version you're using, your platform, and a clear explanation of the steps to reproduce the issue. ## I want to contribute code -Before opening a PR, please confirm that your changes must be contained in the firmware. Many ideas can easily be implemented as external applications and published in the Flipper Application Catalog (coming soon). If you are unsure, you can ask on the [Discord Server](https://flipp.dev/discord) or the [Issues](https://github.com/flipperdevices/flipperzero-firmware/issues) page, and we'll help you find the right place for your code. +Before opening a PR, please confirm that your changes must be contained in the firmware. Many ideas can easily be implemented as external applications and published in the Flipper Application Catalog (coming soon). If you are unsure, reach out to us on the [Discord Server](https://flipp.dev/discord) or the [Issues](https://github.com/flipperdevices/flipperzero-firmware/issues) page, and we'll help you find the right place for your code. -Also, please read our [Contribution Guide](/CONTRIBUTING.md), and our [Coding Style](/CODING_STYLE.md), and ensure that your code is compatible with our project [License](/LICENSE). +Also, please read our [Contribution Guide](/CONTRIBUTING.md) and our [Coding Style](/CODING_STYLE.md), and make sure your code is compatible with our [Project License](/LICENSE). -Finally, open a [Pull Request](https://github.com/flipperdevices/flipperzero-firmware/pulls) and ensure that CI/CD statuses are all green. +Finally, open a [Pull Request](https://github.com/flipperdevices/flipperzero-firmware/pulls) and make sure that CI/CD statuses are all green. # Development -The Flipper Zero Firmware is written in C, with some bits and pieces written in C++ and armv7m assembly languages. An intermediate level of C knowledge is recommended for comfortable programming. For Flipper applications, we support C, C++, and armv7m assembly languages. +Flipper Zero Firmware is written in C, with some bits and pieces written in C++ and armv7m assembly languages. An intermediate level of C knowledge is recommended for comfortable programming. C, C++, and armv7m assembly languages are supported for Flipper applications. ## Requirements @@ -50,11 +50,11 @@ Supported in-circuit debuggers (optional but highly recommended): - ST-Link - J-Link -Everything else will be taken care of by Flipper Build System. +Flipper Build System will take care of all the other dependencies. -## Cloning Source Code +## Cloning source code -Ensure that you have enough space and clone source code with Git: +Make sure you have enough space and clone the source code: ```shell git clone --recursive https://github.com/flipperdevices/flipperzero-firmware.git @@ -68,17 +68,17 @@ Build firmware using Flipper Build Tool: ./fbt ``` -## Flashing Firmware using an in-circuit debugger +## Flashing firmware using an in-circuit debugger -Connect your in-circuit debugger to the Flipper and flash firmware using Flipper Build Tool: +Connect your in-circuit debugger to your Flipper and flash firmware using Flipper Build Tool: ```shell ./fbt flash ``` -## Flashing Firmware using USB +## Flashing firmware using USB -Ensure that your Flipper is working, connect it using a USB cable and flash firmware using Flipper Build Tool: +Make sure your Flipper is on, and your firmware is functioning. Connect your Flipper with a USB cable and flash firmware using Flipper Build Tool: ```shell ./fbt flash_usb @@ -88,11 +88,24 @@ Ensure that your Flipper is working, connect it using a USB cable and flash firm - [Flipper Build Tool](/documentation/fbt.md) - building, flashing, and debugging Flipper software - [Applications](/documentation/AppsOnSDCard.md), [Application Manifest](/documentation/AppManifests.md) - developing, building, deploying, and debugging Flipper applications -- [Hardware combos and Un-bricking](/documentation/KeyCombo.md) - recovering your Flipper from most nasty situations +- [Hardware combos and Un-bricking](/documentation/KeyCombo.md) - recovering your Flipper from the most nasty situations - [Flipper File Formats](/documentation/file_formats) - everything about how Flipper stores your data and how you can work with it - [Universal Remotes](/documentation/UniversalRemotes.md) - contributing your infrared remote to the universal remote database - [Firmware Roadmap](/documentation/RoadMap.md) -- And much more in the [Documentation](/documentation) folder +- And much more in the [documentation](/documentation) folder + +# Project structure + +- `applications` - applications and services used in firmware +- `assets` - assets used by applications and services +- `furi` - Furi Core: OS-level primitives and helpers +- `debug` - debug tool: GDB plugins, an SVD file, etc. +- `documentation` - documentation generation system configs and input files +- `firmware` - firmware source code +- `lib` - our and 3rd party libraries, drivers, etc. +- `scripts` - supplementary scripts and python libraries home + +Also, see `ReadMe.md` files inside those directories for further details. # Links @@ -100,16 +113,3 @@ Ensure that your Flipper is working, connect it using a USB cable and flash firm - Website: [flipperzero.one](https://flipperzero.one) - Forum: [forum.flipperzero.one](https://forum.flipperzero.one/) - Kickstarter: [kickstarter.com](https://www.kickstarter.com/projects/flipper-devices/flipper-zero-tamagochi-for-hackers) - -# Project structure - -- `applications` - Applications and services used in firmware -- `assets` - Assets used by applications and services -- `furi` - Furi Core: OS-level primitives and helpers -- `debug` - Debug tool: GDB-plugins, SVD-file and etc -- `documentation` - Documentation generation system configs and input files -- `firmware` - Firmware source code -- `lib` - Our and 3rd party libraries, drivers, etc. -- `scripts` - Supplementary scripts and python libraries home - -Also, pay attention to `ReadMe.md` files inside those directories. From c24bea6b06f3367f5dc4624c5bdce754267dbc60 Mon Sep 17 00:00:00 2001 From: knrn-ai <25254561+knrn-ai@users.noreply.github.com> Date: Fri, 6 Jan 2023 21:18:43 +0300 Subject: [PATCH 6/7] Documentation: edit texts, markdown linting (#2226) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: konerini <25254561+konerini@users.noreply.github.com> Co-authored-by: あく --- documentation/AppManifests.md | 111 ++++++++--------- documentation/AppsOnSDCard.md | 67 +++++----- documentation/KeyCombo.md | 103 +++++++--------- documentation/OTA.md | 108 ++++++++-------- documentation/RoadMap.md | 19 ++- documentation/UnitTests.md | 40 ++++-- documentation/UniversalRemotes.md | 49 ++++---- documentation/fbt.md | 115 +++++++++-------- .../file_formats/BadUsbScriptFormat.md | 108 +++++++++------- .../file_formats/InfraredFileFormats.md | 74 ++++++----- .../file_formats/LfRfidFileFormat.md | 50 ++++---- documentation/file_formats/NfcFileFormats.md | 22 ++-- .../file_formats/SubGhzFileFormats.md | 116 +++++++++--------- .../file_formats/iButtonFileFormat.md | 12 +- 14 files changed, 509 insertions(+), 485 deletions(-) diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index 2ffa3c0c2..195fe9256 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -1,65 +1,63 @@ # Flipper Application Manifests (.fam) -All components of Flipper Zero firmware — services, user applications, and system settings — are developed independently. Each component has a build system manifest file, named `application.fam`, which defines the basic properties of that component and its relations to other parts of the system. +All components of Flipper Zero firmware — services, user applications, and system settings — are developed independently. Each component has a build system manifest file named `application.fam`, which defines the basic properties of that component and its relations to other parts of the system. -When building firmware, **`fbt`** collects all application manifests and processes their dependencies. Then it builds only those components referenced in the current build configuration. See [fbt docs](./fbt.md#firmware-application-set) for details on build configurations. +When building firmware, **`fbt`** collects all application manifests and processes their dependencies. Then it builds only those components referenced in the current build configuration. See [FBT docs](./fbt.md#firmware-application-set) for details on build configurations. ## Application definition -A firmware component's properties are declared in a Python code snippet, forming a call to App() function with various parameters. +A firmware component's properties are declared in a Python code snippet, forming a call to the `App()` function with various parameters. -Only 2 parameters are mandatory: ***appid*** and ***apptype***; others are optional and may only be meaningful for certain application types. +Only two parameters are mandatory: **_appid_** and **_apptype_**. Others are optional and may only be meaningful for certain application types. ### Parameters -* **appid**: string, application id within the build system. Used to specify which applications to include in the build configuration and resolve dependencies and conflicts. +- **appid**: string, application ID within the build system. It is used to specify which applications to include in the build configuration and resolve dependencies and conflicts. -* **apptype**: member of FlipperAppType.* enumeration. Valid values are: +- **apptype**: member of FlipperAppType.\* enumeration. Valid values are: -| Enum member | Firmware component type | -|--------------|--------------------------| -| SERVICE | System service, created at early startup | -| SYSTEM | Application is not being shown in any menus. It can be started by other apps or from CLI | -| APP | Regular application for the main menu | -| PLUGIN | Application to be built as a part of the firmware and to be placed in the Plugins menu | -| DEBUG | Application only visible in Debug menu with debug mode enabled | -| ARCHIVE | One and only Archive app | -| SETTINGS | Application to be placed in the system settings menu | -| STARTUP | Callback function to run at system startup. Does not define a separate app | -| EXTERNAL | Application to be built as .fap plugin | -| METAPACKAGE | Does not define any code to be run, used for declaring dependencies and application bundles | - -* **name**: Name that is displayed in menus. -* **entry_point**: C function to be used as the application's entry point. Note that C++ function names are mangled, so you need to wrap them in `extern "C"` to use them as entry points. -* **flags**: Internal flags for system apps. Do not use. -* **cdefines**: C preprocessor definitions to declare globally for other apps when the current application is included in the active build configuration. -* **requires**: List of application IDs to include in the build configuration when the current application is referenced in the list of applications to build. -* **conflicts**: List of application IDs that the current application conflicts with. If any of them is found in the constructed application list, **`fbt`** will abort the firmware build process. -* **provides**: Functionally identical to ***requires*** field. -* **stack_size**: Stack size, in bytes, to allocate for application on its startup. Note that allocating a stack too small for an app to run will cause a system crash due to stack overflow, and allocating too much stack space will reduce usable heap memory size for apps to process data. *Note: you can use `ps`, and `free` CLI commands to profile your app's memory usage.* -* **icon**: Animated icon name from built-in assets to be used when building the app as a part of the firmware. -* **order**: Order of an application within its group when sorting entries in it. The lower the order is, the closer to the start of the list the item is placed. *Used for ordering startup hooks and menu entries.* -* **sdk_headers**: List of C header files from this app's code to include in API definitions for external applications. -* **targets**: list of strings, target names, which this application is compatible with. If not specified, the application is built for all targets. The default value is `["all"]`. +| Enum member | Firmware component type | +| ----------- | ------------------------------------------------------------------------------------------- | +| SERVICE | System service, created at early startup | +| SYSTEM | Application is not being shown in any menus. It can be started by other apps or from CLI | +| APP | Regular application for the main menu | +| PLUGIN | Application to be built as a part of the firmware and to be placed in the Plugins menu | +| DEBUG | Application only visible in Debug menu with debug mode enabled | +| ARCHIVE | One and only Archive app | +| SETTINGS | Application to be placed in the system settings menu | +| STARTUP | Callback function to run at system startup. Does not define a separate app | +| EXTERNAL | Application to be built as `.fap` plugin | +| METAPACKAGE | Does not define any code to be run, used for declaring dependencies and application bundles | +- **name**: name displayed in menus. +- **entry_point**: C function to be used as the application's entry point. Note that C++ function names are mangled, so you need to wrap them in `extern "C"` to use them as entry points. +- **flags**: internal flags for system apps. Do not use. +- **cdefines**: C preprocessor definitions to declare globally for other apps when the current application is included in the active build configuration. +- **requires**: list of application IDs to include in the build configuration when the current application is referenced in the list of applications to build. +- **conflicts**: list of application IDs with which the current application conflicts. If any of them is found in the constructed application list, **`fbt`** will abort the firmware build process. +- **provides**: functionally identical to **_requires_** field. +- **stack_size**: stack size in bytes to allocate for an application on its startup. Note that allocating a stack too small for an app to run will cause a system crash due to stack overflow, and allocating too much stack space will reduce usable heap memory size for apps to process data. _Note: you can use `ps` and `free` CLI commands to profile your app's memory usage._ +- **icon**: animated icon name from built-in assets to be used when building the app as a part of the firmware. +- **order**: order of an application within its group when sorting entries in it. The lower the order is, the closer to the start of the list the item is placed. _Used for ordering startup hooks and menu entries._ +- **sdk_headers**: list of C header files from this app's code to include in API definitions for external applications. +- **targets**: list of strings and target names with which this application is compatible. If not specified, the application is built for all targets. The default value is `["all"]`. #### Parameters for external applications The following parameters are used only for [FAPs](./AppsOnSDCard.md): -* **sources**: list of strings, file name masks used for gathering sources within the app folder. The default value of `["*.c*"]` includes C and C++ source files. Applications cannot use the `"lib"` folder for their own source code, as it is reserved for **fap_private_libs**. -* **fap_version**: tuple, 2 numbers in the form of (x,y): application version to be embedded within .fap file. The default value is (0,1), meaning version "0.1". -* **fap_icon**: name of a .png file, 1-bit color depth, 10x10px, to be embedded within .fap file. -* **fap_libs**: list of extra libraries to link the application against. Provides access to extra functions that are not exported as a part of main firmware at the expense of increased .fap file size and RAM consumption. -* **fap_category**: string, may be empty. App subcategory, also determines the path of the FAP within apps folder in the file system. -* **fap_description**: string, may be empty. Short application description. -* **fap_author**: string, may be empty. Application's author. -* **fap_weburl**: string, may be empty. Application's homepage. -* **fap_icon_assets**: string. If present defines a folder name to be used for gathering image assets for this application. These images will be preprocessed and built alongside the application. See [FAP assets](./AppsOnSDCard.md#fap-assets) for details. -* **fap_extbuild**: provides support for parts of application sources to be built by external tools. Contains a list of `ExtFile(path="file name", command="shell command")` definitions. **`fbt`** will run the specified command for each file in the list. - -Note that commands are executed at the firmware root folder's root, and all intermediate files must be placed in an application's temporary build folder. For that, you can use pattern expansion by **`fbt`**: `${FAP_WORK_DIR}` will be replaced with the path to the application's temporary build folder, and `${FAP_SRC_DIR}` will be replaced with the path to the application's source folder. You can also use other variables defined internally by **`fbt`**. +- **sources**: list of strings, file name masks used for gathering sources within the app folder. The default value of `["*.c*"]` includes C and C++ source files. Applications cannot use the `"lib"` folder for their own source code, as it is reserved for **fap_private_libs**. +- **fap_version**: tuple, 2 numbers in the form of (x,y): application version to be embedded within .fap file. The default value is (0,1), meaning version "0.1". +- **fap_icon**: name of a `.png` file, 1-bit color depth, 10x10px, to be embedded within `.fap` file. +- **fap_libs**: list of extra libraries to link the application against. Provides access to extra functions that are not exported as a part of main firmware at the expense of increased `.fap` file size and RAM consumption. +- **fap_category**: string, may be empty. App subcategory, also determines the path of the FAP within the apps folder in the file system. +- **fap_description**: string, may be empty. Short application description. +- **fap_author**: string, may be empty. Application's author. +- **fap_weburl**: string, may be empty. Application's homepage. +- **fap_icon_assets**: string. If present, it defines a folder name to be used for gathering image assets for this application. These images will be preprocessed and built alongside the application. See [FAP assets](./AppsOnSDCard.md#fap-assets) for details. +- **fap_extbuild**: provides support for parts of application sources to be built by external tools. Contains a list of `ExtFile(path="file name", command="shell command")` definitions. **`fbt`** will run the specified command for each file in the list. +Note that commands are executed at the firmware root folder, and all intermediate files must be placed in an application's temporary build folder. For that, you can use pattern expansion by **`fbt`**: `${FAP_WORK_DIR}` will be replaced with the path to the application's temporary build folder, and `${FAP_SRC_DIR}` will be replaced with the path to the application's source folder. You can also use other variables defined internally by **`fbt`**. Example for building an app from Rust sources: @@ -73,16 +71,16 @@ Example for building an app from Rust sources: ), ``` -* **fap_private_libs**: a list of additional libraries distributed as sources alongside the application. These libraries will be built as a part of the application build process. -Library sources must be placed in a subfolder of "`lib`" folder within the application's source folder. -Each library is defined as a call to `Lib()` function, accepting the following parameters: +- **fap_private_libs**: list of additional libraries distributed as sources alongside the application. These libraries will be built as a part of the application build process. + Library sources must be placed in a subfolder of the `lib` folder within the application's source folder. + Each library is defined as a call to the `Lib()` function, accepting the following parameters: - - **name**: name of library's folder. Required. - - **fap_include_paths**: list of library's relative paths to add to parent fap's include path list. The default value is `["."]` meaning the library's source root. - - **sources**: list of filename masks to be used for gathering include files for this library. Paths are relative to the library's source root. The default value is `["*.c*"]`. - - **cflags**: list of additional compiler flags to be used for building this library. The default value is `[]`. - - **cdefines**: list of additional preprocessor definitions to be used for building this library. The default value is `[]`. - - **cincludes**: list of additional include paths to be used for building this library. Paths are relative to the application's root. This can be used for providing external search paths for this library's code - for configuration headers. The default value is `[]`. + - **name**: name of the library's folder. Required. + - **fap_include_paths**: list of the library's relative paths to add to the parent fap's include path list. The default value is `["."]`, meaning the library's source root. + - **sources**: list of filename masks to be used for gathering include files for this library. Paths are relative to the library's source root. The default value is `["*.c*"]`. + - **cflags**: list of additional compiler flags to be used for building this library. The default value is `[]`. + - **cdefines**: list of additional preprocessor definitions to be used for building this library. The default value is `[]`. + - **cincludes**: list of additional include paths to be used for building this library. Paths are relative to the application's root. This can be used for providing external search paths for this library's code — for configuration headers. The default value is `[]`. Example for building an app with a private library: @@ -105,15 +103,14 @@ Example for building an app with a private library: ], ``` -For that snippet, **`fbt`** will build 2 libraries: one from sources in `lib/mbedtls` folder and another from sources in `lib/loclass` folder. For the `mbedtls` library, **`fbt`** will add `lib/mbedtls/include` to the list of include paths for the application and compile only the files specified in the `sources` list. Additionally, **`fbt`** will enable `MBEDTLS_ERROR_C` preprocessor definition for `mbedtls` sources. +For that snippet, **`fbt`** will build 2 libraries: one from sources in `lib/mbedtls` folder and another from sources in the `lib/loclass` folder. For the `mbedtls` library, **`fbt`** will add `lib/mbedtls/include` to the list of include paths for the application and compile only the files specified in the `sources` list. Additionally, **`fbt`** will enable `MBEDTLS_ERROR_C` preprocessor definition for `mbedtls` sources. For the `loclass` library, **`fbt`** will add `lib/loclass` to the list of the include paths for the application and build all sources in that folder. Also, **`fbt`** will disable treating compiler warnings as errors for the `loclass` library, which can be useful when compiling large 3rd-party codebases. Both libraries will be linked with the application. +## `.fam` file contents -## .fam file contents - -.fam file contains one or more Application definitions. For example, here's a part of `applications/service/bt/application.fam`: +The `.fam` file contains one or more application definitions. For example, here's a part of `applications/service/bt/application.fam`: ```python App( @@ -137,4 +134,4 @@ App( ) ``` -For more examples, see .fam files from various firmware parts. +For more examples, see `.fam` files from various firmware parts. diff --git a/documentation/AppsOnSDCard.md b/documentation/AppsOnSDCard.md index d38837276..9ab7e9b26 100644 --- a/documentation/AppsOnSDCard.md +++ b/documentation/AppsOnSDCard.md @@ -1,83 +1,80 @@ # FAP (Flipper Application Package) -[fbt](./fbt.md) has support for building applications as FAP files. FAP are essentially .elf executables with extra metadata and resources bundled in. +[fbt](./fbt.md) supports building applications as FAP files. FAPs are essentially `.elf` executables with extra metadata and resources bundled in. -FAPs are built with `faps` target. They can also be deployed to `dist` folder with `fap_dist` target. +FAPs are built with the `faps` target. They can also be deployed to the `dist` folder with the `fap_dist` target. FAPs do not depend on being run on a specific firmware version. Compatibility is determined by the FAP's metadata, which includes the required [API version](#api-versioning). - ## How to set up an application to be built as a FAP -FAPs are created and developed the same way as internal applications that are part of the firmware. +FAPs are created and developed the same way as internal applications that are part of the firmware. -To build your application as a FAP, just create a folder with your app's source code in `applications_user`, then write its code the way you'd do when creating a regular built-in application. Then configure its `application.fam` manifest — and set its *apptype* to FlipperAppType.EXTERNAL. See [Application Manifests](./AppManifests.md#application-definition) for more details. - - * To build your application, run `./fbt fap_{APPID}`, where APPID is your application's ID in its manifest. - * To build your app, then upload it over USB & run it on Flipper, use `./fbt launch_app APPSRC=applications/path/to/app`. This command is configured in default [VSCode profile](../.vscode/ReadMe.md) as "Launch App on Flipper" build action (Ctrl+Shift+B menu). - * To build all FAPs, run `./fbt faps` or `./fbt fap_dist`. +To build your application as a FAP, create a folder with your app's source code in `applications_user`, then write its code the way you'd do when creating a regular built-in application. Then configure its `application.fam` manifest, and set its _apptype_ to FlipperAppType.EXTERNAL. See [Application Manifests](./AppManifests.md#application-definition) for more details. +- To build your application, run `./fbt fap_{APPID}`, where APPID is your application's ID in its manifest. +- To build your app and upload it over USB to run on Flipper, use `./fbt launch_app APPSRC=applications/path/to/app`. This command is configured in the default [VS Code profile](../.vscode/ReadMe.md) as a "Launch App on Flipper" build action (Ctrl+Shift+B menu). +- To build all FAPs, run `./fbt faps` or `./fbt fap_dist`. ## FAP assets FAPs can include static and animated images as private assets. They will be automatically compiled alongside application sources and can be referenced the same way as assets from the main firmware. -To use that feature, put your images in a subfolder inside your application's folder, then reference that folder in your application's manifest in `fap_icon_assets` field. See [Application Manifests](./AppManifests.md#application-definition) for more details. +To use that feature, put your images in a subfolder inside your application's folder, then reference that folder in your application's manifest in the `fap_icon_assets` field. See [Application Manifests](./AppManifests.md#application-definition) for more details. To use these assets in your application, put `#include "{APPID}_icons.h"` in your application's source code, where `{APPID}` is the `appid` value field from your application's manifest. Then you can use all icons from your application's assets the same way as if they were a part of `assets_icons.h` of the main firmware. -Images and animated icons must follow the same [naming convention](../assets/ReadMe.md#asset-naming-rules) as those from the main firmware. - +Images and animated icons should follow the same [naming convention](../assets/ReadMe.md#asset-naming-rules) as those from the main firmware. ## Debugging FAPs -**`fbt`** includes a script for gdb-py to provide debugging support for FAPs, `debug/flipperapps.py`. It is loaded in default debugging configurations by **`fbt`** and stock VSCode configurations. +**`fbt`** includes a script for gdb-py to provide debugging support for FAPs, `debug/flipperapps.py`. It is loaded in default debugging configurations by **`fbt`** and stock VS Code configurations. -With it, you can debug FAPs as if they were a part of main firmware — inspect variables, set breakpoints, step through the code, etc. +With it, you can debug FAPs as if they were a part of the main firmware — inspect variables, set breakpoints, step through the code, etc. ### Setting up debugging environment -Debugging support script looks up debugging information in the latest firmware build dir (`build/latest`). That directory is symlinked by fbt to the latest firmware configuration (Debug or Release) build dir, when you run `./fbt` for chosen configuration. See [fbt docs](./fbt.md#nb) for details. +The debugging support script looks up debugging information in the latest firmware build directory (`build/latest`). That directory is symlinked by `fbt` to the latest firmware configuration (Debug or Release) build directory when you run `./fbt` for the chosen configuration. See [fbt docs](./fbt.md#nb) for details. + +To debug FAPs, do the following: -So, to debug FAPs, do the following: 1. Build firmware with `./fbt` 2. Flash it with `./fbt flash` -3. [Build your FAP](#how-to-set-up-an-application-to-be-built-as-a-fap) and run it on Flipper. +3. [Build your FAP](#how-to-set-up-an-application-to-be-built-as-a-fap) and run it on Flipper -After that, you can attach with `./fbt debug` or VSCode and use all debug features. +After that, you can attach with `./fbt debug` or VS Code and use all debug features. -It is **important** that firmware and application build type (debug/release) match, and that matching firmware folder is linked as `build/latest`. Otherwise, debugging will not work. +It is **important** that firmware and application build type (debug/release) match and that the matching firmware folder is linked as `build/latest`. Otherwise, debugging will not work. +## How Flipper runs an application from an SD card -## How Flipper runs an application from SD card +Flipper's MCU cannot run code directly from external storage, so it needs to be copied to RAM first. That is done by the App Loader application responsible for loading the FAP from the SD card, verifying its integrity and compatibility, copying it to RAM, and adjusting it for its new location. -Flipper's MCU cannot run code directly from external storage, so it needs to be copied to RAM first. That is done by the App Loader application, which is responsible for loading the FAP from SD card, verifying its integrity and compatibility, copying it to RAM and adjusting it for its new location. +Since FAP has to be loaded to RAM to be executed, the amount of RAM available for allocations from heap is reduced compared to running the same app from flash, as a part of the firmware. Note that the amount of occupied RAM is less than the total FAP file size since only code and data sections are allocated, while the FAP file includes extra information only used at app load time. -Since FAP has to be loaded to RAM to be executed, the amount of RAM available for allocations from heap is reduced compared to running the same app from flash, as a part of firmware. Note that the amount of occupied RAM is less than total FAP file size, since only code and data sections are allocated, while FAP file includes extra information only used at app load time. +Applications are built for a specific API version. It is a part of the hardware target's definition and contains a major and minor version number. The App Loader checks if the application's major API version matches the firmware's major API version. -Applications are built for a specific API version. It is a part of the hardware target's definition and contains a major and minor version number. Application loader checks if the application's major API version matches firmware's major API version. +The App Loader allocates memory for the application and copies it to RAM, processing relocations and providing concrete addresses for imported symbols using the [symbol table](#symbol-table). Then it starts the application. -App loader allocates memory for the application and copies it to RAM, processing relocations and providing concrete addresses for imported symbols using the [symbol table](#symbol-table). Then it starts the application. +## API versioning +Not all parts of firmware are available for external applications. A subset of available functions and variables is defined in the "api_symbols.csv" file, which is a part of the firmware target definition in the `firmware/targets/` directory. -## API Versioning - -Not all parts of firmware are available for external applications. A subset of available functions and variables is defined in "api_symbols.csv" file, which is a part of firmware target definition in `firmware/targets/` directory. - -**`fbt`** uses semantic versioning for API. Major version is incremented when there are breaking changes in the API, minor version is incremented when new features are added. +**`fbt`** uses semantic versioning for the API. The major version is incremented when there are breaking changes in the API. The minor version is incremented when new features are added. Breaking changes include: -- removal of a function or a global variable; -- changing the signature of a function. -API versioning is mostly automated by **`fbt`**. When rebuilding the firmware, **`fbt`** checks if there are any changes in the API exposed by headers gathered from `SDK_HEADERS`. If so, it stops the build, adjusts the API version and asks the user to go through the changes in .csv file. New entries are marked with "`?`" mark, and the user is supposed to change the mark to "`+`" for the entry to be exposed for FAPs, "`-`" for it to be unavailable. +- Removing a function or a global variable +- Changing the signature of a function + +API versioning is mostly automated by **`fbt`**. When rebuilding the firmware, **`fbt`** checks if there are any changes in the API exposed by headers gathered from `SDK_HEADERS`. If so, it stops the build, adjusts the API version, and asks the user to go through the changes in the `.csv` file. New entries are marked with a "`?`" mark, and the user is supposed to change the mark to "`+`" for the entry to be exposed for FAPs, or to "`-`" for it to be unavailable. **`fbt`** will not allow building a firmware until all "`?`" entries are changed to "`+`" or "`-`". -**NB:** **`fbt`** automatically manages the API version. The only case where manually incrementing the major API version is allowed (and required) is when existing "`+`" entries are to be changed to "`-`". +**NB:** **`fbt`** automatically manages the API version. The only case where manually incrementing the major API version is allowed (and required) is when existing "`+`" entries are to be changed to "`-`". -### Symbol Table +### Symbol table The symbol table is a list of symbols exported by firmware and available for external applications. It is generated by **`fbt`** from the API symbols file and is used by the App Loader to resolve addresses of imported symbols. It is build as a part of the `fap_loader` application. -**`fbt`** also checks if all imported symbols are present in the symbol table. If there are any missing symbols, it will issue a warning listing them. Such application won't be able to run on the device until all requires symbols are provided in the symbol table. +**`fbt`** also checks if all imported symbols are present in the symbol table. If there are any missing symbols, it will issue a warning listing them. The application won't be able to run on the device until all required symbols are provided in the symbol table. diff --git a/documentation/KeyCombo.md b/documentation/KeyCombo.md index 93ceb204c..6db5b4113 100644 --- a/documentation/KeyCombo.md +++ b/documentation/KeyCombo.md @@ -1,134 +1,119 @@ # Key Combos -There are times when your Flipper feels blue and doesn't respond to your commands. -In that case, you may find this guide useful. +There are times when your Flipper feels blue and doesn't respond to any of your commands due to a software issue. This guide will help you solve this problem. +## Basic combos -## Basic Combos - - -### Hardware Reset +### Hardware reset - Press `LEFT` and `BACK` and hold for a couple of seconds - Release `LEFT` and `BACK` -This combo performs a hardware reset by pulling MCU reset line down. -Main components involved: Keys -> DD8(NC7SZ32M5X, OR-gate) -> DD1(STM32WB55, MCU) +This combo performs a hardware reset by pulling the MCU reset line down. +Main components involved: Keys -> DD8(NC7SZ32M5X, OR-gate) -> DD1(STM32WB55, MCU). -There is 1 case where it does not work: - -- MCU debug block is active and holding reset line from inside. +It won't work only in one case: +- The MCU debug block is active and holding the reset line from inside. ### Hardware Power Reset -- Disconnect USB and any external power supplies -- Disconnect USB once again -- Make sure that you've disconnected USB and any external power supplies -- Press `BACK` and hold for 30 seconds (Will only work with USB disconnected) -- If you have not disconnected USB, then disconnect USB and repeat previous step -- Release `BACK` key +- Disconnect the USB cable and any external power supplies +- Disconnect the USB once again +- Make sure you've disconnected the USB and any external power supplies +- Press `BACK` and hold for 30 seconds (this will only work with the USB disconnected) +- If you haven't disconnected the USB, then disconnect it and repeat the previous step +- Release the `BACK` key This combo performs a reset by switching SYS power line off and then on. -Main components involved: Keys -> DD6(bq25896, charger) +Main components involved: Keys -> DD6(bq25896, charger). -There is 1 case where it does not work: +It won't work only in one case: - Power supply is connected to USB or 5V_ext - ### Software DFU - Press `LEFT` on boot to enter DFU with Flipper boot-loader -There is 1 case where it does not work: +It won't work only in one case: - Flipper boot-loader is damaged or absent - ### Hardware DFU - Press `OK` on boot to enter DFU with ST boot-loader -There is 1 case where it does not work: +It won't work only in one case: -- Option Bytes are damaged or set to ignore `OK` key - - -## DFU Combos +- Option Bytes are damaged or set to ignore the `OK` key +## DFU combos ### Hardware Reset + Software DFU - Press `LEFT` and `BACK` and hold for a couple of seconds - Release `BACK` -- Device will enter DFU with indication (Blue LED + DFU Screen) +- Device will enter DFU with an indication (Blue LED + DFU Screen) - Release `LEFT` -This combo performs a hardware reset by pulling MCU reset line down. -Then, `LEFT` key indicates to the boot-loader that DFU mode is requested. +This combo performs a hardware reset by pulling the MCU reset line down. Then, the `LEFT` key indicates to the boot-loader that DFU mode is requested. -There are 2 cases where it does not work: +It won't work in two cases: -- MCU debug block is active and holding reset line from inside +- The MCU debug block is active and holding the reset line from inside - Flipper boot-loader is damaged or absent - ### Hardware Reset + Hardware DFU - Press `LEFT`, `BACK` and `OK` and hold for a couple of seconds - Release `BACK` and `LEFT` -- Device will enter DFU without indication +- The device will enter DFU without an indication -This combo performs a hardware reset by pulling MCU reset line down. -Then, `OK` key forces MCU to load internal boot-loader. +This combo performs a hardware reset by pulling the MCU reset line down. Then, the `OK` key forces MCU to load the internal boot-loader. -There are 2 cases where it does not work: - -- MCU debug block is active and holding reset line from inside -- Option Bytes are damaged or set to ignore `OK` key +It won't work in two cases: +- The MCU debug block is active and holding the reset line from inside +- Option Bytes are damaged or set to ignore the `OK` key ### Hardware Power Reset + Software DFU -- Disconnect USB and any external power supplies +- Disconnect the USB and any external power supplies - Press `BACK` and `LEFT` for 30 seconds - Release `BACK` -- Device will enter DFU with indication (Blue LED + DFU Screen) +- The device will enter DFU with an indication (Blue LED + DFU Screen) - Release `LEFT` -- Plug in USB +- Plug in the USB -This combo performs a reset by switching SYS power line off and then on. -Then, `LEFT` key indicates to boot-loader that DFU mode requested. +This combo performs a reset by switching the SYS power line off and then on. Next, the `LEFT` key indicates to the boot-loader that DFU mode is requested. -There are 2 cases where it does not work: +It won't work in two cases: - Power supply is connected to USB or 5V_ext - Flipper boot-loader is damaged or absent - ### Hardware Power Reset + Hardware DFU -- Disconnect USB and any external power supplies +- Disconnect the USB and any external power supplies - Press `BACK` and `OK` and hold for 30 seconds - Release `BACK` and `OK` -- Device will enter DFU without indication -- Plug USB +- The device will enter DFU without indication +- Plug in the USB -This combo performs a reset by switching SYS power line off and then on. -Then, `OK` key forces MCU to load internal boot-loader. +This combo performs a reset by switching the SYS power line off and then on. Next, the `OK` key forces MCU to load the internal boot-loader. -There are 2 cases where it does not work: +It won't work in two cases: - Power supply is connected to USB or 5V_ext -- Option Bytes are damaged or set to ignore `OK` key +- Option Bytes are damaged or set to ignore the `OK` key # Alternative ways to recover your device -If none of the described methods were useful: +If none of the described methods helped you: -- Ensure the battery charged -- Disconnect the battery and connect again (Requires disassembly) -- Try to Flash device with ST-Link or other programmer that supports SWD +- Make sure the battery charged +- Disconnect the battery and connect again (requires disassembly) +- Try to flash the device with ST-Link or another programmer that supports SWD -If you still are here and your device is not working: it's not a software issue. \ No newline at end of file +If you're still here and your device is not working: it's not a software issue. diff --git a/documentation/OTA.md b/documentation/OTA.md index 836bc1b71..9d09c0f7c 100644 --- a/documentation/OTA.md +++ b/documentation/OTA.md @@ -1,84 +1,79 @@ # Executing code from RAM -In Flipper firmware, we have a special boot mode that loads a specially crafted system image into RAM and transfers control to it. System image executing in RAM has full write access to whole Flipper's flash memory — something that's not possible when running main code from the same flash. +In Flipper firmware, we have a special boot mode that loads a specially crafted system image into RAM and transfers control to it. System image executing in RAM has full write access to Flipper's entire flash memory — something that's not possible when running main code from the same flash. We leverage that boot mode to perform OTA firmware updates, including operations on a radio stack running on the second MCU core. - # How does Flipper OTA work? Installation of OTA updates goes through 3 stages: -## 1. Backing up internal storage (`/int/`) +## 1. Backing up internal storage (`/int`) -It is a special partition of Flipper's flash memory, taking up all available space not used by firmware code. Newer versions of firmware may be of different size, and simply installing them would cause flash repartitioning and data loss. - -So, before taking any action upon the firmware, we back up current configuration from `/int/` into a plain tar archive on SD card. +It is a special partition of Flipper's flash memory, taking up all available space not used by the firmware code. Newer versions of firmware may be of different size, and simply installing them would cause flash repartitioning and data loss. +So, before taking any action on the firmware, we back up the current configuration from `/int` into a plain tar archive on the SD card. ## 2. Performing device update -For that, main firmware loads an updater image - a customized build of main Flipper firmware — into RAM and runs it. Updater performs operations on system flash that are described by an Update manifest file. +The main firmware loads an updater image — a customized build of the main Flipper firmware — into RAM and runs it. Updater performs operations on system flash as described by an Update manifest file. -First, if there's a Radio stack image bundled with the update, updater compares its version with currently installed one. If they don't match, updater performs stack deinstallation followed by writing and installing a new one. The installation itself is performed by proprietary software, FUS, running on Core2, and leads to a series of system restarts. +First, if there's a Radio stack image bundled with the update, updater compares its version with the currently installed one. If they don't match, updater performs stack deinstallation followed by writing and installing a new one. The installation itself is performed by proprietary software FUS running on Core2, and leads to a series of system restarts. Then, updater validates and corrects Option Bytes — a special memory region containing low-level configuration for Flipper's MCU. After that, updater loads a `.dfu` file with firmware to be flashed, checks its integrity using CRC32, writes it to system flash and validates written data. - ## 3. Restoring internal storage and updating resources After performing operations on flash memory, the system restarts into newly flashed firmware. Then it performs restoration of previously backed up `/int` contents. -If the update package contains an additional resources archive, it is extracted onto SD card. - +If the update package contains an additional resources archive, it is extracted onto the SD card. # Update manifest -Update packages come with a manifest that contains a description of its contents. The manifest is in Flipper File Format — a simple text file, comprised of key-value pairs. +An update package comes with a manifest that contains a description of its contents. The manifest is in Flipper File Format — a simple text file, comprised of key-value pairs. ## Mandatory fields -An update manifest must contain the following keys in given order: +An update manifest must contain the following keys in the given order: -* __Filetype__: a constant string, "Flipper firmware upgrade configuration"; +- **Filetype**: a constant string, "Flipper firmware upgrade configuration". -* __Version__: manifest version. Current value is 2; +- **Version**: manifest version. The current value is 2. -* __Info__: arbitrary string, describing package contents; +- **Info**: arbitrary string, describing package contents. -* __Target__: hardware revision the package is built for; +- **Target**: hardware revision for which the package is built. -* __Loader__: file name of stage 2 loader that is executed from RAM; +- **Loader**: file name of stage 2 loader that is executed from RAM. -* __Loader CRC__: CRC32 of loader file. Note that it is represented in little-endian hex. +- **Loader CRC**: CRC32 of loader file. Note that it is represented in little-endian hex. ## Optional fields -Other fields may have empty values, is such case, updater skips all operations related to such values. +Other fields may have empty values. In this case, updater skips all operations related to these values. -* __Radio__: file name of radio stack image, provided by STM; +- **Radio**: file name of radio stack image, provided by STM. -* __Radio address__: address to install the radio stack at. It is specified in Release Notes by STM; +- **Radio address**: address to install the radio stack at. It is specified in Release Notes by STM. -* __Radio version__: Radio major, minor and sub versions followed by branch, release, and stack type packed into 6 hex-encoded bytes; +- **Radio version**: radio major, minor and sub versions followed by branch, release and stack type packed into 6 hex-encoded bytes. -* __Radio CRC__: CRC32 of radio image; +- **Radio CRC**: CRC32 of radio image. -* __Resources__: file name of TAR archive with resources to be extracted on SD card; - -* __OB reference__, __OB mask__, __OB write mask__: reference values for validating and correcting option bytes. +- **Resources**: file name of TAR archive with resources to be extracted onto the SD card. +- **OB reference**, **OB mask**, **OB write mask**: reference values for validating and correcting option bytes. # OTA update error codes -We designed the OTA update process to be as fail-safe as possible. We don't start any risky operation before validating all related pieces of data to ensure we don't leave the device in a partially updated, or bricked, state. +We designed the OTA update process to be as fail-safe as possible. We don't start any risky operations before validating all related pieces of data to ensure we don't leave the device in a partially updated, or bricked, state. -Even if something goes wrong, Updater gives you an option to retry failed operations, and reports its state with an error code. These error codes have an `[XX-YY]` format, where `XX` encodes an operation that failed, and `YY` contains extra details on its progress where the error occurred. +Even if something goes wrong, updater allows you to retry failed operations and reports its state with an error code. These error codes have an `[XX-YY]` format, where `XX` encodes the failed operation, and `YY` contains extra details on its progress where the error occurred. | Stage description | Code | Progress | Description | -|:-----------------------:|-------:|------------|--------------------------------------------| +| :---------------------: | -----: | ---------- | ------------------------------------------ | | Loading update manifest | **1** | **13** | Updater reported hardware version mismatch | | | | **20** | Failed to get saved manifest path | | | | **30** | Failed to load manifest | @@ -86,61 +81,60 @@ Even if something goes wrong, Updater gives you an option to retry failed operat | | | **50** | Package has mismatching HW target | | | | **60** | Missing DFU file | | | | **80** | Missing radio firmware file | -| Backing up LFS | **2** | **0-100** | FS read/write error | -| Checking radio FW | **3** | **0-99** | Error reading radio firmware file | +| Backing up LFS | **2** | **0-100** | FS read/write error | +| Checking radio FW | **3** | **0-99** | Error reading radio firmware file | | | | **100** | CRC mismatch | -| Uninstalling radio FW | **4** | **0** | SHCI Delete command error | +| Uninstalling radio FW | **4** | **0** | SHCI Delete command error | | | | **80** | Error awaiting command status | -| Writing radio FW | **5** | **0-100** | Block read/write error | -| Installing radio FW | **6** | **0** | SHCI Install command error | +| Writing radio FW | **5** | **0-100** | Block read/write error | +| Installing radio FW | **6** | **0** | SHCI Install command error | | | | **80** | Error awaiting command status | -| Radio is updating | **7** | **10** | Error waiting for operation completion | -| Validating opt. bytes | **8** | **yy** | Option byte code | -| Checking DFU file | **9** | **0** | Error opening DFU file | +| Radio is updating | **7** | **10** | Error waiting for operation completion | +| Validating opt. bytes | **8** | **yy** | Option byte code | +| Checking DFU file | **9** | **0** | Error opening DFU file | | | | **1-98** | Error reading DFU file | | | | **99-100** | Corrupted DFU file | -| Writing flash | **10** | **0-100** | Block read/write error | -| Validating flash | **11** | **0-100** | Block read/write error | -| Restoring LFS | **12** | **0-100** | FS read/write error | -| Updating resources | **13** | **0-100** | SD card read/write error | - +| Writing flash | **10** | **0-100** | Block read/write error | +| Validating flash | **11** | **0-100** | Block read/write error | +| Restoring LFS | **12** | **0-100** | FS read/write error | +| Updating resources | **13** | **0-100** | SD card read/write error | # Building update packages - ## Full package -To build full update package, including firmware, radio stack and resources for SD card, run `./fbt COMPACT=1 DEBUG=0 updater_package` +To build a full update package, including firmware, radio stack and resources for the SD card, run: +`./fbt COMPACT=1 DEBUG=0 updater_package` ## Minimal package -To build minimal update package, including only firmware, run `./fbt COMPACT=1 DEBUG=0 updater_minpackage` +To build a minimal update package, including only firmware, run: +`./fbt COMPACT=1 DEBUG=0 updater_minpackage` ## Customizing update bundles -Default update packages are built with Bluetooth Light stack. -You can pick a different stack, if your firmware version supports it, and build a bundle with it passing stack type and binary name to `fbt`: +Default update packages are built with Bluetooth Light stack. +You can pick a different stack if your firmware version supports it, and build a bundle with it by passing the stack type and binary name to `fbt`: -`./fbt updater_package COMPACT=1 DEBUG=0 COPRO_OB_DATA=scripts/ob_custradio.data COPRO_STACK_BIN=stm32wb5x_BLE_Stack_full_fw.bin COPRO_STACK_TYPE=ble_full` +`./fbt updater_package COMPACT=1 DEBUG=0 COPRO_OB_DATA=scripts/ob_custradio.data COPRO_STACK_BIN=stm32wb5x_BLE_Stack_full_fw.bin COPRO_STACK_TYPE=ble_full` -Note that `COPRO_OB_DATA` must point to a valid file in `scripts` folder containing reference Option Byte data matching to your radio stack type. +Note that `COPRO_OB_DATA` must point to a valid file in the `scripts` folder containing reference Option Byte data matching your radio stack type. In certain cases, you might have to confirm your intentions by adding `COPRO_DISCLAIMER=...` to the build command line. - ## Building partial update packages -You can customize package contents by calling `scripts/update.py` directly. +You can customize package contents by calling `scripts/update.py` directly. For example, to build a package only for installing BLE FULL stack: ```shell scripts/update.py generate \ - -t f7 -d r13.3_full -v "BLE FULL 13.3" \ - --stage dist/f7/flipper-z-f7-updater-*.bin \ - --radio lib/STM32CubeWB/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x/stm32wb5x_BLE_Stack_full_fw.bin \ - --radiotype ble_full + -t f7 -d r13.3_full -v "BLE FULL 13.3" \ + --stage dist/f7/flipper-z-f7-updater-*.bin \ + --radio lib/STM32CubeWB/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x/stm32wb5x_BLE_Stack_full_fw.bin \ + --radiotype ble_full ``` -For full list of options, check `scripts/update.py generate` help. +For the full list of options, check `scripts/update.py generate` help. diff --git a/documentation/RoadMap.md b/documentation/RoadMap.md index 612e55e62..658bb2086 100644 --- a/documentation/RoadMap.md +++ b/documentation/RoadMap.md @@ -1,9 +1,8 @@ # RoadMap -# Where we are (0.x.x branch) +# Where we are now (0.x.x branch) -Our goal for 0.x.x branch is to build stable, usable apps and API. -The first public release in this branch is 0.43.1. +Our goal for the 0.x.x branch is to build an API and apps that are stable and usable. The first public release in this branch is 0.43.1. ## What's already implemented @@ -17,12 +16,12 @@ The first public release in this branch is 0.43.1. - SubGhz: all most common protocols, reading RAW for everything else - 125kHz RFID: all most common protocols -- NFC: reading/emulating Mifare Ultralight, reading MiFare Classic and DESFire, basic EMV, basic NFC-B/F/V +- NFC: reading/emulating MIFARE Ultralight, reading MIFARE Classic and DESFire, basic EMV, and basic NFC-B/F/V - Infrared: all most common RC protocols, RAW format for everything else - GPIO: UART bridge, basic GPIO controls -- iButton: DS1990, Cyfral, Metacom -- Bad USB: Full USB Rubber Ducky support, some extras for windows alt codes -- U2F: Full U2F specification support +- iButton: DS1990, Cyfral, Metakom +- Bad USB: full USB Rubber Ducky support, some extras for Windows Alt codes +- U2F: full U2F specification support **External applications** @@ -42,8 +41,6 @@ The main goal for 1.0.0 is to provide the first stable version for both Users an ## When will it happen, and where can I see the progress? -Release 1.0.0 will most likely happen around the end of 2023Q1 +Release 1.0.0 will likely happen around the end of 2023Q1. -Development progress can be tracked in our public Miro board: - -https://miro.com/app/board/uXjVO_3D6xU=/?moveToWidget=3458764522498020058&cot=14 +You can track the development progress in our public Miro board: https://miro.com/app/board/uXjVO_3D6xU=/?moveToWidget=3458764522498020058&cot=14 diff --git a/documentation/UnitTests.md b/documentation/UnitTests.md index 896426567..d38d4c4b1 100644 --- a/documentation/UnitTests.md +++ b/documentation/UnitTests.md @@ -1,49 +1,65 @@ # Unit tests + ## Intro -Unit tests are special pieces of code that apply known inputs to the feature code and check the results to see if they were correct. + +Unit tests are special pieces of code that apply known inputs to the feature code and check the results to see if they are correct. They are crucial for writing robust, bug-free code. Flipper Zero firmware includes a separate application called [unit_tests](/applications/debug/unit_tests). -It is run directly on the Flipper Zero in order to employ its hardware features and to rule out any platform-related differences. +It is run directly on Flipper devices in order to employ their hardware features and rule out any platform-related differences. -When contributing code to the Flipper Zero firmware, it is highly desirable to supply unit tests along with the proposed features. +When contributing code to the Flipper Zero firmware, it is highly desirable to supply unit tests along with the proposed features. Running existing unit tests is useful to ensure that the new code doesn't introduce any regressions. ## Running unit tests -In order to run the unit tests, follow these steps: + +To run the unit tests, follow these steps: + 1. Compile the firmware with the tests enabled: `./fbt FIRMWARE_APP_SET=unit_tests`. 2. Flash the firmware using your preferred method. 3. Copy the [assets/unit_tests](assets/unit_tests) folder to the root of your Flipper Zero's SD card. 4. Launch the CLI session and run the `unit_tests` command. -**NOTE:** To run a particular test (and skip all others), specify its name as the command argument. +**NOTE:** To run a particular test (and skip all others), specify its name as the command argument. See [test_index.c](applications/debug/unit_tests/test_index.c) for the complete list of test names. ## Adding unit tests + ### General + #### Entry point + The common entry point for all tests is the [unit_tests](applications/debug/unit_tests) application. Test-specific code is placed into an arbitrarily named subdirectory and is then called from the [test_index.c](applications/debug/unit_tests/test_index.c) source file. + #### Test assets -Some unit tests require external data in order to function. These files (commonly called assets) reside in the [assets/unit_tests](/assets/unit_tests) directory in their respective subdirectories. Asset files can be of any type (plain text, FlipperFormat(FFF), binary, etc). + +Some unit tests require external data in order to function. These files (commonly called assets) reside in the [assets/unit_tests](/assets/unit_tests) directory in their respective subdirectories. Asset files can be of any type (plain text, FlipperFormat (FFF), binary, etc.). + ### Application-specific + #### Infrared + Each infrared protocol has a corresponding set of unit tests, so it makes sense to implement one when adding support for a new protocol. -In order to add unit tests for your protocol, follow these steps: +To add unit tests for your protocol, follow these steps: + 1. Create a file named `test_.irtest` in the [assets](assets/unit_tests/infrared) directory. 2. Fill it with the test data (more on it below). 3. Add the test code to [infrared_test.c](applications/debug/unit_tests/infrared/infrared_test.c). 4. Update the [assets](assets/unit_tests/infrared) on your Flipper Zero and run the tests to see if they pass. ##### Test data format -Each unit test has 3 sections: -1. `decoder` - takes in raw signal and outputs decoded messages. + +Each unit test has three sections: + +1. `decoder` - takes in a raw signal and outputs decoded messages. 2. `encoder` - takes in decoded messages and outputs a raw signal. -3. `encoder_decoder` - takes in decoded messages, turns them into a raw signal, and then decodes again. +3. `encoder_decoder` - takes in decoded messages, turns them into a raw signal, and then decodes again. Infrared test asset files have an `.irtest` extension and are regular `.ir` files with a few additions. -Decoder input data has signal names `decoder_input_N`, where N is a test sequence number. Expected data goes under the name `decoder_expected_N`. When testing the encoder these two are switched. +Decoder input data has signal names `decoder_input_N`, where N is a test sequence number. Expected data goes under the name `decoder_expected_N`. When testing the encoder, these two are switched. -Decoded data is represented in arrays (since a single raw signal may decode to several messages). If there is only one signal, then it has to be an array of size 1. Use the existing files as syntax examples. +Decoded data is represented in arrays (since a single raw signal may be decoded into several messages). If there is only one signal, then it has to be an array of size 1. Use the existing files as syntax examples. ##### Getting raw signals + Recording raw IR signals are possible using the Flipper Zero. Launch the CLI session, run `ir rx raw`, then point the remote towards Flipper's receiver and send the signals. The raw signal data will be printed to the console in a convenient format. diff --git a/documentation/UniversalRemotes.md b/documentation/UniversalRemotes.md index 186a0e65a..264829e16 100644 --- a/documentation/UniversalRemotes.md +++ b/documentation/UniversalRemotes.md @@ -1,62 +1,69 @@ # Universal Remotes + ## Televisions -Adding your TV set to the universal remote is quite straightforward. Up to 6 signals can be recorded: `Power`, `Mute`, `Vol_up`, `Vol_dn`, `Ch_next`, `Ch_prev`. Any of them can be omitted if not supported by the TV. + +Adding your TV set to the universal remote is quite straightforward. Up to 6 signals can be recorded: `Power`, `Mute`, `Vol_up`, `Vol_dn`, `Ch_next`, and `Ch_prev`. Any of them can be omitted if not supported by your TV. Each signal is recorded using the following algorithm: + 1. Get the remote and point it to Flipper's IR receiver. 2. Start learning a new remote if it's the first button or press `+` to add a new button otherwise. 3. Press a remote button and save it under a corresponding name. 4. Repeat steps 2-3 until all required signals are saved. -The signal names are self-explanatory. Don't forget to make sure that every recorded signal does what it's supposed to. +The signal names are self-explanatory. Remember to make sure that every recorded signal does what it's supposed to. If everything checks out, append these signals **to the end** of the [TV universal remote file](/assets/resources/infrared/assets/tv.ir). -## Audio Players -Adding your audio player to the universal remote is done in the same manner as described above. Up to 8 signals can be recorded: `Power`, `Play`, `Pause`, `Vol_up`, `Vol_dn`, `Next`, `Prev`, `Mute`. Any of them can be omitted if not supported by the player. +## Audio players -The signal names are self-explanatory. -On many remotes, the `Play` button doubles as `Pause`. In this case record it as `Play` omitting the `Pause`. +Adding your audio player to the universal remote is done in the same manner as described above. Up to 8 signals can be recorded: `Power`, `Play`, `Pause`, `Vol_up`, `Vol_dn`, `Next`, `Prev`, and `Mute`. Any of them can be omitted if not supported by the player. + +The signal names are self-explanatory. +On many remotes, the `Play` button doubles as `Pause`. In this case, record it as `Play` omitting the `Pause`. Make sure that every signal does what it's supposed to. -If everything checks out, append these signals **to the end** of the [Audio players universal remote file](/assets/resources/infrared/assets/audio.ir). +If everything checks out, append these signals **to the end** of the [audio player universal remote file](/assets/resources/infrared/assets/audio.ir). + +## Air conditioners -## Air Conditioners Air conditioners differ from most other infrared-controlled devices because their state is tracked by the remote. -The majority of A/C remotes have a small display which shows current mode, temperature and other settings. +The majority of A/C remotes have a small display that shows the current mode, temperature, and other settings. When the user presses a button, a whole set of parameters is transmitted to the device, which must be recorded and used as a whole. -In order to add a particular air conditioner to the universal remote, 6 signals must be recorded: `Off`, `Dh`, `Cool_hi`, `Cool_lo`, `Heat_hi`, `Heat_lo`. +In order to add a particular air conditioner to the universal remote, 6 signals must be recorded: `Off`, `Dh`, `Cool_hi`, `Cool_lo`, `Heat_hi`, and `Heat_lo`. Each signal (except `Off`) is recorded using the following algorithm: 1. Get the remote and press the **Power Button** so that the display shows that A/C is ON. -2. Set the A/C to the corresponding mode (see table below), while leaving other parameters such as fan speed or vane on **AUTO** (if applicable). +2. Set the A/C to the corresponding mode (see table below), leaving other parameters such as fan speed or vane on **AUTO** (if applicable). 3. Press the **POWER** button to switch the A/C off. 4. Start learning a new remote on Flipper if it's the first button or press `+` to add a new button otherwise. 5. Point the remote to Flipper's IR receiver as directed and press **POWER** button once again. 6. Save the resulting signal under the specified name. -7. Repeat the steps 2-6 for each signal from the table below. +7. Repeat steps 2-6 for each signal from the table below. -| Signal | Mode | Temperature | Note | +| Signal | Mode | Temperature | Note | | :-----: | :--------: | :---------: | ----------------------------------- | -| Dh | Dehumidify | N/A | | -| Cool_hi | Cooling | See note | Lowest temperature in cooling mode | -| Cool_lo | Cooling | 23°C | | -| Heat_hi | Heating | See note | Highest temperature in heating mode | -| Heat_lo | Heating | 23°C | | +| Dh | Dehumidify | N/A | | +| Cool_hi | Cooling | See note | Lowest temperature in cooling mode | +| Cool_lo | Cooling | 23°C | | +| Heat_hi | Heating | See note | Highest temperature in heating mode | +| Heat_lo | Heating | 23°C | | Finally, record the `Off` signal: -1. Make sure the display shows that A/C is ON. + +1. Make sure the display shows that the A/C is ON. 2. Start learning a new signal on Flipper and point the remote towards the IR receiver. 3. Press the **POWER** button so that the remote shows the OFF state. 4. Save the resulting signal under the name `Off`. -The resulting remote file should now contain 6 signals. Any of them can be omitted, but that will mean that this functionality will not be used. +The resulting remote file should now contain 6 signals. You can omit any of them, but you then won't be able to use their functionality. Test the file against the actual device. Make sure that every signal does what it's supposed to. If everything checks out, append these signals **to the end** of the [A/C universal remote file](/assets/resources/infrared/assets/ac.ir). ## Final steps -The order of signals is not important, but they must be preceded by a following comment: `# Model: ` in order to keep the library organised. + +The order of signals is not important, but they should be preceded by the following comment: `# Model: ` in order to keep the library organized. When done, open a pull request containing the changed file. diff --git a/documentation/fbt.md b/documentation/fbt.md index 4726268d0..7b1aa8b48 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -5,102 +5,99 @@ It is invoked by `./fbt` in the firmware project root directory. Internally, it ## Requirements -Please install Python packages required by assets build scripts: `pip3 install -r scripts/requirements.txt` +Install Python packages required by assets build scripts: `pip3 install -r scripts/requirements.txt` ## NB -* `fbt` constructs all referenced environments & their targets' dependency trees on startup. So, to keep startup time as low as possible, we're hiding construction of certain targets behind command-line options. -* `fbt` always performs `git submodule update --init` on start, unless you set `FBT_NO_SYNC=1` in environment: - * On Windows, that's `set "FBT_NO_SYNC=1"` in the shell you're running `fbt` from - * On \*nix, it's `$ FBT_NO_SYNC=1 ./fbt ...` -* `fbt` builds updater & firmware in separate subdirectories in `build`, with their names depending on optimization settings (`COMPACT` & `DEBUG` options). However, for ease of integration with IDEs, latest built variant's directory is always linked as `built/latest`. Additionally, `compile_commands.json` is generated in that folder, which is used for code completion support in IDE. +- `fbt` constructs all referenced environments and their targets' dependency trees on startup. So, to keep startup time as low as possible, we're hiding the construction of certain targets behind command-line options. +- `fbt` always performs `git submodule update --init` on start, unless you set `FBT_NO_SYNC=1` in the environment: + - On Windows, it's `set "FBT_NO_SYNC=1"` in the shell you're running `fbt` from + - On \*nix, it's `$ FBT_NO_SYNC=1 ./fbt ...` +- `fbt` builds updater & firmware in separate subdirectories in `build`, and their names depend on optimization settings (`COMPACT` & `DEBUG` options). However, for ease of integration with IDEs, the latest built variant's directory is always linked as `built/latest`. Additionally, `compile_commands.json` is generated in that folder (used for code completion support in IDE). ## Invoking FBT -To build with FBT, call it specifying configuration options & targets to build. For example, +To build with FBT, call it and specify configuration options & targets to build. For example: `./fbt COMPACT=1 DEBUG=0 VERBOSE=1 updater_package copro_dist` -To run cleanup (think of `make clean`) for specified targets, add `-c` option. +To run cleanup (think of `make clean`) for specified targets, add the `-c` option. ## VSCode integration -`fbt` includes basic development environment configuration for VSCode. To deploy it, run `./fbt vscode_dist`. That will copy initial environment configuration to `.vscode` folder. After that, you can use that configuration by starting VSCode and choosing firmware root folder in "File > Open Folder" menu. - - * On first start, you'll be prompted to install recommended plug-ins. Please install them for best development experience. _You can find a list of them in `.vscode/extensions.json`._ - * Basic build tasks are invoked in Ctrl+Shift+B menu. - * Debugging requires a supported probe. That includes: - * Wi-Fi devboard with stock firmware (blackmagic), - * ST-Link and compatible devices, - * J-Link for flashing and debugging (in VSCode only). _Note that J-Link tools are not included with our toolchain and you have to [download](https://www.segger.com/downloads/jlink/) them yourself and put on your system's PATH._ - * Without a supported probe, you can install firmware on Flipper using USB installation method. +`fbt` includes basic development environment configuration for VS Code. Run `./fbt vscode_dist` to deploy it. That will copy the initial environment configuration to the `.vscode` folder. After that, you can use that configuration by starting VS Code and choosing the firmware root folder in the "File > Open Folder" menu. +- On the first start, you'll be prompted to install recommended plugins. We highly recommend installing them for the best development experience. _You can find a list of them in `.vscode/extensions.json`._ +- Basic build tasks are invoked in the Ctrl+Shift+B menu. +- Debugging requires a supported probe. That includes: + - Wi-Fi devboard with stock firmware (blackmagic). + - ST-Link and compatible devices. + - J-Link for flashing and debugging (in VSCode only). _Note that J-Link tools are not included with our toolchain and you have to [download](https://www.segger.com/downloads/jlink/) them yourself and put them on your system's PATH._ +- Without a supported probe, you can install firmware on Flipper using the USB installation method. ## FBT targets **`fbt`** keeps track of internal dependencies, so you only need to build the highest-level target you need, and **`fbt`** will make sure everything they depend on is up-to-date. ### High-level (what you most likely need) - -- `fw_dist` - build & publish firmware to `dist` folder. This is a default target, when no other are specified -- `fap_dist` - build external plugins & publish to `dist` folder -- `updater_package`, `updater_minpackage` - build self-update package. Minimal version only includes firmware's DFU file; full version also includes radio stack & resources for SD card -- `copro_dist` - bundle Core2 FUS+stack binaries for qFlipper -- `flash` - flash attached device with OpenOCD over ST-Link -- `flash_usb`, `flash_usb_full` - build, upload and install update package to device over USB. See details on `updater_package`, `updater_minpackage` -- `debug` - build and flash firmware, then attach with gdb with firmware's .elf loaded -- `debug_other`, `debug_other_blackmagic` - attach gdb without loading any .elf. Allows to manually add external elf files with `add-symbol-file` in gdb -- `updater_debug` - attach gdb with updater's .elf loaded -- `blackmagic` - debug firmware with Blackmagic probe (WiFi dev board) -- `openocd` - just start OpenOCD -- `get_blackmagic` - output blackmagic address in gdb remote format. Useful for IDE integration + +- `fw_dist` - build & publish firmware to the `dist` folder. This is a default target when no others are specified. +- `fap_dist` - build external plugins & publish to the `dist` folder. +- `updater_package`, `updater_minpackage` - build a self-update package. The minimal version only includes the firmware's DFU file; the full version also includes a radio stack & resources for the SD card. +- `copro_dist` - bundle Core2 FUS+stack binaries for qFlipper. +- `flash` - flash the attached device with OpenOCD over ST-Link. +- `flash_usb`, `flash_usb_full` - build, upload and install the update package to the device over USB. See details on `updater_package` and `updater_minpackage`. +- `debug` - build and flash firmware, then attach with gdb with firmware's .elf loaded. +- `debug_other`, `debug_other_blackmagic` - attach GDB without loading any `.elf`. It will allow you to manually add external `.elf` files with `add-symbol-file` in GDB. +- `updater_debug` - attach GDB with the updater's `.elf` loaded. +- `blackmagic` - debug firmware with Blackmagic probe (WiFi dev board). +- `openocd` - just start OpenOCD. +- `get_blackmagic` - output the blackmagic address in the GDB remote format. Useful for IDE integration. - `get_stlink` - output serial numbers for attached STLink probes. Used for specifying an adapter with `OPENOCD_ADAPTER_SERIAL=...`. -- `lint`, `format` - run clang-format on C source code to check and reformat it according to `.clang-format` specs -- `lint_py`, `format_py` - run [black](https://black.readthedocs.io/en/stable/index.html) on Python source code, build system files & application manifests -- `cli` - start Flipper CLI session over USB +- `lint`, `format` - run clang-format on the C source code to check and reformat it according to the `.clang-format` specs. +- `lint_py`, `format_py` - run [black](https://black.readthedocs.io/en/stable/index.html) on the Python source code, build system files & application manifests. +- `cli` - start a Flipper CLI session over USB. ### Firmware targets -- `faps` - build all external & plugin apps as [.faps](./AppsOnSDCard.md#fap-flipper-application-package). +- `faps` - build all external & plugin apps as [`.faps`](./AppsOnSDCard.md#fap-flipper-application-package). - **`fbt`** also defines per-app targets. For example, for an app with `appid=snake_game` target names are: - - `fap_snake_game`, etc - build single app as .fap by its application ID. - - Check out [`--extra-ext-apps`](#command-line-parameters) for force adding extra apps to external build - - `fap_snake_game_list`, etc - generate source + assembler listing for app's .fap -- `flash`, `firmware_flash` - flash current version to attached device with OpenOCD over ST-Link -- `jflash` - flash current version to attached device with JFlash using J-Link probe. JFlash executable must be on your $PATH -- `flash_blackmagic` - flash current version to attached device with Blackmagic probe -- `firmware_all`, `updater_all` - build basic set of binaries -- `firmware_list`, `updater_list` - generate source + assembler listing -- `firmware_cdb`, `updater_cdb` - generate `compilation_database.json` file for external tools and IDEs. It can be created without actually building the firmware. + - `fap_snake_game`, etc. - build single app as `.fap` by its application ID. + - Check out [`--extra-ext-apps`](#command-line-parameters) for force adding extra apps to external build. + - `fap_snake_game_list`, etc - generate source + assembler listing for app's `.fap`. +- `flash`, `firmware_flash` - flash the current version to the attached device with OpenOCD over ST-Link. +- `jflash` - flash the current version to the attached device with JFlash using a J-Link probe. The JFlash executable must be on your `$PATH`. +- `flash_blackmagic` - flash the current version to the attached device with a Blackmagic probe. +- `firmware_all`, `updater_all` - build a basic set of binaries. +- `firmware_list`, `updater_list` - generate source + assembler listing. +- `firmware_cdb`, `updater_cdb` - generate a `compilation_database.json` file for external tools and IDEs. It can be created without actually building the firmware. ### Assets -- `resources` - build resources and their Manifest - - `dolphin_ext` - process dolphin animations for SD card -- `icons` - generate .c+.h for icons from png assets -- `proto` - generate .pb.c+.pb.h for .proto sources -- `proto_ver` - generate .h with protobuf version -- `dolphin_internal`, `dolphin_blocking` - generate .c+.h for corresponding dolphin assets - +- `resources` - build resources and their manifest files + - `dolphin_ext` - process dolphin animations for the SD card +- `icons` - generate `.c+.h` for icons from PNG assets +- `proto` - generate `.pb.c+.pb.h` for `.proto` sources +- `proto_ver` - generate `.h` with a protobuf version +- `dolphin_internal`, `dolphin_blocking` - generate `.c+.h` for corresponding dolphin assets ## Command-line parameters -- `--options optionfile.py` (default value `fbt_options.py`) - load file with multiple configuration values -- `--extra-int-apps=app1,app2,appN` - forces listed apps to be built as internal with `firmware` target -- `--extra-ext-apps=app1,app2,appN` - forces listed apps to be built as external with `firmware_extapps` target -- `--proxy-env=VAR1,VAR2` - additional environment variables to expose to subprocesses spawned by `fbt`. By default, `fbt` sanitizes execution environment and doesn't forward all inherited environment variables. You can find list of variables that are always forwarded in `environ.scons` file. +- `--options optionfile.py` (default value `fbt_options.py`) - load a file with multiple configuration values +- `--extra-int-apps=app1,app2,appN` - force listed apps to be built as internal with the `firmware` target +- `--extra-ext-apps=app1,app2,appN` - force listed apps to be built as external with the `firmware_extapps` target +- `--proxy-env=VAR1,VAR2` - additional environment variables to expose to subprocesses spawned by `fbt`. By default, `fbt` sanitizes the execution environment and doesn't forward all inherited environment variables. You can find the list of variables that are always forwarded in the `environ.scons` file. +## Configuration -## Configuration - -Default configuration variables are set in the configuration file `fbt_options.py`. -Values set on command-line have higher precedence over configuration file. +Default configuration variables are set in the configuration file: `fbt_options.py`. +Values set in the command line have higher precedence over the configuration file. You can find out available options with `./fbt -h`. ### Firmware application set -You can create customized firmware builds by modifying the application list to be included in the build. Application presets are configured with the `FIRMWARE_APPS` option, which is a map(configuration_name:str -> application_list:tuple(str)). To specify application set to use in a build, set `FIRMWARE_APP_SET` to its name. +You can create customized firmware builds by modifying the list of applications to be included in the build. Application presets are configured with the `FIRMWARE_APPS` option, which is a `map(configuration_name:str -> application_list:tuple(str))`. To specify an application set to use in the build, set `FIRMWARE_APP_SET` to its name. For example, to build a firmware image with unit tests, run `./fbt FIRMWARE_APP_SET=unit_tests`. Check out `fbt_options.py` for details. diff --git a/documentation/file_formats/BadUsbScriptFormat.md b/documentation/file_formats/BadUsbScriptFormat.md index 713a9ff26..fa5038742 100644 --- a/documentation/file_formats/BadUsbScriptFormat.md +++ b/documentation/file_formats/BadUsbScriptFormat.md @@ -1,16 +1,23 @@ # Command syntax -BadUsb app uses extended Duckyscript syntax. It is compatible with classic USB Rubber Ducky 1.0 scripts, but provides some additional commands and features, such as custom USB ID, ALT+Numpad input method, SYSRQ command and more functional keys. + +BadUsb app uses extended Duckyscript syntax. It is compatible with classic USB Rubber Ducky 1.0 scripts but provides some additional commands and features, such as custom USB ID, ALT+Numpad input method, SYSRQ command, and more functional keys. + # Script file format -BadUsb app can execute only text scrips from .txt files, no compilation is required. Both `\n` and `\r\n` line endings are supported. Empty lines are allowed. You can use spaces ore tabs for line indentation. + +BadUsb app can execute only text scrips from `.txt` files, no compilation is required. Both `\n` and `\r\n` line endings are supported. Empty lines are allowed. You can use spaces or tabs for line indentation. + # Command set + ## Comment line -Just a single comment line. All text after REM command will be ignored by interpreter + +Just a single comment line. The interpreter will ignore all text after the REM command. |Command|Parameters|Notes| |-|-|-| |REM|Comment text|| ## Delay -Pause script execution by defined time + +Pause script execution by a defined time. |Command|Parameters|Notes| |-|-|-| |DELAY|Delay value in ms|Single delay| @@ -18,35 +25,37 @@ Pause script execution by defined time |DEFAULTDELAY|Delay value in ms|Same as DEFAULT_DELAY| ## Special keys -|Command|Notes| -|-|-| -|DOWNARROW / DOWN|| -|LEFTARROW / LEFT|| -|RIGHTARROW / RIGHT|| -|UPARROW / UP|| -|ENTER|| -|DELETE|| -|BACKSPACE|| -|END|| -|HOME|| -|ESCAPE / ESC|| -|INSERT|| -|PAGEUP|| -|PAGEDOWN|| -|CAPSLOCK|| -|NUMLOCK|| -|SCROLLLOCK|| -|PRINTSCREEN|| -|BREAK|Pause/Break key| -|PAUSE|Pause/Break key| -|SPACE|| -|TAB|| -|MENU|Context menu key| -|APP|Same as MENU| -|Fx|F1-F12 keys| + +| Command | Notes | +| ------------------ | ---------------- | +| DOWNARROW / DOWN | | +| LEFTARROW / LEFT | | +| RIGHTARROW / RIGHT | | +| UPARROW / UP | | +| ENTER | | +| DELETE | | +| BACKSPACE | | +| END | | +| HOME | | +| ESCAPE / ESC | | +| INSERT | | +| PAGEUP | | +| PAGEDOWN | | +| CAPSLOCK | | +| NUMLOCK | | +| SCROLLLOCK | | +| PRINTSCREEN | | +| BREAK | Pause/Break key | +| PAUSE | Pause/Break key | +| SPACE | | +| TAB | | +| MENU | Context menu key | +| APP | Same as MENU | +| Fx | F1-F12 keys | ## Modifier keys -Can be combined with special key command or single character + +Can be combined with a special key command or a single character. |Command|Notes| |-|-| |CONTROL / CTRL|| @@ -58,35 +67,44 @@ Can be combined with special key command or single character |ALT-SHIFT|ALT+SHIFT| |ALT-GUI|ALT+WIN| |GUI-SHIFT|WIN+SHIFT| + ## String -|Command|Parameters|Notes| -|-|-|-| -|STRING|Text string|Print text string| + +| Command | Parameters | Notes | +| ------- | ----------- | ----------------- | +| STRING | Text string | Print text string | + ## Repeat -|Command|Parameters|Notes| -|-|-|-| -|REPEAT|Number of additional repeats|Repeat previous command| + +| Command | Parameters | Notes | +| ------- | ---------------------------- | ----------------------- | +| REPEAT | Number of additional repeats | Repeat previous command | + ## ALT+Numpad input -On Windows and some Linux systems you can print character by pressing ALT key and entering its code on numpad + +On Windows and some Linux systems, you can print characters by pressing `ALT` key and entering its code on Numpad. |Command|Parameters|Notes| |-|-|-| |ALTCHAR|Character code|Print single character| |ALTSTRING|Text string|Print text string using ALT+Numpad method| |ALTCODE|Text string|Same as ALTSTRING, presents in some Duckyscript implementations| + ## SysRq + Send [SysRq command](https://en.wikipedia.org/wiki/Magic_SysRq_key) |Command|Parameters|Notes| |-|-|-| |SYSRQ|Single character|| -## USB device ID -You can set custom ID of Flipper USB HID device. ID command should be in the **first line** of script, it is executed before script run. -|Command|Parameters|Notes| -|-|-|-| -|ID|VID:PID Manufacturer:Product|| +## USB device ID + +You can set the custom ID of the Flipper USB HID device. ID command should be in the **first line** of script, it is executed before script run. + +| Command | Parameters | Notes | +| ------- | ---------------------------- | ----- | +| ID | VID:PID Manufacturer:Product | | Example: `ID 1234:abcd Flipper Devices:Flipper Zero` -VID and PID are hex codes and are mandatory, Manufacturer and Product are text strings and are optional. - +VID and PID are hex codes and are mandatory. Manufacturer and Product are text strings and are optional. diff --git a/documentation/file_formats/InfraredFileFormats.md b/documentation/file_formats/InfraredFileFormats.md index 409bf26e8..3c0acdcb7 100644 --- a/documentation/file_formats/InfraredFileFormats.md +++ b/documentation/file_formats/InfraredFileFormats.md @@ -1,11 +1,12 @@ # Infrared Flipper File Formats ## Infrared Remote File Format + ### Example Filetype: IR signals file Version: 1 - # + # name: Button_1 type: parsed protocol: NECext @@ -25,48 +26,59 @@ command: 15 00 00 00 ### Description + Filename extension: `.ir` -This file format is used to store an infrared remote that consists of an arbitrary number of buttons. +This file format is used to store an infrared remote that consists of an arbitrary number of buttons. Each button is separated from others by a comment character (`#`) for better readability. -Known protocols are represented in the `parsed` form, whereas non-recognised signals may be saved and re-transmitted as `raw` data. +Known protocols are represented in the `parsed` form, whereas non-recognized signals may be saved and re-transmitted as `raw` data. #### Version history: + 1. Initial version. #### Format fields -| Name | Use | Type | Description | -| ---------- | ------- | ------ |------------ | -| name | both | string | Name of the button. Only printable ASCII characters are allowed. | -| type | both | string | Type of the signal. Must be `parsed` or `raw`. | -| protocol | parsed | string | Name of the infrared protocol. Refer to `ir` console command for the complete list of supported protocols. | -| address | parsed | hex | Payload address. Must be 4 bytes long. | -| command | parsed | hex | Payload command. Must be 4 bytes long. | -| frequency | raw | uint32 | Carrier frequency, in Hertz, usually 38000 Hz. | -| duty_cycle | raw | float | Carrier duty cycle, usually 0.33. | -| data | raw | uint32 | Raw signal timings, in microseconds between logic level changes. Individual elements must be space-separated. Maximum timings amount is 1024. | + +| Name | Use | Type | Description | +| ---------- | ------ | ------ | --------------------------------------------------------------------------------------------------------------------------------------------- | +| name | both | string | Name of the button. Only printable ASCII characters are allowed. | +| type | both | string | Type of the signal. Must be `parsed` or `raw`. | +| protocol | parsed | string | Name of the infrared protocol. Refer to `ir` console command for the complete list of supported protocols. | +| address | parsed | hex | Payload address. Must be 4 bytes long. | +| command | parsed | hex | Payload command. Must be 4 bytes long. | +| frequency | raw | uint32 | Carrier frequency, in Hertz, usually 38000 Hz. | +| duty_cycle | raw | float | Carrier duty cycle, usually 0.33. | +| data | raw | uint32 | Raw signal timings, in microseconds between logic level changes. Individual elements must be space-separated. Maximum timings amount is 1024. | ## Infrared Library File Format + ### Examples + - [TV Universal Library](/assets/resources/infrared/assets/tv.ir) - [A/C Universal Library](/assets/resources/infrared/assets/ac.ir) - [Audio Universal Library](/assets/resources/infrared/assets/audio.ir) ### Description + Filename extension: `.ir` This file format is used to store universal remote libraries. It is identical to the previous format, differing only in the `Filetype` field.\ -It also has predefined button names for each universal library type, so that the universal remote application could understand them. +It also has predefined button names for each universal library type, so that the universal remote application can understand them. See [Universal Remotes](/documentation/UniversalRemotes.md) for more information. ### Version history: + 1. Initial version. ## Infrared Test File Format + ### Examples + See [Infrared Unit Tests](/assets/unit_tests/infrared/) for various examples. + ### Description + Filename extension: `.irtest` This file format is used to store technical test data that is too large to keep directly in the firmware. @@ -78,34 +90,38 @@ Known protocols are represented in the `parsed_array` form, whereas raw data has Note: a single parsed signal must be represented as an array of size 1. ### Version history: + 1. Initial version. #### Format fields -| Name | Use | Type | Description | -| ---------- | ------------ | ------ |------------ | + +| Name | Use | Type | Description | +| ---------- | ------------ | ------ | ---------------------------------------------------------------- | | name | both | string | Name of the signal. Only printable ASCII characters are allowed. | -| type | both | string | Type of the signal. Must be `parsed_array` or `raw`. | -| count | parsed_array | uint32 | The number of parsed signals in an array. Must be at least 1. | -| protocol | parsed_array | string | Same as in previous formats. | -| address | parsed_array | hex | Ditto. | -| command | parsed_array | hex | Ditto. | -| repeat | parsed_array | bool | Indicates whether the signal is a repeated button press. | -| frequency | raw | uint32 | Same as in previous formats. | -| duty_cycle | raw | float | Ditto. | -| data | raw | uint32 | Ditto. | +| type | both | string | Type of the signal. Must be `parsed_array` or `raw`. | +| count | parsed_array | uint32 | The number of parsed signals in an array. Must be at least 1. | +| protocol | parsed_array | string | Same as in previous formats. | +| address | parsed_array | hex | Ditto. | +| command | parsed_array | hex | Ditto. | +| repeat | parsed_array | bool | Indicates whether the signal is a repeated button press. | +| frequency | raw | uint32 | Same as in previous formats. | +| duty_cycle | raw | float | Ditto. | +| data | raw | uint32 | Ditto. | #### Signal names + The signal names in an `.irtest` file follow a convention ``, where the name is one of: + - decoder_input - decoder_expected - encoder_decoder_input, -and the number is a sequential integer: 1, 2, 3...etc, which produces names like `decoder_input1`, `encoder_decoder_input3`, and so on. +and the number is a sequential integer: 1, 2, 3, etc., which produces names like `decoder_input1`, `encoder_decoder_input3`, and so on. | Name | Type | Description | -| --------------------- | ------------ |-------------------------------------------------------------------------------------------------------| -| decoder_input | raw | A raw signal containing the decoder input. Is also used as the expected encoder output. | -| decoder_expected | parsed_array | An array of parsed signals containing the expected decoder output. Is also used as the encoder input. | +| --------------------- | ------------ | ----------------------------------------------------------------------------------------------------- | +| decoder_input | raw | A raw signal containing the decoder input. Also used as the expected encoder output. | +| decoder_expected | parsed_array | An array of parsed signals containing the expected decoder output. Also used as the encoder input. | | encoder_decoder_input | parsed_array | An array of parsed signals containing both the encoder-decoder input and expected output. | See [Unit Tests](/documentation/UnitTests.md#infrared) for more info. diff --git a/documentation/file_formats/LfRfidFileFormat.md b/documentation/file_formats/LfRfidFileFormat.md index 715c49f6a..5143d8bc1 100644 --- a/documentation/file_formats/LfRfidFileFormat.md +++ b/documentation/file_formats/LfRfidFileFormat.md @@ -1,17 +1,19 @@ # LF RFID key file format ## Example + ``` Filetype: Flipper RFID key Version: 1 Key type: EM4100 Data: 01 23 45 67 89 ``` + ## Description Filename extension: `.rfid` -The file stores single RFID key of type defined by `Key type` parameter +The file stores a single RFID key of the type defined by the `Key type` parameter. ### Version history @@ -19,29 +21,29 @@ The file stores single RFID key of type defined by `Key type` parameter ### Format fields -|Name|Description| -|-|-| -|Key type|Key protocol type| -|Data|Key data (HEX values)| +| Name | Description | +| -------- | --------------------- | +| Key type | Key protocol type | +| Data | Key data (HEX values) | ### Supported key types -|Type|Full name| -|-|-| -|EM4100|EM-Micro EM4100| -|H10301|HID H10301| -|Idteck|IDTECK| -|Indala26|Motorola Indala26| -|IOProxXSF|Kantech IOProxXSF| -|AWID|AWID| -|FDX-A|FECAVA FDX-A| -|FDX-B|ISO FDX-B| -|HIDProx|Generic HIDProx| -|HIDExt|Generic HIDExt| -|Pyramid|Farpointe Pyramid| -|Viking|Viking| -|Jablotron|Jablotron| -|Paradox|Paradox| -|PAC/Stanley|PAC/Stanley| -|Keri|Keri| -|Gallagher|Gallagher| \ No newline at end of file +| Type | Full name | +| ----------- | ----------------- | +| EM4100 | EM-Micro EM4100 | +| H10301 | HID H10301 | +| Idteck | IDTECK | +| Indala26 | Motorola Indala26 | +| IOProxXSF | Kantech IOProxXSF | +| AWID | AWID | +| FDX-A | FECAVA FDX-A | +| FDX-B | ISO FDX-B | +| HIDProx | Generic HIDProx | +| HIDExt | Generic HIDExt | +| Pyramid | Farpointe Pyramid | +| Viking | Viking | +| Jablotron | Jablotron | +| Paradox | Paradox | +| PAC/Stanley | PAC/Stanley | +| Keri | Keri | +| Gallagher | Gallagher | diff --git a/documentation/file_formats/NfcFileFormats.md b/documentation/file_formats/NfcFileFormats.md index e04f3b68c..78c6420ee 100644 --- a/documentation/file_formats/NfcFileFormats.md +++ b/documentation/file_formats/NfcFileFormats.md @@ -19,9 +19,9 @@ This file format is used to store the UID, SAK and ATQA of a NFC-A device. It do Version differences: - 1. Initial version, deprecated - 2. LSB ATQA (e.g. 4400 instead of 0044) - 3. MSB ATQA (current version) +1. Initial version, deprecated +2. LSB ATQA (e.g. 4400 instead of 0044) +3. MSB ATQA (current version) UID can be either 4 or 7 bytes long. ATQA is 2 bytes long. SAK is 1 byte long. @@ -68,13 +68,13 @@ This file format is used to store the UID, SAK and ATQA of a Mifare Ultralight/N The "Signature" field contains the reply of the tag to the READ_SIG command. More on that can be found here: (page 31) -The "Mifare version" field is not related to the file format version, but to the Mifare Ultralight version. It contains the response of the tag to the GET_VERSION command. More on that can be found here: (page 21) +The "Mifare version" field is not related to the file format version but to the Mifare Ultralight version. It contains the response of the tag to the GET_VERSION command. More on that can be found here: (page 21) -Other fields are the direct representation of the card's internal state, more on them can be found in the same datasheet. +Other fields are the direct representation of the card's internal state. Learn more about them in the same datasheet. Version differences: - 1. Current version +1. Current version ## Mifare Classic @@ -126,7 +126,7 @@ This file format is used to store the NFC-A and Mifare Classic specific data of Version differences: - 1. Initial version, has Key A and Key B masks instead of marking unknown data with '??'. +1. Initial version, has Key A and Key B masks instead of marking unknown data with '??'. Example: @@ -137,8 +137,8 @@ Example: Key B map: 000000000000FFFF # Mifare Classic blocks ... - - 2. Current version + +2. Current version ## Mifare DESFire @@ -200,7 +200,7 @@ This file format is used to store the NFC-A and Mifare DESFire specific data of hf mfdes write --aid 123456 --fid 01 -d 1337 Version differences: - None, there are no versions yet. +None, there are no versions yet. ## Mifare Classic Dictionary @@ -252,4 +252,4 @@ This file stores a list of EMV currency codes, country codes, or AIDs and their Version differences: - 1. Initial version +1. Initial version diff --git a/documentation/file_formats/SubGhzFileFormats.md b/documentation/file_formats/SubGhzFileFormats.md index 1446ecd0d..26863f564 100644 --- a/documentation/file_formats/SubGhzFileFormats.md +++ b/documentation/file_formats/SubGhzFileFormats.md @@ -2,7 +2,7 @@ ## `.sub` File Format -Flipper uses `.sub` files to store SubGhz transmissions. They are text files in Flipper File Format. `.sub` files can contain either a SubGhz Key with a certain protocol or SubGhz RAW data. +Flipper uses `.sub` files to store SubGhz transmissions. These are text files in Flipper File Format. `.sub` files can contain either a SubGhz Key with a certain protocol or SubGhz RAW data. A `.sub` files consist of 3 parts: @@ -10,36 +10,36 @@ A `.sub` files consist of 3 parts: - **preset information**: preset type and, in case of a custom preset, transceiver configuration data - **protocol and its data**: contains protocol name and its specific data, such as key, bit length, etc., or RAW data -Flipper's SubGhz subsystem uses presets to configure radio transceiver. Presets are used to configure modulation, bandwidth, filters, etc. There are several presets available in stock firmware, and there is a way to create custom presets. See [SubGhz Presets](#adding-a-custom-preset) for more details. +Flipper's SubGhz subsystem uses presets to configure the radio transceiver. Presets are used to configure modulation, bandwidth, filters, etc. There are several presets available in stock firmware, and there is a way to create custom presets. See [SubGhz Presets](#adding-a-custom-preset) for more details. -## Header Format +## Header format Header is a mandatory part of `.sub` file. It contains file type, version, and frequency. -| Field | Type | Description | -| --- | --- | --- | -| `Filetype` | string | Filetype of subghz file format, must be `Flipper SubGhz Key File` | -| `Version` | uint | Version of subghz file format, current version is 1 | -| `Frequency` | uint | Frequency in Hertz | +| Field | Type | Description | +| ----------- | ------ | ----------------------------------------------------------------- | +| `Filetype` | string | Filetype of subghz file format, must be `Flipper SubGhz Key File` | +| `Version` | uint | Version of subghz file format, current version is 1 | +| `Frequency` | uint | Frequency in Hertz | -## Preset Information +## Preset information -Preset information is a mandatory part of `.sub` file. It contains preset type and, in case of custom preset, transceiver configuration data. +Preset information is a mandatory part for `.sub` files. It contains preset type and, in case of custom preset, transceiver configuration data. -When using one of the standard presets, only `Preset` field is required. When using custom preset, `Custom_preset_module` and `Custom_preset_data` fields are required. +When using one of the standard presets, only `Preset` field is required. When using a custom preset, `Custom_preset_module` and `Custom_preset_data` fields are required. -| Field | Description | -| --- | --- | -| `Preset` | Radio preset name (configures modulation, bandwidth, filters, etc.). When using a custom preset, must be `FuriHalSubGhzPresetCustom` | -| `Custom_preset_module` | Transceiver identifier, `CC1101` for Flipper Zero | -| `Custom_preset_data` | Transceiver configuration data | +| Field | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `Preset` | Radio preset name (configures modulation, bandwidth, filters, etc.). When using a custom preset, must be `FuriHalSubGhzPresetCustom` | +| `Custom_preset_module` | Transceiver identifier, `CC1101` for Flipper Zero | +| `Custom_preset_data` | Transceiver configuration data | Built-in presets: -- `FuriHalSubGhzPresetOok270Async` - On/Off Keying, 270kHz bandwidth, async(IO throw GP0) -- `FuriHalSubGhzPresetOok650Async` - On/Off Keying, 650kHz bandwidth, async(IO throw GP0) -- `FuriHalSubGhzPreset2FSKDev238Async` - 2 Frequency Shift Keying, deviation 2kHz, 270kHz bandwidth, async(IO throw GP0) -- `FuriHalSubGhzPreset2FSKDev476Async` - 2 Frequency Shift Keying, deviation 47kHz, 270kHz bandwidth, async(IO throw GP0) +- `FuriHalSubGhzPresetOok270Async` — On/Off Keying, 270kHz bandwidth, async(IO throw GP0) +- `FuriHalSubGhzPresetOok650Async` — On/Off Keying, 650kHz bandwidth, async(IO throw GP0) +- `FuriHalSubGhzPreset2FSKDev238Async` — 2 Frequency Shift Keying, deviation 2kHz, 270kHz bandwidth, async(IO throw GP0) +- `FuriHalSubGhzPreset2FSKDev476Async` — 2 Frequency Shift Keying, deviation 47kHz, 270kHz bandwidth, async(IO throw GP0) ### Transceiver Configuration Data @@ -50,7 +50,7 @@ Transceiver configuration data is a string of bytes, encoded in hex format, sepa - 00 00: marks register block end, - `ZZ ZZ ZZ ZZ ZZ ZZ ZZ ZZ`: 8 byte PA table (Power amplifier ramp table). -More details can be found in [CC1101 datasheet](https://www.ti.com/lit/ds/symlink/cc1101.pdf) and `furi_hal_subghz` code. +You can find more details in the [CC1101 datasheet](https://www.ti.com/lit/ds/symlink/cc1101.pdf) and `furi_hal_subghz` code. ## File Data @@ -59,9 +59,9 @@ More details can be found in [CC1101 datasheet](https://www.ti.com/lit/ds/symlin ### Key Files `.sub` files with key data files contain protocol name and its specific data, such as key value, bit length, etc. -Check out protocol registry for full list of supported protocol names. +Check out the protocol registry for the full list of supported protocol names. -Example of key data block in Princeton format: +Example of a key data block in Princeton format: ``` ... @@ -74,7 +74,7 @@ TE: 400 Protocol-specific fields in this example: | Field | Description | -| --- | --- | +| ----- | --------------------------------- | | `Bit` | Princeton payload length, in bits | | `Key` | Princeton payload data | | `TE` | Princeton quantization interval | @@ -83,25 +83,23 @@ This file may contain additional fields, more details on available fields can be ### RAW Files -RAW `.sub` files contain raw signal data that is not processed through protocol-specific decoding. These files are useful for testing purposes, or for sending data that is not supported by any known protocol. +RAW `.sub` files contain raw signal data that is not processed through protocol-specific decoding. These files are useful for testing or sending data not supported by any known protocol. For RAW files, 2 fields are required: - * `Protocol`, must be `RAW` - * `RAW_Data`, contains an array of timings, specified in micro seconds. Values must be non-zero, start with a positive number, and interleaved (change sign with each value). Up to 512 values per line. Can be specified multiple times to store multiple lines of data. +- `Protocol`, must be `RAW` +- `RAW_Data`, contains an array of timings, specified in microseconds Values must be non-zero, start with a positive number, and interleaved (change sign with each value). Up to 512 values per line. Can be specified multiple times to store multiple lines of data. Example of RAW data: Protocol: RAW RAW_Data: 29262 361 -68 2635 -66 24113 -66 11 ... +Long payload not fitting into internal memory buffer and consisting of short duration timings (< 10us) may not be read fast enough from the SD card. That might cause the signal transmission to stop before reaching the end of the payload. Ensure that your SD Card has good performance before transmitting long or complex RAW payloads. -Long payload not fitting into internal memory buffer and consisting of short duration timings (<10us) may not be read fast enough from SD Card. That might cause signal transmission to stop before reaching the end of the payload. Ensure that your SD Card has good performance before transmitting long or complex RAW payloads. +## File examples - -## File Examples - -### Key File, Standard Preset +### Key file, standard preset Filetype: Flipper SubGhz Key File Version: 1 @@ -112,8 +110,7 @@ Long payload not fitting into internal memory buffer and consisting of short dur Key: 00 00 00 00 00 95 D5 D4 TE: 400 - -### Key File, Custom Preset +### Key file, custom preset Filetype: Flipper SubGhz Key File Version: 1 @@ -126,7 +123,7 @@ Long payload not fitting into internal memory buffer and consisting of short dur Key: 00 00 00 00 00 95 D5 D4 TE: 400 -### RAW File, Standard Preset +### RAW file, standard preset Filetype: Flipper SubGhz RAW File Version: 1 @@ -137,7 +134,7 @@ Long payload not fitting into internal memory buffer and consisting of short dur RAW_Data: -424 205 -412 159 -412 381 -240 181 ... RAW_Data: -1448 361 -17056 131 -134 233 -1462 131 -166 953 -100 ... -### RAW File, Custom Preset +### RAW file, custom preset Filetype: Flipper SubGhz RAW File Version: 1 @@ -150,26 +147,26 @@ Long payload not fitting into internal memory buffer and consisting of short dur RAW_Data: -424 205 -412 159 -412 381 -240 181 ... RAW_Data: -1448 361 -17056 131 -134 233 -1462 131 -166 953 -100 ... -# SubGhz Configuration Files +# SubGhz configuration files SubGhz application provides support for adding extra radio presets and additional keys for decoding transmissions in certain protocols. -## SubGhz `keeloq_mfcodes_user` File +## SubGhz `keeloq_mfcodes_user` file This file contains additional manufacturer keys for Keeloq protocol. It is used to decode Keeloq transmissions. This file is loaded at subghz application start and is located at path `/ext/subghz/assets/keeloq_mfcodes_user`. -### File Format +### File format File contains a header and a list of manufacturer keys. File header format: -| Field | Type | Description | -| --- | | --- | -| `Filetype` | string | SubGhz Keystore file format, always `Flipper SubGhz Keystore File` | -| `Version` | uint | File format version, 0 | -| `Encryption` | uint | File encryption: for user-provided file, set to 0 (disabled) | +| Field | Type | Description | +| ------------ | ------ | ------------------------------------------------------------------ | +| `Filetype` | string | SubGhz Keystore file format, always `Flipper SubGhz Keystore File` | +| `Version` | uint | File format version, 0 | +| `Encryption` | uint | File encryption: for user-provided file, set to 0 (disabled) | Following the header, file contains a list of user-provided manufacture keys, one key per line. For each key, a name and encryption method must be specified, according to comment in file header. More information can be found in keeloq decoder source code. @@ -186,7 +183,7 @@ For each key, a name and encryption method must be specified, according to comme # - 2 - Normal_Learning # - 3 - Secure_Learning # - 4 - Magic_xor_type1 Learning - # + # # NAME - name (string without spaces) max 64 characters long Filetype: Flipper SubGhz Keystore File Version: 0 @@ -194,45 +191,44 @@ For each key, a name and encryption method must be specified, according to comme AABBCCDDEEFFAABB:1:Test1 AABBCCDDEEFFAABB:1:Test2 - -## SubGhz `setting_user` File +## SubGhz `setting_user` file This file contains additional radio presets and frequencies for SubGhz application. It is used to add new presets and frequencies for existing presets. This file is being loaded on subghz application start and is located at path `/ext/subghz/assets/setting_user`. -### File Format +### File format File contains a header, basic options, and optional lists of presets and frequencies. -Header must contain following fields: +Header must contain the following fields: - `Filetype`: SubGhz setting file format, must be `Flipper SubGhz Setting File`. - `Version`: file format version, current is `1`. -#### Basic Settings +#### Basic settings - `Add_standard_frequencies`: bool, flag indicating whether to load standard frequencies shipped with firmware. If set to `false`, only frequencies specified in this file will be used. - `Default_frequency`: uint, default frequency used in SubGhz application. -#### Adding More Frequencies +#### Adding more frequencies - `Frequency`: uint — additional frequency for the subghz application frequency list. Used in Read and Read RAW. You can specify multiple frequencies, one per line. -#### Adding More Hopper Frequencies +#### Adding more hopper frequencies - `Hopper_frequency`: uint — additional frequency for subghz application frequency hopping. Used in Frequency Analyzer. You can specify multiple frequencies, one per line. -Repeating same frequency will cause Flipper to listen on this frequency more often. +Repeating the same frequency will cause Flipper to listen to this frequency more often. #### Adding a Custom Preset You can have as many presets as you want. Presets are embedded into `.sub` files, so another Flipper can load them directly from that file. -Each preset is defined by following fields: +Each preset is defined by the following fields: -| Field | Description | -| --- | --- | -| `Custom_preset_name` | string, preset name that will be shown in SubGHz application | -| `Custom_preset_module` | string, transceiver identifier. Set to `CC1101` for Flipper Zero | -| `Custom_preset_data` | transceiver configuration data. See [Transceiver Configuration Data](#transceiver-configuration-data) for details. | +| Field | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------ | +| `Custom_preset_name` | string, preset name that will be shown in SubGHz application | +| `Custom_preset_module` | string, transceiver identifier. Set to `CC1101` for Flipper Zero | +| `Custom_preset_data` | transceiver configuration data. See [Transceiver Configuration Data](#transceiver-configuration-data) for details. | ### Example @@ -252,7 +248,7 @@ Frequency: 300000000 Frequency: 310000000 Frequency: 320000000 -# Frequencies used for hopping mode (keep this list small or flipper will miss signal) +# Frequencies used for hopping mode (keep this list small or Flipper will miss the signal) Hopper_frequency: 300000000 Hopper_frequency: 310000000 Hopper_frequency: 310000000 diff --git a/documentation/file_formats/iButtonFileFormat.md b/documentation/file_formats/iButtonFileFormat.md index a5d41b495..c04586195 100644 --- a/documentation/file_formats/iButtonFileFormat.md +++ b/documentation/file_formats/iButtonFileFormat.md @@ -1,6 +1,7 @@ # iButton key file format ## Example + ``` Filetype: Flipper iButton key Version: 1 @@ -9,11 +10,12 @@ Key type: Dallas # Data size for Cyfral is 2, for Metakom is 4, for Dallas is 8 Data: 12 34 56 78 9A BC DE F0 ``` + ## Description Filename extension: `.ibtn` -The file stores single iButton key of type defined by `Key type` parameter +The file stores a single iButton key of the type defined by the `Key type` parameter. ### Version history @@ -21,7 +23,7 @@ The file stores single iButton key of type defined by `Key type` parameter ### Format fields -|Name|Description| -|-|-| -|Key type|Currently supported: Cyfral, Dallas, Metakom| -|Data|Key data (HEX values)| \ No newline at end of file +| Name | Description | +| -------- | -------------------------------------------- | +| Key type | Currently supported: Cyfral, Dallas, Metakom | +| Data | Key data (HEX values) | From 2c450bd835dffa18dfa6662d658c3f75079e9b92 Mon Sep 17 00:00:00 2001 From: Maksim Derbasov Date: Sat, 7 Jan 2023 04:28:28 +0900 Subject: [PATCH 7/7] Show region information in sub-GHz app (#2249) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Show region info in sub-GHz app * SubGhz: reset widget on region info scene exit * Format sources Co-authored-by: あく --- .../main/nfc/scenes/nfc_scene_start.c | 6 +-- .../main/subghz/scenes/subghz_scene_config.h | 1 + .../subghz/scenes/subghz_scene_region_info.c | 39 +++++++++++++++++++ .../main/subghz/scenes/subghz_scene_start.c | 20 ++++++++-- .../picopass/scenes/picopass_scene_start.c | 2 +- 5 files changed, 60 insertions(+), 8 deletions(-) create mode 100644 applications/main/subghz/scenes/subghz_scene_region_info.c diff --git a/applications/main/nfc/scenes/nfc_scene_start.c b/applications/main/nfc/scenes/nfc_scene_start.c index f8b9f52c6..2a116fe09 100644 --- a/applications/main/nfc/scenes/nfc_scene_start.c +++ b/applications/main/nfc/scenes/nfc_scene_start.c @@ -7,7 +7,7 @@ enum SubmenuIndex { SubmenuIndexDetectReader, SubmenuIndexSaved, SubmenuIndexExtraAction, - SubmenuIndexAddManualy, + SubmenuIndexAddManually, SubmenuIndexDebug, }; @@ -28,7 +28,7 @@ void nfc_scene_start_on_enter(void* context) { submenu_add_item( submenu, "Extra Actions", SubmenuIndexExtraAction, nfc_scene_start_submenu_callback, nfc); submenu_add_item( - submenu, "Add Manually", SubmenuIndexAddManualy, nfc_scene_start_submenu_callback, nfc); + submenu, "Add Manually", SubmenuIndexAddManually, nfc_scene_start_submenu_callback, nfc); if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { submenu_add_item( @@ -68,7 +68,7 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) { } else if(event.event == SubmenuIndexExtraAction) { scene_manager_next_scene(nfc->scene_manager, NfcSceneExtraActions); consumed = true; - } else if(event.event == SubmenuIndexAddManualy) { + } else if(event.event == SubmenuIndexAddManually) { scene_manager_next_scene(nfc->scene_manager, NfcSceneSetType); consumed = true; } else if(event.event == SubmenuIndexDebug) { diff --git a/applications/main/subghz/scenes/subghz_scene_config.h b/applications/main/subghz/scenes/subghz_scene_config.h index fd1271c8c..86a307317 100644 --- a/applications/main/subghz/scenes/subghz_scene_config.h +++ b/applications/main/subghz/scenes/subghz_scene_config.h @@ -23,3 +23,4 @@ ADD_SCENE(subghz, more_raw, MoreRAW) ADD_SCENE(subghz, delete_raw, DeleteRAW) ADD_SCENE(subghz, need_saving, NeedSaving) ADD_SCENE(subghz, rpc, Rpc) +ADD_SCENE(subghz, region_info, RegionInfo) diff --git a/applications/main/subghz/scenes/subghz_scene_region_info.c b/applications/main/subghz/scenes/subghz_scene_region_info.c new file mode 100644 index 000000000..82486314d --- /dev/null +++ b/applications/main/subghz/scenes/subghz_scene_region_info.c @@ -0,0 +1,39 @@ +#include "../subghz_i.h" + +#include + +void subghz_scene_region_info_on_enter(void* context) { + SubGhz* subghz = context; + const FuriHalRegion* const region = furi_hal_region_get(); + FuriString* buffer; + buffer = furi_string_alloc(); + if(region) { + furi_string_cat_printf(buffer, "Region: %s, bands:\n", region->country_code); + for(uint16_t i = 0; i < region->bands_count; ++i) { + furi_string_cat_printf( + buffer, + " %lu-%lu kHz\n", + region->bands[i].start / 1000, + region->bands[i].end / 1000); + } + } else { + furi_string_cat_printf(buffer, "Region: N/A\n"); + } + + widget_add_string_multiline_element( + subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(buffer)); + + furi_string_free(buffer); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget); +} + +bool subghz_scene_region_info_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void subghz_scene_region_info_on_exit(void* context) { + SubGhz* subghz = context; + widget_reset(subghz->widget); +} diff --git a/applications/main/subghz/scenes/subghz_scene_start.c b/applications/main/subghz/scenes/subghz_scene_start.c index 0b1c3c159..a50f73a81 100644 --- a/applications/main/subghz/scenes/subghz_scene_start.c +++ b/applications/main/subghz/scenes/subghz_scene_start.c @@ -5,9 +5,10 @@ enum SubmenuIndex { SubmenuIndexRead = 10, SubmenuIndexSaved, SubmenuIndexTest, - SubmenuIndexAddManualy, + SubmenuIndexAddManually, SubmenuIndexFrequencyAnalyzer, SubmenuIndexReadRAW, + SubmenuIndexShowRegionInfo }; void subghz_scene_start_submenu_callback(void* context, uint32_t index) { @@ -33,7 +34,7 @@ void subghz_scene_start_on_enter(void* context) { submenu_add_item( subghz->submenu, "Add Manually", - SubmenuIndexAddManualy, + SubmenuIndexAddManually, subghz_scene_start_submenu_callback, subghz); submenu_add_item( @@ -42,6 +43,12 @@ void subghz_scene_start_on_enter(void* context) { SubmenuIndexFrequencyAnalyzer, subghz_scene_start_submenu_callback, subghz); + submenu_add_item( + subghz->submenu, + "Region Information", + SubmenuIndexShowRegionInfo, + subghz_scene_start_submenu_callback, + subghz); if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { submenu_add_item( subghz->submenu, "Test", SubmenuIndexTest, subghz_scene_start_submenu_callback, subghz); @@ -76,9 +83,9 @@ bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) { subghz->scene_manager, SubGhzSceneStart, SubmenuIndexSaved); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaved); return true; - } else if(event.event == SubmenuIndexAddManualy) { + } else if(event.event == SubmenuIndexAddManually) { scene_manager_set_scene_state( - subghz->scene_manager, SubGhzSceneStart, SubmenuIndexAddManualy); + subghz->scene_manager, SubGhzSceneStart, SubmenuIndexAddManually); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSetType); return true; } else if(event.event == SubmenuIndexFrequencyAnalyzer) { @@ -92,6 +99,11 @@ bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) { subghz->scene_manager, SubGhzSceneStart, SubmenuIndexTest); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTest); return true; + } else if(event.event == SubmenuIndexShowRegionInfo) { + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneStart, SubmenuIndexShowRegionInfo); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneRegionInfo); + return true; } } return false; diff --git a/applications/plugins/picopass/scenes/picopass_scene_start.c b/applications/plugins/picopass/scenes/picopass_scene_start.c index 76c18a22a..6d1aeedcd 100644 --- a/applications/plugins/picopass/scenes/picopass_scene_start.c +++ b/applications/plugins/picopass/scenes/picopass_scene_start.c @@ -3,7 +3,7 @@ enum SubmenuIndex { SubmenuIndexRead, SubmenuIndexRunScript, SubmenuIndexSaved, - SubmenuIndexAddManualy, + SubmenuIndexAddManually, SubmenuIndexDebug, };