From d008c95aa0363d21761f8278d6dc72b83b40fdb6 Mon Sep 17 00:00:00 2001 From: Zachary Weiss Date: Sun, 31 Mar 2024 13:51:53 -0400 Subject: [PATCH 001/129] Notes / comments --- .../nfc/plugins/supported_cards/charliecard.c | 38 +++++++++++++------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/charliecard.c b/applications/main/nfc/plugins/supported_cards/charliecard.c index 7a405fffb..1b34f369c 100644 --- a/applications/main/nfc/plugins/supported_cards/charliecard.c +++ b/applications/main/nfc/plugins/supported_cards/charliecard.c @@ -26,26 +26,40 @@ * – ASCII art &/or unified read function for the balance sectors, * to improve readability / interpretability by others? * — Improve string output formatting, esp. of transaction log + * — Mapping of buses to garages, and subsequently, route subsets via + * http://roster.transithistory.org/ data + * — Mapping of stations to lines + * — Add'l data fields for side of station fare gates are on? Some stations + * separate inbound & outbound sides, so direction could be inferred + * from gates used. * — Continually gather data on fare gate ID mappings, update as collected; * check locations this might be scrapable / inferrable from: * [X] MBTA GTFS spec (https://www.mbta.com/developers/gtfs) features & IDs * seem too-coarse-grained & uncorrelated - * [X] MBTA ArcGIS (https://mbta-massdot.opendata.arcgis.com/) & Tableau (https://public.tableau.com/app/profile/mbta.office.of.performance.management.and.innovation/vizzes) + * [X] MBTA ArcGIS (https://mbta-massdot.opendata.arcgis.com/) & Tableau + * (https://public.tableau.com/app/profile/mbta.office.of.performance.management.and.innovation/vizzes) * files don't seem to have anything of that resolution (only down to ridership by station) * [X] (skim of) MBTA public GitHub (https://github.com/mbta) repos make no reference to fare-gate-level data * [X] (skim of) MBTA public engineering docs (https://www.mbta.com/engineering) unfruitful; - * Closest mention spotted is 2014 "Ridership and Service Statistics" (https://cdn.mbta.com/sites/default/files/fmcb-meeting-docs/reports-policies/2014-07-mbta-bluebook-ed14.pdf) + * Closest mention spotted is 2014 "Ridership and Service Statistics" + * (https://cdn.mbta.com/sites/default/files/fmcb-meeting-docs/reports-policies/2014-07-mbta-bluebook-ed14.pdf) * where on pg.40, "Equipment at Stations" is enumerated, and fare gates counts are given, - * listed as "AFC Gates" (presumably standing for "Automated Fare Control") + * listed as "AFC Gates" (presumably standing for "Automated Fare Collection") * [X] Josiah Zachery criminal trial public evidence — convicted partially on * data on his CharlieCard, appeals partially on basis of legality of this search. * Prev. court case (gag order mentioned in preamble) leaked some data in the files * entered into evidence. Seemingly did not happen here; fare gate IDs unmentioned, * only ever the nature of stored/saved data and methods of retrieval. - * Appelate case dockets 2019-P-0401, SJC-12952, SJ-2017-0390 (https://www.ma-appellatecourts.org/party) - * Trial court case 04/02/2015 #1584CR10265 @Suffolk County Criminal Superior Court (https://www.masscourts.org/eservices/home.page.16) - * [ ] FOIA / public records request? (https://massachusettsdot.mycusthelp.com/WEBAPP/_rs/(S(tbcygdlm0oojy35p1wv0y2y5))/supporthome.aspx) - * [ ] MBTA data blog? (https://www.massdottracker.com/datablog/) + * Appelate case dockets 2019-P-0401, SJC-12952, SJ-2017-0390 + * (https://www.ma-appellatecourts.org/party) + * Trial court indictment 04/02/2015, Case# 1584CR10265 @Suffolk County Criminal Superior Court + * (https://www.masscourts.org/eservices/home.page.16) + * [ ] FOIA / public records request? + * (https://massachusettsdot.mycusthelp.com/WEBAPP/_rs/(S(tbcygdlm0oojy35p1wv0y2y5))/supporthome.aspx) + * [X] MBTA data blog? (https://www.massdottracker.com/datablog/) + * [ ] MassDOT developers Google group? (https://groups.google.com/g/massdotdevelopers) + * [X] preexisting posts + * [ ] ask directly? * [ ] Other? * * This program is free software: you can redistribute it and/or modify it @@ -164,10 +178,10 @@ static const IdMapping charliecard_types[] = { // Passes {.id = 135, .name = "30 Day Local Bus Pass"}, - {.id = 136, .name = "30 Day Inner Express Bus Pass"}, // - {.id = 137, .name = "30 Day Outer Express Bus Pass"}, // - {.id = 138, .name = "30 Day LinkPass"}, // - {.id = 139, .name = "30 Day Senior LinkPass"}, // + {.id = 136, .name = "30 Day Inner Express Bus Pass"}, + {.id = 137, .name = "30 Day Outer Express Bus Pass"}, + {.id = 138, .name = "30 Day LinkPass"}, + {.id = 139, .name = "30 Day Senior LinkPass"}, {.id = 148, .name = "30 Day TAP LinkPass"}, {.id = 150, .name = "Monthly Student LinkPass"}, {.id = 424, .name = "Monthly TAP LinkPass"}, // 0b0110101000 @@ -713,7 +727,7 @@ static DateTime end_validity_parse(const MfClassicData* data, enum CharlieActiveSector active_sec) { // End validity field is a bit odd; shares byte 1 with another variable (the card type field), // occupying only the last 3 bits (and subsequent two bytes), hence bitmask - // TODO; what are the add'l 3 bits between type & end validity fields? + // TODO: what are the add'l 3 bits between type & end validity fields? uint32_t ts_charlie_ev = pos_to_num(data, (active_sec == CHARLIE_ACTIVE_SECTOR_2) ? 2 : 3, 1, 1, 3); ts_charlie_ev = ts_charlie_ev & 0x1FFFFF; From 23c8c23051ff34812cfbad53499d104d25504a0a Mon Sep 17 00:00:00 2001 From: Zachary Weiss Date: Sun, 31 Mar 2024 17:31:00 -0400 Subject: [PATCH 002/129] Minor gate ID mapping corrections --- .../main/nfc/plugins/supported_cards/charliecard.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/charliecard.c b/applications/main/nfc/plugins/supported_cards/charliecard.c index 1b34f369c..f10c6dcdb 100644 --- a/applications/main/nfc/plugins/supported_cards/charliecard.c +++ b/applications/main/nfc/plugins/supported_cards/charliecard.c @@ -161,7 +161,7 @@ typedef struct { const char* name; } IdMapping; -// this should be a complete accounting of types, +// this should be a complete accounting of types, (1 and 7 day pass types maybe missing?) static const IdMapping charliecard_types[] = { // Regular card types {.id = 367, .name = "Adult"}, @@ -402,9 +402,7 @@ static const IdMapping charliecard_fare_gate_ids[] = { {.id = 6647, .name = "Malden Center"}, {.id = 6648, .name = "Malden Center"}, // Chinatown - {.id = 6704, - .name = - "Malden Center"}, // Entry error? Placed after "Chinatown" divider, but with name Malden Center + {.id = 6704, .name = "Chinatown"}, {.id = 6705, .name = "Chinatown"}, {.id = 2099, .name = "Chinatown"}, {.id = 7003, .name = "Chinatown"}, @@ -485,7 +483,7 @@ static const IdMapping charliecard_fare_gate_ids[] = { {.id = 7016, .name = "Forest Hills"}, {.id = 6950, .name = "Forest Hills"}, {.id = 6951, .name = "Forest Hills"}, - {.id = 604, .name = "Forest Hills"}, // Entry error? + {.id = 604, .name = "Forest Hills"}, {.id = 7096, .name = "Forest Hills"}, // South Station {.id = 7039, .name = "South Station"}, From af315bc926dc2372a5a9c382b8c3b1d5464530b0 Mon Sep 17 00:00:00 2001 From: SG Date: Thu, 4 Apr 2024 20:09:36 +0300 Subject: [PATCH 003/129] add tlsf as submodule --- .gitmodules | 3 +++ lib/tlsf | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/tlsf diff --git a/.gitmodules b/.gitmodules index c4c68a6a7..a8d12803c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -41,3 +41,6 @@ [submodule "documentation/doxygen/doxygen-awesome-css"] path = documentation/doxygen/doxygen-awesome-css url = https://github.com/jothepro/doxygen-awesome-css.git +[submodule "lib/tlsf"] + path = lib/tlsf + url = https://github.com/espressif/tlsf diff --git a/lib/tlsf b/lib/tlsf new file mode 160000 index 000000000..8fc595fe2 --- /dev/null +++ b/lib/tlsf @@ -0,0 +1 @@ +Subproject commit 8fc595fe223cd0b3b5d7b29eb86825e4bd38e6e8 From 39899a9b0de3f5ecefea648505a3f8ef784d3bec Mon Sep 17 00:00:00 2001 From: SG Date: Thu, 4 Apr 2024 22:04:17 +0300 Subject: [PATCH 004/129] libs: tlsf --- lib/SConscript | 1 + lib/tlsf.scons | 21 +++++++++++++++++++++ targets/f7/target.json | 3 ++- 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 lib/tlsf.scons diff --git a/lib/SConscript b/lib/SConscript index 812573932..29c48de6d 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -13,6 +13,7 @@ env.Append( libs = env.BuildModules( [ + "tlsf", "mlib", "stm32wb", "freertos", diff --git a/lib/tlsf.scons b/lib/tlsf.scons new file mode 100644 index 000000000..0a8419dbd --- /dev/null +++ b/lib/tlsf.scons @@ -0,0 +1,21 @@ +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/tlsf", + ], +) + + +libenv = env.Clone(FW_LIB_NAME="tlsf") +libenv.ApplyLibFlags() + +libenv.Append( + CPPDEFINES=[], +) + +sources = [File("tlsf/tlsf.c")] + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/targets/f7/target.json b/targets/f7/target.json index 25872198b..caa3f58ee 100644 --- a/targets/f7/target.json +++ b/targets/f7/target.json @@ -22,6 +22,7 @@ "print", "flipper7", "furi", + "tlsf", "freertos", "stm32wb", "hwdrivers", @@ -55,4 +56,4 @@ "bit_lib", "datetime" ] -} +} \ No newline at end of file From 4d4010345c7e6ed7580fc3a07154f6788a946926 Mon Sep 17 00:00:00 2001 From: SG Date: Thu, 4 Apr 2024 22:04:43 +0300 Subject: [PATCH 005/129] Furi: tlsf as allocator --- furi/core/memmgr_heap.c | 674 +++++----------------------------------- 1 file changed, 73 insertions(+), 601 deletions(-) diff --git a/furi/core/memmgr_heap.c b/furi/core/memmgr_heap.c index 24bd327fd..65e1cae1d 100644 --- a/furi/core/memmgr_heap.c +++ b/furi/core/memmgr_heap.c @@ -1,664 +1,136 @@ -/* - * FreeRTOS Kernel V10.2.1 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * http://www.FreeRTOS.org - * http://aws.amazon.com/freertos - * - * 1 tab == 4 spaces! - */ - -/* - * A sample implementation of pvPortMalloc() and vPortFree() that combines - * (coalescences) adjacent memory blocks as they are freed, and in so doing - * limits memory fragmentation. - * - * See heap_1.c, heap_2.c and heap_3.c for alternative implementations, and the - * memory management pages of http://www.FreeRTOS.org for more information. - */ - -#include "memmgr_heap.h" -#include "check.h" -#include -#include -#include -#include -#include - -/* Defining MPU_WRAPPERS_INCLUDED_FROM_API_FILE prevents task.h from redefining -all the API functions to use the MPU wrappers. That should only be done when -task.h is included from an application file. */ -#define MPU_WRAPPERS_INCLUDED_FROM_API_FILE - +#include +#include +#include #include #include -#undef MPU_WRAPPERS_INCLUDED_FROM_API_FILE - -#ifdef HEAP_PRINT_DEBUG -#error This feature is broken, logging transport must be replaced with RTT -#endif - -#if(configSUPPORT_DYNAMIC_ALLOCATION == 0) -#error This file must not be used if configSUPPORT_DYNAMIC_ALLOCATION is 0 -#endif - -/* Block sizes must not get too small. */ -#define heapMINIMUM_BLOCK_SIZE ((size_t)(xHeapStructSize << 1)) - -/* Assumes 8bit bytes! */ -#define heapBITS_PER_BYTE ((size_t)8) - -/* Heap start end symbols provided by linker */ extern const void __heap_start__; extern const void __heap_end__; -uint8_t* ucHeap = (uint8_t*)&__heap_start__; -/* Define the linked list structure. This is used to link free blocks in order -of their memory address. */ -typedef struct A_BLOCK_LINK { - struct A_BLOCK_LINK* pxNextFreeBlock; /*<< The next free block in the list. */ - size_t xBlockSize; /*<< The size of the free block. */ -} BlockLink_t; +static tlsf_t tlsf = NULL; +static size_t heap_used = 0; +static size_t heap_max_used = 0; -/*-----------------------------------------------------------*/ +static inline void memmgr_lock(void) { + vTaskSuspendAll(); +} -/* - * Inserts a block of memory that is being freed into the correct position in - * the list of free memory blocks. The block being freed will be merged with - * the block in front it and/or the block behind it if the memory blocks are - * adjacent to each other. - */ -static void prvInsertBlockIntoFreeList(BlockLink_t* pxBlockToInsert); +static inline void memmgr_unlock(void) { + xTaskResumeAll(); +} -/* - * Called automatically to setup the required heap structures the first time - * pvPortMalloc() is called. - */ -static void prvHeapInit(void); - -/*-----------------------------------------------------------*/ - -/* The size of the structure placed at the beginning of each allocated memory -block must by correctly byte aligned. */ -static const size_t xHeapStructSize = (sizeof(BlockLink_t) + ((size_t)(portBYTE_ALIGNMENT - 1))) & - ~((size_t)portBYTE_ALIGNMENT_MASK); - -/* Create a couple of list links to mark the start and end of the list. */ -static BlockLink_t xStart, *pxEnd = NULL; - -/* Keeps track of the number of free bytes remaining, but says nothing about -fragmentation. */ -static size_t xFreeBytesRemaining = 0U; -static size_t xMinimumEverFreeBytesRemaining = 0U; - -/* Gets set to the top bit of an size_t type. When this bit in the xBlockSize -member of an BlockLink_t structure is set then the block belongs to the -application. When the bit is free the block is still part of the free heap -space. */ -static size_t xBlockAllocatedBit = 0; - -/* Furi heap extension */ -#include - -/* Allocation tracking types */ -DICT_DEF2(MemmgrHeapAllocDict, uint32_t, uint32_t) //-V1048 - -DICT_DEF2( //-V1048 - MemmgrHeapThreadDict, - uint32_t, - M_DEFAULT_OPLIST, - MemmgrHeapAllocDict_t, - DICT_OPLIST(MemmgrHeapAllocDict)) - -/* Thread allocation tracing storage */ -static MemmgrHeapThreadDict_t memmgr_heap_thread_dict = {0}; -static volatile uint32_t memmgr_heap_thread_trace_depth = 0; - -/* Initialize tracing storage on start */ -void memmgr_heap_init(void) { - MemmgrHeapThreadDict_init(memmgr_heap_thread_dict); +static inline size_t memmgr_get_heap_size(void) { + return (size_t)&__heap_end__ - (size_t)&__heap_start__; } void memmgr_heap_enable_thread_trace(FuriThreadId thread_id) { - vTaskSuspendAll(); - { - memmgr_heap_thread_trace_depth++; - furi_check(MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id) == NULL); - MemmgrHeapAllocDict_t alloc_dict; - MemmgrHeapAllocDict_init(alloc_dict); - MemmgrHeapThreadDict_set_at(memmgr_heap_thread_dict, (uint32_t)thread_id, alloc_dict); - MemmgrHeapAllocDict_clear(alloc_dict); - memmgr_heap_thread_trace_depth--; - } - (void)xTaskResumeAll(); + UNUSED(thread_id); } void memmgr_heap_disable_thread_trace(FuriThreadId thread_id) { - vTaskSuspendAll(); - { - memmgr_heap_thread_trace_depth++; - furi_check(MemmgrHeapThreadDict_erase(memmgr_heap_thread_dict, (uint32_t)thread_id)); - memmgr_heap_thread_trace_depth--; - } - (void)xTaskResumeAll(); + UNUSED(thread_id); } size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id) { - size_t leftovers = MEMMGR_HEAP_UNKNOWN; - vTaskSuspendAll(); - { - memmgr_heap_thread_trace_depth++; - MemmgrHeapAllocDict_t* alloc_dict = - MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); - if(alloc_dict) { - leftovers = 0; - MemmgrHeapAllocDict_it_t alloc_dict_it; - for(MemmgrHeapAllocDict_it(alloc_dict_it, *alloc_dict); - !MemmgrHeapAllocDict_end_p(alloc_dict_it); - MemmgrHeapAllocDict_next(alloc_dict_it)) { - MemmgrHeapAllocDict_itref_t* data = MemmgrHeapAllocDict_ref(alloc_dict_it); - if(data->key != 0) { - uint8_t* puc = (uint8_t*)data->key; - puc -= xHeapStructSize; - BlockLink_t* pxLink = (void*)puc; - - if((pxLink->xBlockSize & xBlockAllocatedBit) != 0 && - pxLink->pxNextFreeBlock == NULL) { - leftovers += data->value; - } - } - } - } - memmgr_heap_thread_trace_depth--; - } - (void)xTaskResumeAll(); - return leftovers; + UNUSED(thread_id); + return 0; } -#undef traceMALLOC -static inline void traceMALLOC(void* pointer, size_t size) { - FuriThreadId thread_id = furi_thread_get_current_id(); - if(thread_id && memmgr_heap_thread_trace_depth == 0) { - memmgr_heap_thread_trace_depth++; - MemmgrHeapAllocDict_t* alloc_dict = - MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); - if(alloc_dict) { - MemmgrHeapAllocDict_set_at(*alloc_dict, (uint32_t)pointer, (uint32_t)size); - } - memmgr_heap_thread_trace_depth--; - } -} +static bool tlsf_walker_max_free(void* ptr, size_t size, int used, void* user) { + UNUSED(ptr); -#undef traceFREE -static inline void traceFREE(void* pointer, size_t size) { - UNUSED(size); - FuriThreadId thread_id = furi_thread_get_current_id(); - if(thread_id && memmgr_heap_thread_trace_depth == 0) { - memmgr_heap_thread_trace_depth++; - MemmgrHeapAllocDict_t* alloc_dict = - MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); - if(alloc_dict) { - // In some cases thread may want to release memory that was not allocated by it - const bool res = MemmgrHeapAllocDict_erase(*alloc_dict, (uint32_t)pointer); - UNUSED(res); - } - memmgr_heap_thread_trace_depth--; + size_t* max_free_block_size = (size_t*)user; + if(!used && size > *max_free_block_size) { + *max_free_block_size = size; } + + return true; } size_t memmgr_heap_get_max_free_block(void) { - size_t max_free_size = 0; - BlockLink_t* pxBlock; - vTaskSuspendAll(); + size_t max_free_block_size = 0; - pxBlock = xStart.pxNextFreeBlock; - while(pxBlock->pxNextFreeBlock != NULL) { - if(pxBlock->xBlockSize > max_free_size) { - max_free_size = pxBlock->xBlockSize; - } - pxBlock = pxBlock->pxNextFreeBlock; - } + memmgr_lock(); - xTaskResumeAll(); - return max_free_size; + pool_t pool = tlsf_get_pool(tlsf); + tlsf_walk_pool(pool, tlsf_walker_max_free, &max_free_block_size); + + memmgr_unlock(); + + return max_free_block_size; } void memmgr_heap_printf_free_blocks(void) { - BlockLink_t* pxBlock; - //TODO enable when we can do printf with a locked scheduler - //vTaskSuspendAll(); - - pxBlock = xStart.pxNextFreeBlock; - while(pxBlock->pxNextFreeBlock != NULL) { - printf("A %p S %lu\r\n", (void*)pxBlock, (uint32_t)pxBlock->xBlockSize); - pxBlock = pxBlock->pxNextFreeBlock; - } - - //xTaskResumeAll(); } -#ifdef HEAP_PRINT_DEBUG -char* ultoa(unsigned long num, char* str, int radix) { - char temp[33]; // at radix 2 the string is at most 32 + 1 null long. - int temp_loc = 0; - int digit; - int str_loc = 0; - - //construct a backward string of the number. - do { - digit = (unsigned long)num % ((unsigned long)radix); - if(digit < 10) - temp[temp_loc++] = digit + '0'; - else - temp[temp_loc++] = digit - 10 + 'A'; - num = ((unsigned long)num) / ((unsigned long)radix); - } while((unsigned long)num > 0); - - temp_loc--; - - //now reverse the string. - while(temp_loc >= 0) { // while there are still chars - str[str_loc++] = temp[temp_loc--]; - } - str[str_loc] = 0; // add null termination. - - return str; -} - -static void print_heap_init(void) { - char tmp_str[33]; - size_t heap_start = (size_t)&__heap_start__; - size_t heap_end = (size_t)&__heap_end__; - - // {PHStart|heap_start|heap_end} - FURI_CRITICAL_ENTER(); - furi_log_puts("{PHStart|"); - ultoa(heap_start, tmp_str, 16); - furi_log_puts(tmp_str); - furi_log_puts("|"); - ultoa(heap_end, tmp_str, 16); - furi_log_puts(tmp_str); - furi_log_puts("}\r\n"); - FURI_CRITICAL_EXIT(); -} - -static void print_heap_malloc(void* ptr, size_t size) { - char tmp_str[33]; - const char* name = furi_thread_get_name(furi_thread_get_current_id()); - if(!name) { - name = ""; - } - - // {thread name|m|address|size} - FURI_CRITICAL_ENTER(); - furi_log_puts("{"); - furi_log_puts(name); - furi_log_puts("|m|0x"); - ultoa((unsigned long)ptr, tmp_str, 16); - furi_log_puts(tmp_str); - furi_log_puts("|"); - utoa(size, tmp_str, 10); - furi_log_puts(tmp_str); - furi_log_puts("}\r\n"); - FURI_CRITICAL_EXIT(); -} - -static void print_heap_free(void* ptr) { - char tmp_str[33]; - const char* name = furi_thread_get_name(furi_thread_get_current_id()); - if(!name) { - name = ""; - } - - // {thread name|f|address} - FURI_CRITICAL_ENTER(); - furi_log_puts("{"); - furi_log_puts(name); - furi_log_puts("|f|0x"); - ultoa((unsigned long)ptr, tmp_str, 16); - furi_log_puts(tmp_str); - furi_log_puts("}\r\n"); - FURI_CRITICAL_EXIT(); -} -#endif -/*-----------------------------------------------------------*/ - -void* pvPortMalloc(size_t xWantedSize) { - BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink; - void* pvReturn = NULL; - size_t to_wipe = xWantedSize; - +void* pvPortMalloc(size_t xSize) { + // memory management in ISR is not allowed if(FURI_IS_IRQ_MODE()) { furi_crash("memmgt in ISR"); } -#ifdef HEAP_PRINT_DEBUG - BlockLink_t* print_heap_block = NULL; -#endif + memmgr_lock(); - /* If this is the first call to malloc then the heap will require - initialisation to setup the list of free blocks. */ - if(pxEnd == NULL) { -#ifdef HEAP_PRINT_DEBUG - print_heap_init(); -#endif - - vTaskSuspendAll(); - { - prvHeapInit(); - memmgr_heap_init(); - } - (void)xTaskResumeAll(); - } else { - mtCOVERAGE_TEST_MARKER(); + // initialize tlsf, if not initialized + if(tlsf == NULL) { + size_t pool_size = (size_t)&__heap_end__ - (size_t)&__heap_start__; + tlsf = tlsf_create_with_pool((void*)&__heap_start__, pool_size, pool_size); } - vTaskSuspendAll(); - { - /* Check the requested block size is not so large that the top bit is - set. The top bit of the block size member of the BlockLink_t structure - is used to determine who owns the block - the application or the - kernel, so it must be free. */ - if((xWantedSize & xBlockAllocatedBit) == 0) { - /* The wanted size is increased so it can contain a BlockLink_t - structure in addition to the requested amount of bytes. */ - if(xWantedSize > 0) { - xWantedSize += xHeapStructSize; - - /* Ensure that blocks are always aligned to the required number - of bytes. */ - if((xWantedSize & portBYTE_ALIGNMENT_MASK) != 0x00) { - /* Byte alignment required. */ - xWantedSize += (portBYTE_ALIGNMENT - (xWantedSize & portBYTE_ALIGNMENT_MASK)); - configASSERT((xWantedSize & portBYTE_ALIGNMENT_MASK) == 0); - } else { - mtCOVERAGE_TEST_MARKER(); - } - } else { - mtCOVERAGE_TEST_MARKER(); - } - - if((xWantedSize > 0) && (xWantedSize <= xFreeBytesRemaining)) { - /* Traverse the list from the start (lowest address) block until - one of adequate size is found. */ - pxPreviousBlock = &xStart; - pxBlock = xStart.pxNextFreeBlock; - while((pxBlock->xBlockSize < xWantedSize) && (pxBlock->pxNextFreeBlock != NULL)) { - pxPreviousBlock = pxBlock; - pxBlock = pxBlock->pxNextFreeBlock; - } - - /* If the end marker was reached then a block of adequate size - was not found. */ - if(pxBlock != pxEnd) { - /* Return the memory space pointed to - jumping over the - BlockLink_t structure at its start. */ - pvReturn = - (void*)(((uint8_t*)pxPreviousBlock->pxNextFreeBlock) + xHeapStructSize); - - /* This block is being returned for use so must be taken out - of the list of free blocks. */ - pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock; - - /* If the block is larger than required it can be split into - two. */ - if((pxBlock->xBlockSize - xWantedSize) > heapMINIMUM_BLOCK_SIZE) { - /* This block is to be split into two. Create a new - block following the number of bytes requested. The void - cast is used to prevent byte alignment warnings from the - compiler. */ - pxNewBlockLink = (void*)(((uint8_t*)pxBlock) + xWantedSize); - configASSERT((((size_t)pxNewBlockLink) & portBYTE_ALIGNMENT_MASK) == 0); - - /* Calculate the sizes of two blocks split from the - single block. */ - pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize; - pxBlock->xBlockSize = xWantedSize; - - /* Insert the new block into the list of free blocks. */ - prvInsertBlockIntoFreeList(pxNewBlockLink); - } else { - mtCOVERAGE_TEST_MARKER(); - } - - xFreeBytesRemaining -= pxBlock->xBlockSize; - - if(xFreeBytesRemaining < xMinimumEverFreeBytesRemaining) { - xMinimumEverFreeBytesRemaining = xFreeBytesRemaining; - } else { - mtCOVERAGE_TEST_MARKER(); - } - - /* The block is being returned - it is allocated and owned - by the application and has no "next" block. */ - pxBlock->xBlockSize |= xBlockAllocatedBit; - pxBlock->pxNextFreeBlock = NULL; - -#ifdef HEAP_PRINT_DEBUG - print_heap_block = pxBlock; -#endif - } else { - mtCOVERAGE_TEST_MARKER(); - } - } else { - mtCOVERAGE_TEST_MARKER(); - } - } else { - mtCOVERAGE_TEST_MARKER(); - } - - traceMALLOC(pvReturn, xWantedSize); + // allocate block + void* data = tlsf_malloc(tlsf, xSize); + if(data == NULL) { + furi_crash("out of memory"); } - (void)xTaskResumeAll(); -#ifdef HEAP_PRINT_DEBUG - print_heap_malloc(print_heap_block, print_heap_block->xBlockSize & ~xBlockAllocatedBit); -#endif - -#if(configUSE_MALLOC_FAILED_HOOK == 1) - { - if(pvReturn == NULL) { - extern void vApplicationMallocFailedHook(void); - vApplicationMallocFailedHook(); - } else { - mtCOVERAGE_TEST_MARKER(); - } + // update heap usage + heap_used += tlsf_block_size(data); + heap_used += tlsf_alloc_overhead(); + if(heap_used > heap_max_used) { + heap_max_used = heap_used; } -#endif - configASSERT((((size_t)pvReturn) & (size_t)portBYTE_ALIGNMENT_MASK) == 0); + // clear block content + memset(data, 0, xSize); - furi_check(pvReturn); - pvReturn = memset(pvReturn, 0, to_wipe); - return pvReturn; + memmgr_unlock(); + + return data; } -/*-----------------------------------------------------------*/ void vPortFree(void* pv) { - uint8_t* puc = (uint8_t*)pv; - BlockLink_t* pxLink; - + // memory management in ISR is not allowed if(FURI_IS_IRQ_MODE()) { furi_crash("memmgt in ISR"); } + // ignore NULL pointer if(pv != NULL) { - /* The memory being freed will have an BlockLink_t structure immediately - before it. */ - puc -= xHeapStructSize; + memmgr_lock(); - /* This casting is to keep the compiler from issuing warnings. */ - pxLink = (void*)puc; + // clear block content + size_t block_size = tlsf_block_size(pv); + memset(pv, 0, block_size); - /* Check the block is actually allocated. */ - configASSERT((pxLink->xBlockSize & xBlockAllocatedBit) != 0); - configASSERT(pxLink->pxNextFreeBlock == NULL); + // update heap usage + heap_used -= block_size; + heap_used -= tlsf_alloc_overhead(); - if((pxLink->xBlockSize & xBlockAllocatedBit) != 0) { - if(pxLink->pxNextFreeBlock == NULL) { - /* The block is being returned to the heap - it is no longer - allocated. */ - pxLink->xBlockSize &= ~xBlockAllocatedBit; + // free + tlsf_free(tlsf, pv); -#ifdef HEAP_PRINT_DEBUG - print_heap_free(pxLink); -#endif - - vTaskSuspendAll(); - { - furi_assert((size_t)pv >= SRAM_BASE); - furi_assert((size_t)pv < SRAM_BASE + 1024 * 256); - furi_assert(pxLink->xBlockSize >= xHeapStructSize); - furi_assert((pxLink->xBlockSize - xHeapStructSize) < 1024 * 256); - - /* Add this block to the list of free blocks. */ - xFreeBytesRemaining += pxLink->xBlockSize; - traceFREE(pv, pxLink->xBlockSize); - memset(pv, 0, pxLink->xBlockSize - xHeapStructSize); - prvInsertBlockIntoFreeList(((BlockLink_t*)pxLink)); - } - (void)xTaskResumeAll(); - } else { - mtCOVERAGE_TEST_MARKER(); - } - } else { - mtCOVERAGE_TEST_MARKER(); - } - } else { -#ifdef HEAP_PRINT_DEBUG - print_heap_free(pv); -#endif + memmgr_unlock(); } } -/*-----------------------------------------------------------*/ - -size_t xPortGetTotalHeapSize(void) { - return (size_t)&__heap_end__ - (size_t)&__heap_start__; -} -/*-----------------------------------------------------------*/ size_t xPortGetFreeHeapSize(void) { - return xFreeBytesRemaining; + return memmgr_get_heap_size() - heap_used; +} + +size_t xPortGetTotalHeapSize(void) { + return memmgr_get_heap_size(); } -/*-----------------------------------------------------------*/ size_t xPortGetMinimumEverFreeHeapSize(void) { - return xMinimumEverFreeBytesRemaining; -} -/*-----------------------------------------------------------*/ - -void vPortInitialiseBlocks(void) { - /* This just exists to keep the linker quiet. */ -} -/*-----------------------------------------------------------*/ - -static void prvHeapInit(void) { - BlockLink_t* pxFirstFreeBlock; - uint8_t* pucAlignedHeap; - size_t uxAddress; - size_t xTotalHeapSize = (size_t)&__heap_end__ - (size_t)&__heap_start__; - - /* Ensure the heap starts on a correctly aligned boundary. */ - uxAddress = (size_t)ucHeap; - - if((uxAddress & portBYTE_ALIGNMENT_MASK) != 0) { - uxAddress += (portBYTE_ALIGNMENT - 1); - uxAddress &= ~((size_t)portBYTE_ALIGNMENT_MASK); - xTotalHeapSize -= uxAddress - (size_t)ucHeap; - } - - pucAlignedHeap = (uint8_t*)uxAddress; - - /* xStart is used to hold a pointer to the first item in the list of free - blocks. The void cast is used to prevent compiler warnings. */ - xStart.pxNextFreeBlock = (void*)pucAlignedHeap; - xStart.xBlockSize = (size_t)0; - - /* pxEnd is used to mark the end of the list of free blocks and is inserted - at the end of the heap space. */ - uxAddress = ((size_t)pucAlignedHeap) + xTotalHeapSize; - uxAddress -= xHeapStructSize; - uxAddress &= ~((size_t)portBYTE_ALIGNMENT_MASK); - pxEnd = (void*)uxAddress; - pxEnd->xBlockSize = 0; - pxEnd->pxNextFreeBlock = NULL; - - /* To start with there is a single free block that is sized to take up the - entire heap space, minus the space taken by pxEnd. */ - pxFirstFreeBlock = (void*)pucAlignedHeap; - pxFirstFreeBlock->xBlockSize = uxAddress - (size_t)pxFirstFreeBlock; - pxFirstFreeBlock->pxNextFreeBlock = pxEnd; - - /* Only one block exists - and it covers the entire usable heap space. */ - xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize; - xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize; - - /* Work out the position of the top bit in a size_t variable. */ - xBlockAllocatedBit = ((size_t)1) << ((sizeof(size_t) * heapBITS_PER_BYTE) - 1); -} -/*-----------------------------------------------------------*/ - -static void prvInsertBlockIntoFreeList(BlockLink_t* pxBlockToInsert) { - BlockLink_t* pxIterator; - uint8_t* puc; - - /* Iterate through the list until a block is found that has a higher address - than the block being inserted. */ - for(pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; - pxIterator = pxIterator->pxNextFreeBlock) { - /* Nothing to do here, just iterate to the right position. */ - } - - /* Do the block being inserted, and the block it is being inserted after - make a contiguous block of memory? */ - puc = (uint8_t*)pxIterator; - if((puc + pxIterator->xBlockSize) == (uint8_t*)pxBlockToInsert) { - pxIterator->xBlockSize += pxBlockToInsert->xBlockSize; - pxBlockToInsert = pxIterator; - } else { - mtCOVERAGE_TEST_MARKER(); - } - - /* Do the block being inserted, and the block it is being inserted before - make a contiguous block of memory? */ - puc = (uint8_t*)pxBlockToInsert; - if((puc + pxBlockToInsert->xBlockSize) == (uint8_t*)pxIterator->pxNextFreeBlock) { - if(pxIterator->pxNextFreeBlock != pxEnd) { - /* Form one big block from the two blocks. */ - pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize; - pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock; - } else { - pxBlockToInsert->pxNextFreeBlock = pxEnd; - } - } else { - pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock; - } - - /* If the block being inserted plugged a gab, so was merged with the block - before and the block after, then it's pxNextFreeBlock pointer will have - already been set, and should not be set here as that would make it point - to itself. */ - if(pxIterator != pxBlockToInsert) { - pxIterator->pxNextFreeBlock = pxBlockToInsert; - } else { - mtCOVERAGE_TEST_MARKER(); - } -} + return memmgr_get_heap_size() - heap_max_used; +} \ No newline at end of file From d6145356a457d347c714c4fde0a8f91fcc261756 Mon Sep 17 00:00:00 2001 From: SG Date: Thu, 4 Apr 2024 23:58:54 +0300 Subject: [PATCH 006/129] Furi: heap walker --- applications/services/cli/cli_commands.c | 44 +++++++- furi/core/memmgr_heap.c | 131 +++++++++++++++++++++-- furi/core/memmgr_heap.h | 12 ++- targets/f7/api_symbols.csv | 4 +- 4 files changed, 178 insertions(+), 13 deletions(-) diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index 43f1c01c4..9105e40e8 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -429,12 +429,54 @@ void cli_command_free(Cli* cli, FuriString* args, void* context) { printf("Maximum pool block: %zu\r\n", memmgr_pool_get_max_block()); } +typedef struct { + void* addr; + size_t size; +} FreeBlockInfo; + +#define FREE_BLOCK_INFO_MAX 128 + +typedef struct { + FreeBlockInfo free_blocks[FREE_BLOCK_INFO_MAX]; + size_t free_blocks_count; +} FreeBlockContext; + +static bool free_block_walker(void* pointer, size_t size, bool used, void* context) { + FreeBlockContext* free_blocks = (FreeBlockContext*)context; + if(!used) { + if(free_blocks->free_blocks_count < FREE_BLOCK_INFO_MAX) { + free_blocks->free_blocks[free_blocks->free_blocks_count].addr = pointer; + free_blocks->free_blocks[free_blocks->free_blocks_count].size = size; + free_blocks->free_blocks_count++; + } else { + return false; + } + } + return true; +} + void cli_command_free_blocks(Cli* cli, FuriString* args, void* context) { UNUSED(cli); UNUSED(args); UNUSED(context); - memmgr_heap_printf_free_blocks(); + FreeBlockContext* free_blocks = malloc(sizeof(FreeBlockContext)); + free_blocks->free_blocks_count = 0; + + memmgr_heap_walk_blocks(free_block_walker, free_blocks); + + for(size_t i = 0; i < free_blocks->free_blocks_count; i++) { + printf( + "A %p S %zu\r\n", + (void*)free_blocks->free_blocks[i].addr, + free_blocks->free_blocks[i].size); + } + + if(free_blocks->free_blocks_count == FREE_BLOCK_INFO_MAX) { + printf("... and more\r\n"); + } + + free(free_blocks); } void cli_command_i2c(Cli* cli, FuriString* args, void* context) { diff --git a/furi/core/memmgr_heap.c b/furi/core/memmgr_heap.c index 65e1cae1d..748ab845b 100644 --- a/furi/core/memmgr_heap.c +++ b/furi/core/memmgr_heap.c @@ -11,6 +11,23 @@ static tlsf_t tlsf = NULL; static size_t heap_used = 0; static size_t heap_max_used = 0; +// Furi heap extension +#include + +// Allocation tracking types +DICT_DEF2(MemmgrHeapAllocDict, uint32_t, uint32_t) //-V1048 + +DICT_DEF2( //-V1048 + MemmgrHeapThreadDict, + uint32_t, + M_DEFAULT_OPLIST, + MemmgrHeapAllocDict_t, + DICT_OPLIST(MemmgrHeapAllocDict)) + +// Thread allocation tracing storage +static MemmgrHeapThreadDict_t memmgr_heap_thread_dict = {0}; +static volatile uint32_t memmgr_heap_thread_trace_depth = 0; + static inline void memmgr_lock(void) { vTaskSuspendAll(); } @@ -23,24 +40,70 @@ static inline size_t memmgr_get_heap_size(void) { return (size_t)&__heap_end__ - (size_t)&__heap_start__; } +// Initialize tracing storage on start +void memmgr_heap_init(void) { + MemmgrHeapThreadDict_init(memmgr_heap_thread_dict); +} + void memmgr_heap_enable_thread_trace(FuriThreadId thread_id) { - UNUSED(thread_id); + memmgr_lock(); + { + memmgr_heap_thread_trace_depth++; + furi_check(MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id) == NULL); + MemmgrHeapAllocDict_t alloc_dict; + MemmgrHeapAllocDict_init(alloc_dict); + MemmgrHeapThreadDict_set_at(memmgr_heap_thread_dict, (uint32_t)thread_id, alloc_dict); + MemmgrHeapAllocDict_clear(alloc_dict); + memmgr_heap_thread_trace_depth--; + } + memmgr_unlock(); } void memmgr_heap_disable_thread_trace(FuriThreadId thread_id) { - UNUSED(thread_id); + memmgr_lock(); + { + memmgr_heap_thread_trace_depth++; + furi_check(MemmgrHeapThreadDict_erase(memmgr_heap_thread_dict, (uint32_t)thread_id)); + memmgr_heap_thread_trace_depth--; + } + memmgr_unlock(); } size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id) { - UNUSED(thread_id); - return 0; + size_t leftovers = MEMMGR_HEAP_UNKNOWN; + vTaskSuspendAll(); + { + memmgr_heap_thread_trace_depth++; + MemmgrHeapAllocDict_t* alloc_dict = + MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); + if(alloc_dict) { + leftovers = 0; + MemmgrHeapAllocDict_it_t alloc_dict_it; + for(MemmgrHeapAllocDict_it(alloc_dict_it, *alloc_dict); + !MemmgrHeapAllocDict_end_p(alloc_dict_it); + MemmgrHeapAllocDict_next(alloc_dict_it)) { + MemmgrHeapAllocDict_itref_t* data = MemmgrHeapAllocDict_ref(alloc_dict_it); + if(data->key != 0) { + block_header_t* block = block_from_ptr((uint8_t*)data->key); + if(!block_is_free(block)) { + // TODO: with tlsf we know the size of the block, so we don't need to store it on the dict + leftovers += data->value; + } + } + } + } + memmgr_heap_thread_trace_depth--; + } + (void)xTaskResumeAll(); + return leftovers; } static bool tlsf_walker_max_free(void* ptr, size_t size, int used, void* user) { UNUSED(ptr); + bool free = !used; size_t* max_free_block_size = (size_t*)user; - if(!used && size > *max_free_block_size) { + if(free && size > *max_free_block_size) { *max_free_block_size = size; } @@ -60,7 +123,52 @@ size_t memmgr_heap_get_max_free_block(void) { return max_free_block_size; } -void memmgr_heap_printf_free_blocks(void) { +typedef struct { + BlockWalker walker; + void* context; +} BlockWalkerWrapper; + +static bool tlsf_walker_wrapper(void* ptr, size_t size, int used, void* user) { + BlockWalkerWrapper* wrapper = (BlockWalkerWrapper*)user; + return wrapper->walker(ptr, size, used, wrapper->context); +} + +void memmgr_heap_walk_blocks(BlockWalker walker, void* context) { + memmgr_lock(); + + BlockWalkerWrapper wrapper = {walker, context}; + pool_t pool = tlsf_get_pool(tlsf); + tlsf_walk_pool(pool, tlsf_walker_wrapper, &wrapper); + + memmgr_unlock(); +} + +static inline void memmgr_heap_trace_malloc(void* pointer, size_t size) { + FuriThreadId thread_id = furi_thread_get_current_id(); + if(thread_id && memmgr_heap_thread_trace_depth == 0) { + memmgr_heap_thread_trace_depth++; + MemmgrHeapAllocDict_t* alloc_dict = + MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); + if(alloc_dict) { + MemmgrHeapAllocDict_set_at(*alloc_dict, (uint32_t)pointer, (uint32_t)size); + } + memmgr_heap_thread_trace_depth--; + } +} + +static inline void memmgr_heap_trace_free(void* pointer) { + FuriThreadId thread_id = furi_thread_get_current_id(); + if(thread_id && memmgr_heap_thread_trace_depth == 0) { + memmgr_heap_thread_trace_depth++; + MemmgrHeapAllocDict_t* alloc_dict = + MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); + if(alloc_dict) { + // In some cases thread may want to release memory that was not allocated by it + const bool res = MemmgrHeapAllocDict_erase(*alloc_dict, (uint32_t)pointer); + UNUSED(res); + } + memmgr_heap_thread_trace_depth--; + } } void* pvPortMalloc(size_t xSize) { @@ -75,6 +183,7 @@ void* pvPortMalloc(size_t xSize) { if(tlsf == NULL) { size_t pool_size = (size_t)&__heap_end__ - (size_t)&__heap_start__; tlsf = tlsf_create_with_pool((void*)&__heap_start__, pool_size, pool_size); + memmgr_heap_init(); } // allocate block @@ -93,6 +202,9 @@ void* pvPortMalloc(size_t xSize) { // clear block content memset(data, 0, xSize); + // trace allocation + memmgr_heap_trace_malloc(data, xSize); + memmgr_unlock(); return data; @@ -119,12 +231,15 @@ void vPortFree(void* pv) { // free tlsf_free(tlsf, pv); + // trace free + memmgr_heap_trace_free(pv); + memmgr_unlock(); } } size_t xPortGetFreeHeapSize(void) { - return memmgr_get_heap_size() - heap_used; + return memmgr_get_heap_size() - heap_used - tlsf_size(tlsf); } size_t xPortGetTotalHeapSize(void) { @@ -132,5 +247,5 @@ size_t xPortGetTotalHeapSize(void) { } size_t xPortGetMinimumEverFreeHeapSize(void) { - return memmgr_get_heap_size() - heap_max_used; + return memmgr_get_heap_size() - heap_max_used - tlsf_size(tlsf); } \ No newline at end of file diff --git a/furi/core/memmgr_heap.h b/furi/core/memmgr_heap.h index 7d889f152..2f61deb64 100644 --- a/furi/core/memmgr_heap.h +++ b/furi/core/memmgr_heap.h @@ -40,9 +40,17 @@ size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id); */ size_t memmgr_heap_get_max_free_block(void); -/** Print the address and size of all free blocks to stdout +typedef bool (*BlockWalker)(void* pointer, size_t size, bool used, void* context); + +/** + * @brief Walk through all heap blocks + * @warning This function will lock memory manager and may cause deadlocks if any malloc/free is called inside the callback. + * Also, printf and furi_log contains malloc calls, so do not use them. + * + * @param walker + * @param context */ -void memmgr_heap_printf_free_blocks(void); +void memmgr_heap_walk_blocks(BlockWalker walker, void* context); #ifdef __cplusplus } diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 6e65b9471..1d0cf168d 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.4,, +Version,+,61.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -2360,7 +2360,7 @@ Function,+,memmgr_heap_disable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_enable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_get_max_free_block,size_t, Function,+,memmgr_heap_get_thread_memory,size_t,FuriThreadId -Function,+,memmgr_heap_printf_free_blocks,void, +Function,+,memmgr_heap_walk_blocks,void,"BlockWalker, void*" Function,-,memmgr_pool_get_free,size_t, Function,-,memmgr_pool_get_max_block,size_t, Function,+,memmove,void*,"void*, const void*, size_t" From c868f924acb0624ce5980f6a14ba4cc8a6cdaa58 Mon Sep 17 00:00:00 2001 From: SG Date: Fri, 5 Apr 2024 00:04:18 +0300 Subject: [PATCH 007/129] shmal fixshesh --- furi/core/memmgr_heap.c | 2 +- targets/f18/api_symbols.csv | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/furi/core/memmgr_heap.c b/furi/core/memmgr_heap.c index 748ab845b..f45c2da3b 100644 --- a/furi/core/memmgr_heap.c +++ b/furi/core/memmgr_heap.c @@ -86,7 +86,7 @@ size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id) { if(data->key != 0) { block_header_t* block = block_from_ptr((uint8_t*)data->key); if(!block_is_free(block)) { - // TODO: with tlsf we know the size of the block, so we don't need to store it on the dict + // with tlsf we know the size of the block, so we don't need to store it on the dict leftovers += data->value; } } diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index f6199445d..79ad30648 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.4,, +Version,+,61.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1980,7 +1980,7 @@ Function,+,memmgr_heap_disable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_enable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_get_max_free_block,size_t, Function,+,memmgr_heap_get_thread_memory,size_t,FuriThreadId -Function,+,memmgr_heap_printf_free_blocks,void, +Function,+,memmgr_heap_walk_blocks,void,"BlockWalker, void*" Function,-,memmgr_pool_get_free,size_t, Function,-,memmgr_pool_get_max_block,size_t, Function,+,memmove,void*,"void*, const void*, size_t" From 0f4b048b8a47a2078edb9b9e557e68586e5650f1 Mon Sep 17 00:00:00 2001 From: SG Date: Fri, 5 Apr 2024 00:04:54 +0300 Subject: [PATCH 008/129] f18: tlsf --- targets/f18/target.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/targets/f18/target.json b/targets/f18/target.json index 43e9254cd..a61c1373e 100644 --- a/targets/f18/target.json +++ b/targets/f18/target.json @@ -13,6 +13,7 @@ "print", "flipper18", "furi", + "tlsf", "freertos", "stm32wb", "hwdrivers", @@ -68,4 +69,4 @@ "ibutton", "infrared" ] -} +} \ No newline at end of file From 2a234e4129f210d3be8ef8c4ae88b58e276cf841 Mon Sep 17 00:00:00 2001 From: SG Date: Fri, 5 Apr 2024 00:26:57 +0300 Subject: [PATCH 009/129] PVS: ignore tlsf --- .pvsoptions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pvsoptions b/.pvsoptions index 8606eef15..590a34de8 100644 --- a/.pvsoptions +++ b/.pvsoptions @@ -1 +1 @@ ---ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/microtar -e lib/mlib -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e lib/mjs -e */arm-none-eabi/* +--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/tlsf -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/microtar -e lib/mlib -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e lib/mjs -e */arm-none-eabi/* From a830d6b5113bf7232ecb5fe683c0a6c216db068d Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 5 Apr 2024 04:02:33 +0300 Subject: [PATCH 010/129] rework subghz settings, enable tx-rx state on unused gpio pin by default --- .../scenes/subghz_scene_radio_settings.c | 37 ----- .../main/subghz/subghz_dangerous_freq.c | 3 - .../main/subghz/subghz_last_settings.c | 146 +++++++----------- .../main/subghz/subghz_last_settings.h | 5 - lib/subghz/devices/devices.c | 2 +- targets/f7/api_symbols.csv | 2 - targets/f7/furi_hal/furi_hal_subghz.c | 10 -- targets/f7/furi_hal/furi_hal_subghz.h | 49 +----- 8 files changed, 55 insertions(+), 199 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_radio_settings.c b/applications/main/subghz/scenes/subghz_scene_radio_settings.c index ec4fee4a1..e16a422f0 100644 --- a/applications/main/subghz/scenes/subghz_scene_radio_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_radio_settings.c @@ -20,12 +20,6 @@ const char* const timestamp_names_text[TIMESTAMP_NAMES_COUNT] = { "ON", }; -#define EXT_MOD_POWER_AMP_COUNT 2 -const char* const ext_mod_power_amp_text[EXT_MOD_POWER_AMP_COUNT] = { - "OFF", - "ON", -}; - #define DEBUG_P_COUNT 2 const char* const debug_pin_text[DEBUG_P_COUNT] = { "OFF", @@ -94,27 +88,6 @@ static void subghz_scene_receiver_config_set_debug_counter(VariableItem* item) { furi_hal_subghz_set_rolling_counter_mult(debug_counter_val[index]); } -static void subghz_scene_reciever_config_set_ext_mod_power_amp_text(VariableItem* item) { - SubGhz* subghz = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - - variable_item_set_current_value_text(item, ext_mod_power_amp_text[index]); - - subghz->last_settings->external_module_power_amp = index == 1; - - // Set globally in furi hal - furi_hal_subghz_set_ext_power_amp(subghz->last_settings->external_module_power_amp); - - subghz_last_settings_save(subghz->last_settings); - - // reinit external device - const SubGhzRadioDeviceType current = subghz_txrx_radio_device_get(subghz->txrx); - if(current != SubGhzRadioDeviceTypeInternal) { - subghz_txrx_radio_device_set(subghz->txrx, SubGhzRadioDeviceTypeInternal); - subghz_txrx_radio_device_set(subghz->txrx, current); - } -} - static void subghz_scene_receiver_config_set_timestamp_file_names(VariableItem* item) { SubGhz* subghz = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); @@ -147,16 +120,6 @@ void subghz_scene_radio_settings_on_enter(void* context) { variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, radio_device_text[value_index]); - item = variable_item_list_add( - variable_item_list, - "Ext Power Amp", - EXT_MOD_POWER_AMP_COUNT, - subghz_scene_reciever_config_set_ext_mod_power_amp_text, - subghz); - value_index = subghz->last_settings->external_module_power_amp ? 1 : 0; - variable_item_set_current_value_index(item, value_index); - variable_item_set_current_value_text(item, ext_mod_power_amp_text[value_index]); - item = variable_item_list_add( variable_item_list, "Protocol Names", diff --git a/applications/main/subghz/subghz_dangerous_freq.c b/applications/main/subghz/subghz_dangerous_freq.c index 9722d28b6..699c42754 100644 --- a/applications/main/subghz/subghz_dangerous_freq.c +++ b/applications/main/subghz/subghz_dangerous_freq.c @@ -27,9 +27,6 @@ void subghz_dangerous_freq() { SubGhzLastSettings* last_settings = subghz_last_settings_alloc(); subghz_last_settings_load(last_settings, 0); - // Set globally in furi hal - furi_hal_subghz_set_ext_power_amp(last_settings->external_module_power_amp); - subghz_last_settings_free(last_settings); furi_record_close(RECORD_STORAGE); diff --git a/applications/main/subghz/subghz_last_settings.c b/applications/main/subghz/subghz_last_settings.c index 5dd2680e2..262f60c63 100644 --- a/applications/main/subghz/subghz_last_settings.c +++ b/applications/main/subghz/subghz_last_settings.c @@ -4,17 +4,14 @@ #define TAG "SubGhzLastSettings" #define SUBGHZ_LAST_SETTING_FILE_TYPE "Flipper SubGhz Last Setting File" -#define SUBGHZ_LAST_SETTING_FILE_VERSION 1 +#define SUBGHZ_LAST_SETTING_FILE_VERSION 2 #define SUBGHZ_LAST_SETTINGS_PATH EXT_PATH("subghz/assets/last_subghz.settings") #define SUBGHZ_LAST_SETTING_FIELD_FREQUENCY "Frequency" #define SUBGHZ_LAST_SETTING_FIELD_PRESET "Preset" // AKA Modulation #define SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_FEEDBACK_LEVEL "FeedbackLevel" #define SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_TRIGGER "FATrigger" -#define SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_ENABLED "External" -#define SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_POWER "ExtPower" #define SUBGHZ_LAST_SETTING_FIELD_TIMESTAMP_FILE_NAMES "TimestampNames" -#define SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_POWER_AMP "ExtPowerAmp" #define SUBGHZ_LAST_SETTING_FIELD_HOPPING_ENABLE "Hopping" #define SUBGHZ_LAST_SETTING_FIELD_IGNORE_FILTER "IgnoreFilter" #define SUBGHZ_LAST_SETTING_FIELD_FILTER "Filter" @@ -40,9 +37,6 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count uint32_t temp_frequency = 0; uint32_t temp_frequency_analyzer_feedback_level = 0; float temp_frequency_analyzer_trigger = 0; - bool temp_external_module_enabled = false; - bool temp_external_module_power_5v_disable = false; - bool temp_external_module_power_amp = false; bool temp_timestamp_file_names = false; bool temp_enable_hopping = false; bool temp_delete_old_sig = false; @@ -58,62 +52,63 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count bool frequency_analyzer_feedback_level_was_read = false; bool frequency_analyzer_trigger_was_read = false; + FuriString* temp_str = furi_string_alloc(); + uint32_t config_version = 0; + if(FSE_OK == storage_sd_status(storage) && SUBGHZ_LAST_SETTINGS_PATH && flipper_format_file_open_existing(fff_data_file, SUBGHZ_LAST_SETTINGS_PATH)) { - preset_was_read = flipper_format_read_uint32( - fff_data_file, SUBGHZ_LAST_SETTING_FIELD_PRESET, (uint32_t*)&temp_preset, 1); - flipper_format_read_uint32( - fff_data_file, SUBGHZ_LAST_SETTING_FIELD_FREQUENCY, (uint32_t*)&temp_frequency, 1); - frequency_analyzer_feedback_level_was_read = flipper_format_read_uint32( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_FEEDBACK_LEVEL, - (uint32_t*)&temp_frequency_analyzer_feedback_level, - 1); - frequency_analyzer_trigger_was_read = flipper_format_read_float( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_TRIGGER, - (float*)&temp_frequency_analyzer_trigger, - 1); - flipper_format_read_bool( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_ENABLED, - (bool*)&temp_external_module_enabled, - 1); - flipper_format_read_bool( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_POWER, - (bool*)&temp_external_module_power_5v_disable, - 1); - flipper_format_read_bool( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_TIMESTAMP_FILE_NAMES, - (bool*)&temp_timestamp_file_names, - 1); - flipper_format_read_bool( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_POWER_AMP, - (bool*)&temp_external_module_power_amp, - 1); - flipper_format_read_bool( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_HOPPING_ENABLE, - (bool*)&temp_enable_hopping, - 1); - rssi_was_read = flipper_format_read_float( - fff_data_file, SUBGHZ_LAST_SETTING_FIELD_RSSI_THRESHOLD, (float*)&temp_rssi, 1); - ignore_filter_was_read = flipper_format_read_uint32( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_IGNORE_FILTER, - (uint32_t*)&temp_ignore_filter, - 1); - filter_was_read = flipper_format_read_uint32( - fff_data_file, SUBGHZ_LAST_SETTING_FIELD_FILTER, (uint32_t*)&temp_filter, 1); - flipper_format_read_bool( - fff_data_file, SUBGHZ_LAST_SETTING_FIELD_DELETE_OLD, (bool*)&temp_delete_old_sig, 1); + do { + if(!flipper_format_read_header(fff_data_file, temp_str, &config_version)) break; + if((strcmp(furi_string_get_cstr(temp_str), SUBGHZ_LAST_SETTING_FILE_TYPE) != 0) || + (config_version != SUBGHZ_LAST_SETTING_FILE_VERSION)) { + break; + } + + preset_was_read = flipper_format_read_uint32( + fff_data_file, SUBGHZ_LAST_SETTING_FIELD_PRESET, (uint32_t*)&temp_preset, 1); + flipper_format_read_uint32( + fff_data_file, SUBGHZ_LAST_SETTING_FIELD_FREQUENCY, (uint32_t*)&temp_frequency, 1); + frequency_analyzer_feedback_level_was_read = flipper_format_read_uint32( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_FEEDBACK_LEVEL, + (uint32_t*)&temp_frequency_analyzer_feedback_level, + 1); + frequency_analyzer_trigger_was_read = flipper_format_read_float( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_TRIGGER, + (float*)&temp_frequency_analyzer_trigger, + 1); + flipper_format_read_bool( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_TIMESTAMP_FILE_NAMES, + (bool*)&temp_timestamp_file_names, + 1); + flipper_format_read_bool( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_HOPPING_ENABLE, + (bool*)&temp_enable_hopping, + 1); + rssi_was_read = flipper_format_read_float( + fff_data_file, SUBGHZ_LAST_SETTING_FIELD_RSSI_THRESHOLD, (float*)&temp_rssi, 1); + ignore_filter_was_read = flipper_format_read_uint32( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_IGNORE_FILTER, + (uint32_t*)&temp_ignore_filter, + 1); + filter_was_read = flipper_format_read_uint32( + fff_data_file, SUBGHZ_LAST_SETTING_FIELD_FILTER, (uint32_t*)&temp_filter, 1); + flipper_format_read_bool( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_DELETE_OLD, + (bool*)&temp_delete_old_sig, + 1); + } while(0); } else { FURI_LOG_E(TAG, "Error open file %s", SUBGHZ_LAST_SETTINGS_PATH); } + furi_string_free(temp_str); + if(temp_frequency == 0 || !furi_hal_subghz_is_tx_allowed(temp_frequency)) { FURI_LOG_W(TAG, "Last used frequency not found or can't be used!"); @@ -122,9 +117,7 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count instance->frequency_analyzer_feedback_level = SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_FEEDBACK_LEVEL; instance->frequency_analyzer_trigger = SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_TRIGGER; - instance->external_module_enabled = false; instance->timestamp_file_names = false; - instance->external_module_power_amp = false; instance->enable_hopping = false; instance->delete_old_signals = false; instance->ignore_filter = 0x00; @@ -155,17 +148,10 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count } else { instance->preset_index = temp_preset; } - instance->external_module_enabled = temp_external_module_enabled; - - instance->external_module_power_5v_disable = temp_external_module_power_5v_disable; - instance->timestamp_file_names = temp_timestamp_file_names; instance->delete_old_signals = temp_delete_old_sig; - // External power amp CC1101 - instance->external_module_power_amp = temp_external_module_power_amp; - instance->rssi = rssi_was_read ? temp_rssi : SUBGHZ_RAW_THRESHOLD_MIN; instance->enable_hopping = temp_enable_hopping; instance->ignore_filter = ignore_filter_was_read ? temp_ignore_filter : 0x00; @@ -180,8 +166,6 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count instance->filter = SubGhzProtocolFlag_Decodable; } #endif - // Set globally in furi hal - furi_hal_subghz_set_ext_power_amp(instance->external_module_power_amp); } flipper_format_file_close(fff_data_file); @@ -233,20 +217,6 @@ bool subghz_last_settings_save(SubGhzLastSettings* instance) { 1)) { break; } - if(!flipper_format_insert_or_update_bool( - file, - SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_ENABLED, - &instance->external_module_enabled, - 1)) { - break; - } - if(!flipper_format_insert_or_update_bool( - file, - SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_POWER, - &instance->external_module_power_5v_disable, - 1)) { - break; - } if(!flipper_format_insert_or_update_bool( file, SUBGHZ_LAST_SETTING_FIELD_TIMESTAMP_FILE_NAMES, @@ -254,13 +224,6 @@ bool subghz_last_settings_save(SubGhzLastSettings* instance) { 1)) { break; } - if(!flipper_format_insert_or_update_bool( - file, - SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_POWER_AMP, - &instance->external_module_power_amp, - 1)) { - break; - } if(!flipper_format_insert_or_update_bool( file, SUBGHZ_LAST_SETTING_FIELD_HOPPING_ENABLE, &instance->enable_hopping, 1)) { break; @@ -312,17 +275,14 @@ void subghz_last_settings_log(SubGhzLastSettings* instance) { FURI_LOG_I( TAG, - "Frequency: %03ld.%02ld, FeedbackLevel: %ld, FATrigger: %.2f, External: %s, ExtPower: %s, TimestampNames: %s, ExtPowerAmp: %s,\n" + "Frequency: %03ld.%02ld, FeedbackLevel: %ld, FATrigger: %.2f, TimestampNames: %s,\n" "Hopping: %s,\nPreset: %ld, RSSI: %.2f, " "Starline: %s, Cars: %s, Magellan: %s, NiceFloR-S: %s, BinRAW: %s", instance->frequency / 1000000 % 1000, instance->frequency / 10000 % 100, instance->frequency_analyzer_feedback_level, (double)instance->frequency_analyzer_trigger, - bool_to_char(instance->external_module_enabled), - bool_to_char(instance->external_module_power_5v_disable), bool_to_char(instance->timestamp_file_names), - bool_to_char(instance->external_module_power_amp), bool_to_char(instance->enable_hopping), instance->preset_index, (double)instance->rssi, diff --git a/applications/main/subghz/subghz_last_settings.h b/applications/main/subghz/subghz_last_settings.h index 74dded4b7..084e76738 100644 --- a/applications/main/subghz/subghz_last_settings.h +++ b/applications/main/subghz/subghz_last_settings.h @@ -20,11 +20,6 @@ typedef struct { uint32_t preset_index; // AKA Modulation uint32_t frequency_analyzer_feedback_level; float frequency_analyzer_trigger; - // TODO not using but saved so as not to change the version - bool external_module_enabled; - bool external_module_power_5v_disable; - bool external_module_power_amp; - // saved so as not to change the version bool timestamp_file_names; bool enable_hopping; uint32_t ignore_filter; diff --git a/lib/subghz/devices/devices.c b/lib/subghz/devices/devices.c index c3fb3168b..9ae252017 100644 --- a/lib/subghz/devices/devices.c +++ b/lib/subghz/devices/devices.c @@ -33,7 +33,7 @@ bool subghz_devices_begin(const SubGhzDevice* device) { SubGhzDeviceConf conf = { .ver = 1, .extended_range = false, // TODO - .power_amp = furi_hal_subghz_get_ext_power_amp(), + .power_amp = true, }; ret = device->interconnect->begin(&conf); diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 236a41f9c..9fe9964f0 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1590,7 +1590,6 @@ Function,-,furi_hal_subghz_dump_state,void, Function,+,furi_hal_subghz_flush_rx,void, Function,+,furi_hal_subghz_flush_tx,void, Function,+,furi_hal_subghz_get_data_gpio,const GpioPin*, -Function,+,furi_hal_subghz_get_ext_power_amp,_Bool, Function,+,furi_hal_subghz_get_lqi,uint8_t, Function,+,furi_hal_subghz_get_rolling_counter_mult,int8_t, Function,+,furi_hal_subghz_get_rssi,float, @@ -1608,7 +1607,6 @@ Function,+,furi_hal_subghz_reset,void, Function,+,furi_hal_subghz_rx,void, Function,+,furi_hal_subghz_rx_pipe_not_empty,_Bool, Function,+,furi_hal_subghz_set_async_mirror_pin,void,const GpioPin* -Function,+,furi_hal_subghz_set_ext_power_amp,void,_Bool Function,+,furi_hal_subghz_set_frequency,uint32_t,uint32_t Function,+,furi_hal_subghz_set_frequency_and_path,uint32_t,uint32_t Function,+,furi_hal_subghz_set_path,void,FuriHalSubGhzPath diff --git a/targets/f7/furi_hal/furi_hal_subghz.c b/targets/f7/furi_hal/furi_hal_subghz.c index 29418bfc3..843f637f5 100644 --- a/targets/f7/furi_hal/furi_hal_subghz.c +++ b/targets/f7/furi_hal/furi_hal_subghz.c @@ -52,7 +52,6 @@ typedef struct { const GpioPin* async_mirror_pin; int8_t rolling_counter_mult; - bool ext_power_amp : 1; bool dangerous_frequency_i : 1; } FuriHalSubGhz; @@ -61,7 +60,6 @@ volatile FuriHalSubGhz furi_hal_subghz = { .regulation = SubGhzRegulationTxRx, .async_mirror_pin = NULL, .rolling_counter_mult = 1, - .ext_power_amp = false, .dangerous_frequency_i = false, }; @@ -77,14 +75,6 @@ void furi_hal_subghz_set_dangerous_frequency(bool state_i) { furi_hal_subghz.dangerous_frequency_i = state_i; } -void furi_hal_subghz_set_ext_power_amp(bool enabled) { - furi_hal_subghz.ext_power_amp = enabled; -} - -bool furi_hal_subghz_get_ext_power_amp(void) { - return furi_hal_subghz.ext_power_amp; -} - void furi_hal_subghz_set_async_mirror_pin(const GpioPin* pin) { furi_hal_subghz.async_mirror_pin = pin; } diff --git a/targets/f7/furi_hal/furi_hal_subghz.h b/targets/f7/furi_hal/furi_hal_subghz.h index 68c040928..10d89947a 100644 --- a/targets/f7/furi_hal/furi_hal_subghz.h +++ b/targets/f7/furi_hal/furi_hal_subghz.h @@ -238,54 +238,7 @@ bool furi_hal_subghz_is_async_tx_complete(void); */ void furi_hal_subghz_stop_async_tx(void); -// /** Initialize and switch to power save mode Used by internal API-HAL -// * initialization routine Can be used to reinitialize device to safe state and -// * send it to sleep -// * @return true if initialisation is successfully -// */ -// bool furi_hal_subghz_init_check(void); - -// /** Switching between internal and external radio -// * @param state SubGhzRadioInternal or SubGhzRadioExternal -// * @return true if switching is successful -// */ -// bool furi_hal_subghz_init_radio_type(SubGhzRadioType state); - -// /** Get current radio -// * @return SubGhzRadioInternal or SubGhzRadioExternal -// */ -// SubGhzRadioType furi_hal_subghz_get_radio_type(void); - -// /** Check for a radio module -// * @return true if check is successful -// */ -// bool furi_hal_subghz_check_radio(void); - -// /** Turn on the power of the external radio module -// * @return true if power-up is successful -// */ -// bool furi_hal_subghz_enable_ext_power(void); - -// /** Turn off the power of the external radio module -// */ -// void furi_hal_subghz_disable_ext_power(void); - -// /** If true - disable 5v power of the external radio module -// */ -// void furi_hal_subghz_set_external_power_disable(bool state); - -// /** Get the current state of the external power disable flag -// */ -// bool furi_hal_subghz_get_external_power_disable(void); - -// /** Set what radio module we will be using -// */ -// void furi_hal_subghz_select_radio_type(SubGhzRadioType state); - -// External CC1101 Ebytes power amplifier control -void furi_hal_subghz_set_ext_power_amp(bool enabled); - -bool furi_hal_subghz_get_ext_power_amp(void); +// External CC1101 Ebytes power amplifier control is now enabled by default #ifdef __cplusplus } From ff7cb37c451dabeb57a25824bef9eabd160bf497 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 5 Apr 2024 04:26:29 +0100 Subject: [PATCH 011/129] SubGHz: Refactor last settings logic --- .../scenes/subghz_scene_frequency_analyzer.c | 3 - .../subghz/scenes/subghz_scene_read_raw.c | 2 - .../subghz/scenes/subghz_scene_receiver.c | 4 - .../scenes/subghz_scene_receiver_config.c | 10 - applications/main/subghz/subghz.c | 7 - .../main/subghz/subghz_last_settings.c | 444 +++++++----------- .../main/subghz/subghz_last_settings.h | 13 +- 7 files changed, 162 insertions(+), 321 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c b/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c index 308f6dbb3..d72cf0a57 100644 --- a/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c +++ b/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c @@ -60,9 +60,6 @@ bool subghz_scene_frequency_analyzer_on_event(void* context, SceneManagerEvent e subghz_frequency_analyzer_get_frequency_to_save(subghz->subghz_frequency_analyzer); if(frequency > 0) { subghz->last_settings->frequency = frequency; -#ifdef FURI_DEBUG - subghz_last_settings_log(subghz->last_settings); -#endif // Disable Hopping before opening the receiver scene! if(subghz->last_settings->enable_hopping) { subghz->last_settings->enable_hopping = false; diff --git a/applications/main/subghz/scenes/subghz_scene_read_raw.c b/applications/main/subghz/scenes/subghz_scene_read_raw.c index ca1268917..46f22b080 100644 --- a/applications/main/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_read_raw.c @@ -104,14 +104,12 @@ void subghz_scene_read_raw_on_enter(void* context) { if(subghz_rx_key_state_get(subghz) != SubGhzRxKeyStateBack) { subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE); -#if SUBGHZ_LAST_SETTING_SAVE_PRESET if(furi_string_empty(file_name)) { subghz_txrx_set_preset_internal( subghz->txrx, subghz->last_settings->frequency, subghz->last_settings->preset_index); } -#endif } subghz_scene_read_raw_update_statusbar(subghz); diff --git a/applications/main/subghz/scenes/subghz_scene_receiver.c b/applications/main/subghz/scenes/subghz_scene_receiver.c index cd300e7c1..32ad1d2c9 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver.c @@ -250,12 +250,8 @@ void subghz_scene_receiver_on_enter(void* context) { FuriString* item_time = furi_string_alloc(); if(subghz_rx_key_state_get(subghz) == SubGhzRxKeyStateIDLE) { -#if SUBGHZ_LAST_SETTING_SAVE_PRESET subghz_txrx_set_preset_internal( subghz->txrx, subghz->last_settings->frequency, subghz->last_settings->preset_index); -#else - subghz_txrx_set_default_preset(subghz->txrx, subghz->last_settings->frequency); -#endif subghz->filter = subghz->last_settings->filter; subghz_txrx_receiver_set_filter(subghz->txrx, subghz->filter); diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_config.c b/applications/main/subghz/scenes/subghz_scene_receiver_config.c index ce0d82ee6..dc91d7e6a 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_config.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_config.c @@ -410,14 +410,10 @@ static void subghz_scene_receiver_config_var_list_enter_callback(void* context, subghz->view_dispatcher, SubGhzCustomEventSceneSettingLock); } else if(index == SubGhzSettingIndexResetToDefault) { // Reset all values to default state! -#if SUBGHZ_LAST_SETTING_SAVE_PRESET subghz_txrx_set_preset_internal( subghz->txrx, SUBGHZ_LAST_SETTING_DEFAULT_FREQUENCY, SUBGHZ_LAST_SETTING_DEFAULT_PRESET); -#else - subghz_txrx_set_default_preset(subghz->txrx, SUBGHZ_LAST_SETTING_DEFAULT_FREQUENCY); -#endif SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx); SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx); const char* preset_name = furi_string_get_cstr(preset.name); @@ -449,9 +445,6 @@ static void subghz_scene_receiver_config_var_list_enter_callback(void* context, variable_item_list_set_selected_item(subghz->variable_item_list, default_index); variable_item_list_reset(subghz->variable_item_list); -#ifdef FURI_DEBUG - subghz_last_settings_log(subghz->last_settings); -#endif subghz_last_settings_save(subghz->last_settings); view_dispatcher_send_custom_event( @@ -745,9 +738,6 @@ void subghz_scene_receiver_config_on_exit(void* context) { variable_item_list_set_selected_item(subghz->variable_item_list, 0); variable_item_list_reset(subghz->variable_item_list); -#ifdef FURI_DEBUG - subghz_last_settings_log(subghz->last_settings); -#endif subghz_last_settings_save(subghz->last_settings); scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerNoSet); diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c index a884759dd..7d21bb2cf 100644 --- a/applications/main/subghz/subghz.c +++ b/applications/main/subghz/subghz.c @@ -207,16 +207,9 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) { subghz->last_settings = subghz_last_settings_alloc(); size_t preset_count = subghz_setting_get_preset_count(setting); subghz_last_settings_load(subghz->last_settings, preset_count); -#ifdef FURI_DEBUG - subghz_last_settings_log(subghz->last_settings); -#endif if(!alloc_for_tx_only) { -#if SUBGHZ_LAST_SETTING_SAVE_PRESET subghz_txrx_set_preset_internal( subghz->txrx, subghz->last_settings->frequency, subghz->last_settings->preset_index); -#else - subghz_txrx_set_default_preset(subghz->txrx, subghz->last_settings->frequency); -#endif subghz->history = subghz_history_alloc(); } diff --git a/applications/main/subghz/subghz_last_settings.c b/applications/main/subghz/subghz_last_settings.c index 0ab621306..7f9aa22a8 100644 --- a/applications/main/subghz/subghz_last_settings.c +++ b/applications/main/subghz/subghz_last_settings.c @@ -4,26 +4,24 @@ #define TAG "SubGhzLastSettings" #define SUBGHZ_LAST_SETTING_FILE_TYPE "Flipper SubGhz Last Setting File" -#define SUBGHZ_LAST_SETTING_FILE_VERSION 1 +#define SUBGHZ_LAST_SETTING_FILE_VERSION 2 #define SUBGHZ_LAST_SETTINGS_PATH EXT_PATH("subghz/assets/last_subghz.settings") #define SUBGHZ_LAST_SETTING_FIELD_FREQUENCY "Frequency" #define SUBGHZ_LAST_SETTING_FIELD_PRESET "Preset" // AKA Modulation #define SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_FEEDBACK_LEVEL "FeedbackLevel" #define SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_TRIGGER "FATrigger" -#define SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_ENABLED "External" -#define SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_POWER "ExtPower" -#define SUBGHZ_LAST_SETTING_FIELD_PROTOCOL_FILE_NAMES "TimestampNames" -#define SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_POWER_AMP "ExtPowerAmp" -#define SUBGHZ_LAST_SETTING_FIELD_GPS "Gps" +#define SUBGHZ_LAST_SETTING_FIELD_PROTOCOL_FILE_NAMES "ProtocolNames" #define SUBGHZ_LAST_SETTING_FIELD_HOPPING_ENABLE "Hopping" -#define SUBGHZ_LAST_SETTING_FIELD_REMOVE_DUPLICATES "RemoveDuplicates" #define SUBGHZ_LAST_SETTING_FIELD_IGNORE_FILTER "IgnoreFilter" #define SUBGHZ_LAST_SETTING_FIELD_FILTER "Filter" #define SUBGHZ_LAST_SETTING_FIELD_RSSI_THRESHOLD "RSSI" +#define SUBGHZ_LAST_SETTING_FIELD_DELETE_OLD "DelOldSignals" + +#define SUBGHZ_LAST_SETTING_FIELD_GPS_BAUDRATE "GpsBaudrate" +#define SUBGHZ_LAST_SETTING_FIELD_REMOVE_DUPLICATES "RemoveDuplicates" #define SUBGHZ_LAST_SETTING_FIELD_REPEATER "Repeater" #define SUBGHZ_LAST_SETTING_FIELD_ENABLE_SOUND "Sound" -#define SUBGHZ_LAST_SETTING_FIELD_DELETE_OLD "DelOldSignals" #define SUBGHZ_LAST_SETTING_FIELD_AUTOSAVE "Autosave" SubGhzLastSettings* subghz_last_settings_alloc(void) { @@ -39,202 +37,145 @@ void subghz_last_settings_free(SubGhzLastSettings* instance) { void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count) { furi_assert(instance); + // Default values (all others set to 0, if read from file fails these are used) + instance->frequency = SUBGHZ_LAST_SETTING_DEFAULT_FREQUENCY; + instance->preset_index = SUBGHZ_LAST_SETTING_DEFAULT_PRESET; + instance->frequency_analyzer_feedback_level = + SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_FEEDBACK_LEVEL; + instance->frequency_analyzer_trigger = SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_TRIGGER; + // See bin_raw_value in scenes/subghz_scene_receiver_config.c + instance->filter = SubGhzProtocolFlag_Decodable; + instance->rssi = SUBGHZ_RAW_THRESHOLD_MIN; + Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); - uint32_t temp_frequency = 0; - uint32_t temp_frequency_analyzer_feedback_level = 0; - float temp_frequency_analyzer_trigger = 0; - bool temp_external_module_enabled = false; - bool temp_external_module_power_5v_disable = false; - bool temp_external_module_power_amp = false; - bool temp_protocol_file_names = false; - bool temp_enable_hopping = false; - bool temp_enable_sound = false; - uint32_t temp_repeater_state; - bool temp_remove_duplicates = false; - bool temp_delete_old_sig = false; - bool temp_autosave = false; - uint32_t temp_ignore_filter = 0; - uint32_t temp_filter = 0; - float temp_rssi = 0; - uint32_t temp_preset = 0; + FuriString* temp_str = furi_string_alloc(); + uint32_t config_version = 0; - bool preset_was_read = false; - bool rssi_was_read = false; - bool filter_was_read = false; - bool ignore_filter_was_read = false; - bool remove_duplicates_was_read = false; - bool frequency_analyzer_feedback_level_was_read = false; - bool frequency_analyzer_trigger_was_read = false; - bool repeater_was_read = false; - bool enable_sound_was_read = false; - - uint32_t temp_gps_baudrate = 0; - - if(FSE_OK == storage_sd_status(storage) && SUBGHZ_LAST_SETTINGS_PATH && + if(FSE_OK == storage_sd_status(storage) && flipper_format_file_open_existing(fff_data_file, SUBGHZ_LAST_SETTINGS_PATH)) { - preset_was_read = flipper_format_read_uint32( - fff_data_file, SUBGHZ_LAST_SETTING_FIELD_PRESET, (uint32_t*)&temp_preset, 1); - flipper_format_read_uint32( - fff_data_file, SUBGHZ_LAST_SETTING_FIELD_FREQUENCY, (uint32_t*)&temp_frequency, 1); - frequency_analyzer_feedback_level_was_read = flipper_format_read_uint32( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_FEEDBACK_LEVEL, - (uint32_t*)&temp_frequency_analyzer_feedback_level, - 1); - frequency_analyzer_trigger_was_read = flipper_format_read_float( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_TRIGGER, - (float*)&temp_frequency_analyzer_trigger, - 1); - flipper_format_read_bool( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_ENABLED, - (bool*)&temp_external_module_enabled, - 1); - flipper_format_read_bool( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_POWER, - (bool*)&temp_external_module_power_5v_disable, - 1); - flipper_format_read_bool( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_PROTOCOL_FILE_NAMES, - (bool*)&temp_protocol_file_names, - 1); - flipper_format_read_bool( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_POWER_AMP, - (bool*)&temp_external_module_power_amp, - 1); - flipper_format_read_uint32( - fff_data_file, SUBGHZ_LAST_SETTING_FIELD_GPS, (uint32_t*)&temp_gps_baudrate, 1); - flipper_format_read_bool( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_HOPPING_ENABLE, - (bool*)&temp_enable_hopping, - 1); - rssi_was_read = flipper_format_read_float( - fff_data_file, SUBGHZ_LAST_SETTING_FIELD_RSSI_THRESHOLD, (float*)&temp_rssi, 1); - remove_duplicates_was_read = flipper_format_read_bool( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_REMOVE_DUPLICATES, - (bool*)&temp_remove_duplicates, - 1); - ignore_filter_was_read = flipper_format_read_uint32( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_IGNORE_FILTER, - (uint32_t*)&temp_ignore_filter, - 1); - filter_was_read = flipper_format_read_uint32( - fff_data_file, SUBGHZ_LAST_SETTING_FIELD_FILTER, (uint32_t*)&temp_filter, 1); - repeater_was_read = flipper_format_read_uint32( - fff_data_file, SUBGHZ_LAST_SETTING_FIELD_REPEATER, (uint32_t*)&temp_repeater_state, 1); - enable_sound_was_read = flipper_format_read_bool( - fff_data_file, SUBGHZ_LAST_SETTING_FIELD_ENABLE_SOUND, (bool*)&temp_enable_sound, 1); - flipper_format_read_bool( - fff_data_file, SUBGHZ_LAST_SETTING_FIELD_DELETE_OLD, (bool*)&temp_delete_old_sig, 1); - flipper_format_read_bool( - fff_data_file, SUBGHZ_LAST_SETTING_FIELD_AUTOSAVE, (bool*)&temp_autosave, 1); + do { + if(!flipper_format_read_header(fff_data_file, temp_str, &config_version)) break; + if((strcmp(furi_string_get_cstr(temp_str), SUBGHZ_LAST_SETTING_FILE_TYPE) != 0) || + (config_version != SUBGHZ_LAST_SETTING_FILE_VERSION)) { + break; + } + if(!flipper_format_read_uint32( + fff_data_file, SUBGHZ_LAST_SETTING_FIELD_FREQUENCY, &instance->frequency, 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_uint32( + fff_data_file, SUBGHZ_LAST_SETTING_FIELD_PRESET, &instance->preset_index, 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_uint32( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_FEEDBACK_LEVEL, + &instance->frequency_analyzer_feedback_level, + 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_float( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_TRIGGER, + &instance->frequency_analyzer_trigger, + 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_bool( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_PROTOCOL_FILE_NAMES, + &instance->protocol_file_names, + 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_bool( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_HOPPING_ENABLE, + &instance->enable_hopping, + 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_uint32( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_IGNORE_FILTER, + &instance->ignore_filter, + 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_uint32( + fff_data_file, SUBGHZ_LAST_SETTING_FIELD_FILTER, &instance->filter, 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_float( + fff_data_file, SUBGHZ_LAST_SETTING_FIELD_RSSI_THRESHOLD, &instance->rssi, 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_bool( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_DELETE_OLD, + &instance->delete_old_signals, + 1)) { + flipper_format_rewind(fff_data_file); + } + + if(!flipper_format_read_uint32( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_GPS_BAUDRATE, + &instance->gps_baudrate, + 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_bool( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_REMOVE_DUPLICATES, + &instance->remove_duplicates, + 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_uint32( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_REPEATER, + &instance->repeater_state, + 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_bool( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_ENABLE_SOUND, + &instance->enable_sound, + 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_bool( + fff_data_file, SUBGHZ_LAST_SETTING_FIELD_AUTOSAVE, &instance->autosave, 1)) { + flipper_format_rewind(fff_data_file); + } + } while(0); } else { FURI_LOG_E(TAG, "Error open file %s", SUBGHZ_LAST_SETTINGS_PATH); } - if(temp_frequency == 0 || !furi_hal_subghz_is_tx_allowed(temp_frequency)) { - FURI_LOG_W(TAG, "Last used frequency not found or can't be used!"); - - instance->frequency = SUBGHZ_LAST_SETTING_DEFAULT_FREQUENCY; - instance->preset_index = SUBGHZ_LAST_SETTING_DEFAULT_PRESET; - instance->frequency_analyzer_feedback_level = - SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_FEEDBACK_LEVEL; - instance->frequency_analyzer_trigger = SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_TRIGGER; - instance->external_module_enabled = false; - instance->protocol_file_names = false; - instance->external_module_power_amp = false; - instance->gps_baudrate = 0; - instance->enable_hopping = false; - instance->remove_duplicates = false; - instance->repeater_state = 0; - instance->enable_sound = 0; - instance->delete_old_signals = false; - instance->autosave = false; - instance->ignore_filter = 0x00; - // See bin_raw_value in applications/main/subghz/scenes/subghz_scene_receiver_config.c - instance->filter = SubGhzProtocolFlag_Decodable; - instance->rssi = SUBGHZ_RAW_THRESHOLD_MIN; - } else { - instance->frequency = temp_frequency; - instance->frequency_analyzer_feedback_level = - frequency_analyzer_feedback_level_was_read ? - temp_frequency_analyzer_feedback_level : - SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_FEEDBACK_LEVEL; - - instance->frequency_analyzer_trigger = frequency_analyzer_trigger_was_read ? - temp_frequency_analyzer_trigger : - SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_TRIGGER; - - if(!preset_was_read) { - FURI_LOG_W(TAG, "Preset was not read. Set default"); - instance->preset_index = SUBGHZ_LAST_SETTING_DEFAULT_PRESET; - } else if(temp_preset > (uint32_t)preset_count - 1) { - FURI_LOG_W( - TAG, - "Last used preset out of range. Preset to set: %ld, Max index: %ld. Set default", - temp_preset, - (uint32_t)preset_count - 1); - instance->preset_index = SUBGHZ_LAST_SETTING_DEFAULT_PRESET; - } else { - instance->preset_index = temp_preset; - } - instance->external_module_enabled = temp_external_module_enabled; - - instance->external_module_power_5v_disable = temp_external_module_power_5v_disable; - - instance->protocol_file_names = temp_protocol_file_names; - - instance->delete_old_signals = temp_delete_old_sig; - - instance->autosave = temp_autosave; - - // External power amp CC1101 - instance->external_module_power_amp = temp_external_module_power_amp; - - instance->rssi = rssi_was_read ? temp_rssi : SUBGHZ_RAW_THRESHOLD_MIN; - instance->enable_hopping = temp_enable_hopping; - instance->repeater_state = repeater_was_read ? temp_repeater_state : 0; - instance->enable_sound = enable_sound_was_read ? temp_enable_sound : false; - instance->remove_duplicates = remove_duplicates_was_read ? temp_remove_duplicates : false; - instance->ignore_filter = ignore_filter_was_read ? temp_ignore_filter : 0x00; -#if SUBGHZ_LAST_SETTING_SAVE_BIN_RAW - instance->filter = filter_was_read ? temp_filter : SubGhzProtocolFlag_Decodable; -#else - if(filter_was_read) { - instance->filter = temp_filter != SubGhzProtocolFlag_Decodable ? - SubGhzProtocolFlag_Decodable : - temp_filter; - } else { - instance->filter = SubGhzProtocolFlag_Decodable; - } -#endif - // Set globally in furi hal - furi_hal_subghz_set_ext_power_amp(instance->external_module_power_amp); - - instance->gps_baudrate = temp_gps_baudrate; - } + furi_string_free(temp_str); flipper_format_file_close(fff_data_file); flipper_format_free(fff_data_file); furi_record_close(RECORD_STORAGE); + + if(instance->frequency == 0 || !furi_hal_subghz_is_tx_allowed(instance->frequency)) { + instance->frequency = SUBGHZ_LAST_SETTING_DEFAULT_FREQUENCY; + } + + if(instance->preset_index > (uint32_t)preset_count - 1) { + instance->preset_index = SUBGHZ_LAST_SETTING_DEFAULT_PRESET; + } } bool subghz_last_settings_save(SubGhzLastSettings* instance) { furi_assert(instance); -#if SUBGHZ_LAST_SETTING_SAVE_BIN_RAW != true - instance->filter = SubGhzProtocolFlag_Decodable; -#endif bool saved = false; Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* file = flipper_format_file_alloc(storage); @@ -251,96 +192,76 @@ bool subghz_last_settings_save(SubGhzLastSettings* instance) { if(!flipper_format_write_header_cstr( file, SUBGHZ_LAST_SETTING_FILE_TYPE, SUBGHZ_LAST_SETTING_FILE_VERSION)) break; - if(!flipper_format_insert_or_update_uint32( - file, SUBGHZ_LAST_SETTING_FIELD_PRESET, &instance->preset_index, 1)) { - break; - } - if(!flipper_format_insert_or_update_uint32( + if(!flipper_format_write_uint32( file, SUBGHZ_LAST_SETTING_FIELD_FREQUENCY, &instance->frequency, 1)) { break; } - if(!flipper_format_insert_or_update_uint32( + if(!flipper_format_write_uint32( + file, SUBGHZ_LAST_SETTING_FIELD_PRESET, &instance->preset_index, 1)) { + break; + } + if(!flipper_format_write_uint32( file, SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_FEEDBACK_LEVEL, &instance->frequency_analyzer_feedback_level, 1)) { break; } - if(!flipper_format_insert_or_update_float( + if(!flipper_format_write_float( file, SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_TRIGGER, &instance->frequency_analyzer_trigger, 1)) { break; } - if(!flipper_format_insert_or_update_bool( - file, - SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_ENABLED, - &instance->external_module_enabled, - 1)) { - break; - } - if(!flipper_format_insert_or_update_bool( - file, - SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_POWER, - &instance->external_module_power_5v_disable, - 1)) { - break; - } - if(!flipper_format_insert_or_update_bool( + if(!flipper_format_write_bool( file, SUBGHZ_LAST_SETTING_FIELD_PROTOCOL_FILE_NAMES, &instance->protocol_file_names, 1)) { break; } - if(!flipper_format_insert_or_update_bool( - file, - SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_POWER_AMP, - &instance->external_module_power_amp, - 1)) { - break; - } - if(!flipper_format_insert_or_update_uint32( - file, SUBGHZ_LAST_SETTING_FIELD_GPS, &instance->gps_baudrate, 1)) { - break; - } - if(!flipper_format_insert_or_update_bool( + if(!flipper_format_write_bool( file, SUBGHZ_LAST_SETTING_FIELD_HOPPING_ENABLE, &instance->enable_hopping, 1)) { break; } - if(!flipper_format_insert_or_update_float( + if(!flipper_format_write_uint32( + file, SUBGHZ_LAST_SETTING_FIELD_IGNORE_FILTER, &instance->ignore_filter, 1)) { + break; + } + if(!flipper_format_write_uint32( + file, SUBGHZ_LAST_SETTING_FIELD_FILTER, &instance->filter, 1)) { + break; + } + if(!flipper_format_write_float( file, SUBGHZ_LAST_SETTING_FIELD_RSSI_THRESHOLD, &instance->rssi, 1)) { break; } - if(!flipper_format_insert_or_update_bool( + if(!flipper_format_write_bool( + file, SUBGHZ_LAST_SETTING_FIELD_DELETE_OLD, &instance->delete_old_signals, 1)) { + break; + } + + if(!flipper_format_write_uint32( + file, SUBGHZ_LAST_SETTING_FIELD_GPS_BAUDRATE, &instance->gps_baudrate, 1)) { + break; + } + if(!flipper_format_write_bool( file, SUBGHZ_LAST_SETTING_FIELD_REMOVE_DUPLICATES, &instance->remove_duplicates, 1)) { break; } - if(!flipper_format_insert_or_update_uint32( - file, SUBGHZ_LAST_SETTING_FIELD_IGNORE_FILTER, &instance->ignore_filter, 1)) { - break; - } - if(!flipper_format_insert_or_update_uint32( - file, SUBGHZ_LAST_SETTING_FIELD_FILTER, &instance->filter, 1)) { - break; - } - if(!flipper_format_insert_or_update_uint32( + if(!flipper_format_write_uint32( file, SUBGHZ_LAST_SETTING_FIELD_REPEATER, &instance->repeater_state, 1)) { break; } - if(!flipper_format_insert_or_update_bool( + if(!flipper_format_write_bool( file, SUBGHZ_LAST_SETTING_FIELD_ENABLE_SOUND, &instance->enable_sound, 1)) { break; } - if(!flipper_format_insert_or_update_bool( - file, SUBGHZ_LAST_SETTING_FIELD_DELETE_OLD, &instance->delete_old_signals, 1)) { - break; - } - if(!flipper_format_insert_or_update_bool( + if(!flipper_format_write_bool( file, SUBGHZ_LAST_SETTING_FIELD_AUTOSAVE, &instance->autosave, 1)) { break; } @@ -357,54 +278,3 @@ bool subghz_last_settings_save(SubGhzLastSettings* instance) { return saved; } - -const char* LOG_ON = "ON"; -const char* LOG_OFF = "OFF"; - -static inline const char* - subghz_last_settings_log_filter_get_index(uint32_t filter, uint32_t flag) { - return READ_BIT(filter, flag) > 0 ? LOG_ON : LOG_OFF; -} - -static inline const char* bool_to_char(bool value) { - return value ? LOG_ON : LOG_OFF; -} - -void subghz_last_settings_log(SubGhzLastSettings* instance) { - furi_assert(instance); - - FURI_LOG_I( - TAG, - "Frequency: %03ld.%02ld, FeedbackLevel: %ld, FATrigger: %.2f, External: %s, ExtPower: %s, TimestampNames: %s, ExtPowerAmp: %s,\n" - "GPSBaudrate: %ld, Hopping: %s,\nPreset: %ld, RSSI: %.2f, " - "BinRAW: %s, Repeater: %lu, Duplicates: %s, Autosave: %s, Starline: %s, Cars: %s, Magellan: %s, NiceFloR-S: %s, Weather: %s, TPMS: %s, Sound: %s", - instance->frequency / 1000000 % 1000, - instance->frequency / 10000 % 100, - instance->frequency_analyzer_feedback_level, - (double)instance->frequency_analyzer_trigger, - bool_to_char(instance->external_module_enabled), - bool_to_char(instance->external_module_power_5v_disable), - bool_to_char(instance->protocol_file_names), - bool_to_char(instance->external_module_power_amp), - instance->gps_baudrate, - bool_to_char(instance->enable_hopping), - instance->preset_index, - (double)instance->rssi, - subghz_last_settings_log_filter_get_index(instance->filter, SubGhzProtocolFlag_BinRAW), - instance->repeater_state, - bool_to_char(instance->remove_duplicates), - bool_to_char(instance->autosave), - subghz_last_settings_log_filter_get_index( - instance->ignore_filter, SubGhzProtocolFilter_StarLine), - subghz_last_settings_log_filter_get_index( - instance->ignore_filter, SubGhzProtocolFilter_AutoAlarms), - subghz_last_settings_log_filter_get_index( - instance->ignore_filter, SubGhzProtocolFilter_Magellan), - subghz_last_settings_log_filter_get_index( - instance->ignore_filter, SubGhzProtocolFilter_NiceFlorS), - subghz_last_settings_log_filter_get_index( - instance->ignore_filter, SubGhzProtocolFilter_Weather), - subghz_last_settings_log_filter_get_index( - instance->ignore_filter, SubGhzProtocolFilter_TPMS), - bool_to_char(instance->enable_sound)); -} diff --git a/applications/main/subghz/subghz_last_settings.h b/applications/main/subghz/subghz_last_settings.h index 6c0842a4e..91dfbef3a 100644 --- a/applications/main/subghz/subghz_last_settings.h +++ b/applications/main/subghz/subghz_last_settings.h @@ -7,8 +7,6 @@ #include #define SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_TRIGGER (-93.0f) -#define SUBGHZ_LAST_SETTING_SAVE_BIN_RAW true -#define SUBGHZ_LAST_SETTING_SAVE_PRESET true // 1 = "AM650" // "AM270", "AM650", "FM238", "FM476", #define SUBGHZ_LAST_SETTING_DEFAULT_PRESET 1 @@ -21,15 +19,16 @@ typedef struct { uint32_t frequency_analyzer_feedback_level; float frequency_analyzer_trigger; bool protocol_file_names; - uint32_t gps_baudrate; bool enable_hopping; - uint32_t repeater_state; - bool enable_sound; - bool remove_duplicates; uint32_t ignore_filter; uint32_t filter; float rssi; bool delete_old_signals; + + uint32_t gps_baudrate; + bool remove_duplicates; + uint32_t repeater_state; + bool enable_sound; bool autosave; } SubGhzLastSettings; @@ -40,5 +39,3 @@ void subghz_last_settings_free(SubGhzLastSettings* instance); void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count); bool subghz_last_settings_save(SubGhzLastSettings* instance); - -void subghz_last_settings_log(SubGhzLastSettings* instance); From ef29ed149da7defc3fab0ac8ee13cb419cc1de65 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 5 Apr 2024 06:36:35 +0300 Subject: [PATCH 012/129] better subghz settings and more anim unload fixes by Willy-JL --- .../scenes/subghz_scene_frequency_analyzer.c | 6 - .../scenes/subghz_scene_radio_settings.c | 4 +- .../subghz/scenes/subghz_scene_read_raw.c | 3 +- .../subghz/scenes/subghz_scene_receiver.c | 4 - .../scenes/subghz_scene_receiver_config.c | 13 +- .../subghz/scenes/subghz_scene_save_name.c | 2 +- applications/main/subghz/subghz.c | 7 - .../main/subghz/subghz_last_settings.c | 277 +++++++----------- .../main/subghz/subghz_last_settings.h | 6 +- applications/services/desktop/desktop.c | 6 +- .../desktop/scenes/desktop_scene_main.c | 2 +- applications/services/loader/loader.c | 66 +++-- applications/services/loader/loader.h | 10 + applications/services/loader/loader_i.h | 1 + targets/f7/api_symbols.csv | 1 + 15 files changed, 167 insertions(+), 241 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c b/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c index 308f6dbb3..434304024 100644 --- a/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c +++ b/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c @@ -60,9 +60,6 @@ bool subghz_scene_frequency_analyzer_on_event(void* context, SceneManagerEvent e subghz_frequency_analyzer_get_frequency_to_save(subghz->subghz_frequency_analyzer); if(frequency > 0) { subghz->last_settings->frequency = frequency; -#ifdef FURI_DEBUG - subghz_last_settings_log(subghz->last_settings); -#endif // Disable Hopping before opening the receiver scene! if(subghz->last_settings->enable_hopping) { subghz->last_settings->enable_hopping = false; @@ -73,9 +70,6 @@ bool subghz_scene_frequency_analyzer_on_event(void* context, SceneManagerEvent e return true; } else if(event.event == SubGhzCustomEventViewFreqAnalOkLong) { // Don't need to save, we already saved on short event -#ifdef FURI_DEBUG - FURI_LOG_W(TAG, "Goto next scene!"); -#endif //scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneStart, 10); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiver); return true; diff --git a/applications/main/subghz/scenes/subghz_scene_radio_settings.c b/applications/main/subghz/scenes/subghz_scene_radio_settings.c index e16a422f0..ac1675d01 100644 --- a/applications/main/subghz/scenes/subghz_scene_radio_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_radio_settings.c @@ -94,7 +94,7 @@ static void subghz_scene_receiver_config_set_timestamp_file_names(VariableItem* variable_item_set_current_value_text(item, timestamp_names_text[index]); - subghz->last_settings->timestamp_file_names = (index == 1); + subghz->last_settings->protocol_file_names = (index == 1); subghz_last_settings_save(subghz->last_settings); } @@ -126,7 +126,7 @@ void subghz_scene_radio_settings_on_enter(void* context) { TIMESTAMP_NAMES_COUNT, subghz_scene_receiver_config_set_timestamp_file_names, subghz); - value_index = subghz->last_settings->timestamp_file_names; + value_index = subghz->last_settings->protocol_file_names; variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, timestamp_names_text[value_index]); diff --git a/applications/main/subghz/scenes/subghz_scene_read_raw.c b/applications/main/subghz/scenes/subghz_scene_read_raw.c index b6ae9f9dc..2727fa347 100644 --- a/applications/main/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_read_raw.c @@ -104,14 +104,13 @@ void subghz_scene_read_raw_on_enter(void* context) { if(subghz_rx_key_state_get(subghz) != SubGhzRxKeyStateBack) { subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE); -#if SUBGHZ_LAST_SETTING_SAVE_PRESET + if(furi_string_empty(file_name)) { subghz_txrx_set_preset_internal( subghz->txrx, subghz->last_settings->frequency, subghz->last_settings->preset_index); } -#endif } subghz_scene_read_raw_update_statusbar(subghz); diff --git a/applications/main/subghz/scenes/subghz_scene_receiver.c b/applications/main/subghz/scenes/subghz_scene_receiver.c index 118a8bc1c..23ac44222 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver.c @@ -164,12 +164,8 @@ void subghz_scene_receiver_on_enter(void* context) { FuriString* item_time = furi_string_alloc(); if(subghz_rx_key_state_get(subghz) == SubGhzRxKeyStateIDLE) { -#if SUBGHZ_LAST_SETTING_SAVE_PRESET subghz_txrx_set_preset_internal( subghz->txrx, subghz->last_settings->frequency, subghz->last_settings->preset_index); -#else - subghz_txrx_set_default_preset(subghz->txrx, subghz->last_settings->frequency); -#endif subghz->filter = subghz->last_settings->filter; subghz_txrx_receiver_set_filter(subghz->txrx, subghz->filter); diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_config.c b/applications/main/subghz/scenes/subghz_scene_receiver_config.c index f98b33b4f..f937e80b9 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_config.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_config.c @@ -301,14 +301,11 @@ static void subghz_scene_receiver_config_var_list_enter_callback(void* context, subghz->view_dispatcher, SubGhzCustomEventSceneSettingLock); } else if(index == SubGhzSettingIndexResetToDefault) { // Reset all values to default state! -#if SUBGHZ_LAST_SETTING_SAVE_PRESET subghz_txrx_set_preset_internal( subghz->txrx, SUBGHZ_LAST_SETTING_DEFAULT_FREQUENCY, SUBGHZ_LAST_SETTING_DEFAULT_PRESET); -#else - subghz_txrx_set_default_preset(subghz->txrx, SUBGHZ_LAST_SETTING_DEFAULT_FREQUENCY); -#endif + SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx); SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx); const char* preset_name = furi_string_get_cstr(preset.name); @@ -333,9 +330,7 @@ static void subghz_scene_receiver_config_var_list_enter_callback(void* context, variable_item_list_set_selected_item(subghz->variable_item_list, default_index); variable_item_list_reset(subghz->variable_item_list); -#ifdef FURI_DEBUG - subghz_last_settings_log(subghz->last_settings); -#endif + subghz_last_settings_save(subghz->last_settings); view_dispatcher_send_custom_event( @@ -556,9 +551,7 @@ void subghz_scene_receiver_config_on_exit(void* context) { SubGhz* subghz = context; variable_item_list_set_selected_item(subghz->variable_item_list, 0); variable_item_list_reset(subghz->variable_item_list); -#ifdef FURI_DEBUG - subghz_last_settings_log(subghz->last_settings); -#endif + subghz_last_settings_save(subghz->last_settings); scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerNoSet); diff --git a/applications/main/subghz/scenes/subghz_scene_save_name.c b/applications/main/subghz/scenes/subghz_scene_save_name.c index 0b2a25959..b94f4430b 100644 --- a/applications/main/subghz/scenes/subghz_scene_save_name.c +++ b/applications/main/subghz/scenes/subghz_scene_save_name.c @@ -32,7 +32,7 @@ void subghz_scene_save_name_on_enter(void* context) { if(decoder_result != 0x0) { if(decoder_result != NULL) { if(strlen(decoder_result->protocol->name) != 0 && - subghz->last_settings->timestamp_file_names) { + subghz->last_settings->protocol_file_names) { if(!scene_manager_has_previous_scene( subghz->scene_manager, SubGhzSceneSetType)) { name_generator_make_auto_datetime( diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c index 8eb7eec99..c4169530d 100644 --- a/applications/main/subghz/subghz.c +++ b/applications/main/subghz/subghz.c @@ -202,16 +202,9 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) { subghz->last_settings = subghz_last_settings_alloc(); size_t preset_count = subghz_setting_get_preset_count(setting); subghz_last_settings_load(subghz->last_settings, preset_count); -#ifdef FURI_DEBUG - subghz_last_settings_log(subghz->last_settings); -#endif if(!alloc_for_tx_only) { -#if SUBGHZ_LAST_SETTING_SAVE_PRESET subghz_txrx_set_preset_internal( subghz->txrx, subghz->last_settings->frequency, subghz->last_settings->preset_index); -#else - subghz_txrx_set_default_preset(subghz->txrx, subghz->last_settings->frequency); -#endif subghz->history = subghz_history_alloc(); } diff --git a/applications/main/subghz/subghz_last_settings.c b/applications/main/subghz/subghz_last_settings.c index 262f60c63..09d5821a5 100644 --- a/applications/main/subghz/subghz_last_settings.c +++ b/applications/main/subghz/subghz_last_settings.c @@ -4,14 +4,14 @@ #define TAG "SubGhzLastSettings" #define SUBGHZ_LAST_SETTING_FILE_TYPE "Flipper SubGhz Last Setting File" -#define SUBGHZ_LAST_SETTING_FILE_VERSION 2 +#define SUBGHZ_LAST_SETTING_FILE_VERSION 3 #define SUBGHZ_LAST_SETTINGS_PATH EXT_PATH("subghz/assets/last_subghz.settings") #define SUBGHZ_LAST_SETTING_FIELD_FREQUENCY "Frequency" #define SUBGHZ_LAST_SETTING_FIELD_PRESET "Preset" // AKA Modulation #define SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_FEEDBACK_LEVEL "FeedbackLevel" #define SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_TRIGGER "FATrigger" -#define SUBGHZ_LAST_SETTING_FIELD_TIMESTAMP_FILE_NAMES "TimestampNames" +#define SUBGHZ_LAST_SETTING_FIELD_PROTOCOL_FILE_NAMES "ProtocolNames" #define SUBGHZ_LAST_SETTING_FIELD_HOPPING_ENABLE "Hopping" #define SUBGHZ_LAST_SETTING_FIELD_IGNORE_FILTER "IgnoreFilter" #define SUBGHZ_LAST_SETTING_FIELD_FILTER "Filter" @@ -31,31 +31,23 @@ void subghz_last_settings_free(SubGhzLastSettings* instance) { void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count) { furi_assert(instance); + // Default values (all others set to 0, if read from file fails these are used) + instance->frequency = SUBGHZ_LAST_SETTING_DEFAULT_FREQUENCY; + instance->preset_index = SUBGHZ_LAST_SETTING_DEFAULT_PRESET; + instance->frequency_analyzer_feedback_level = + SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_FEEDBACK_LEVEL; + instance->frequency_analyzer_trigger = SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_TRIGGER; + // See bin_raw_value in scenes/subghz_scene_receiver_config.c + instance->filter = SubGhzProtocolFlag_Decodable; + instance->rssi = SUBGHZ_RAW_THRESHOLD_MIN; + Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); - uint32_t temp_frequency = 0; - uint32_t temp_frequency_analyzer_feedback_level = 0; - float temp_frequency_analyzer_trigger = 0; - bool temp_timestamp_file_names = false; - bool temp_enable_hopping = false; - bool temp_delete_old_sig = false; - uint32_t temp_ignore_filter = 0; - uint32_t temp_filter = 0; - float temp_rssi = 0; - uint32_t temp_preset = 0; - - bool preset_was_read = false; - bool rssi_was_read = false; - bool filter_was_read = false; - bool ignore_filter_was_read = false; - bool frequency_analyzer_feedback_level_was_read = false; - bool frequency_analyzer_trigger_was_read = false; - FuriString* temp_str = furi_string_alloc(); uint32_t config_version = 0; - if(FSE_OK == storage_sd_status(storage) && SUBGHZ_LAST_SETTINGS_PATH && + if(FSE_OK == storage_sd_status(storage) && flipper_format_file_open_existing(fff_data_file, SUBGHZ_LAST_SETTINGS_PATH)) { do { if(!flipper_format_read_header(fff_data_file, temp_str, &config_version)) break; @@ -64,44 +56,65 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count break; } - preset_was_read = flipper_format_read_uint32( - fff_data_file, SUBGHZ_LAST_SETTING_FIELD_PRESET, (uint32_t*)&temp_preset, 1); - flipper_format_read_uint32( - fff_data_file, SUBGHZ_LAST_SETTING_FIELD_FREQUENCY, (uint32_t*)&temp_frequency, 1); - frequency_analyzer_feedback_level_was_read = flipper_format_read_uint32( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_FEEDBACK_LEVEL, - (uint32_t*)&temp_frequency_analyzer_feedback_level, - 1); - frequency_analyzer_trigger_was_read = flipper_format_read_float( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_TRIGGER, - (float*)&temp_frequency_analyzer_trigger, - 1); - flipper_format_read_bool( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_TIMESTAMP_FILE_NAMES, - (bool*)&temp_timestamp_file_names, - 1); - flipper_format_read_bool( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_HOPPING_ENABLE, - (bool*)&temp_enable_hopping, - 1); - rssi_was_read = flipper_format_read_float( - fff_data_file, SUBGHZ_LAST_SETTING_FIELD_RSSI_THRESHOLD, (float*)&temp_rssi, 1); - ignore_filter_was_read = flipper_format_read_uint32( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_IGNORE_FILTER, - (uint32_t*)&temp_ignore_filter, - 1); - filter_was_read = flipper_format_read_uint32( - fff_data_file, SUBGHZ_LAST_SETTING_FIELD_FILTER, (uint32_t*)&temp_filter, 1); - flipper_format_read_bool( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_DELETE_OLD, - (bool*)&temp_delete_old_sig, - 1); + if(!flipper_format_read_uint32( + fff_data_file, SUBGHZ_LAST_SETTING_FIELD_FREQUENCY, &instance->frequency, 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_uint32( + fff_data_file, SUBGHZ_LAST_SETTING_FIELD_PRESET, &instance->preset_index, 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_uint32( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_FEEDBACK_LEVEL, + &instance->frequency_analyzer_feedback_level, + 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_float( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_TRIGGER, + &instance->frequency_analyzer_trigger, + 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_bool( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_PROTOCOL_FILE_NAMES, + &instance->protocol_file_names, + 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_bool( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_HOPPING_ENABLE, + &instance->enable_hopping, + 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_uint32( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_IGNORE_FILTER, + &instance->ignore_filter, + 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_uint32( + fff_data_file, SUBGHZ_LAST_SETTING_FIELD_FILTER, &instance->filter, 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_float( + fff_data_file, SUBGHZ_LAST_SETTING_FIELD_RSSI_THRESHOLD, &instance->rssi, 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_bool( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_DELETE_OLD, + &instance->delete_old_signals, + 1)) { + flipper_format_rewind(fff_data_file); + } + } while(0); } else { FURI_LOG_E(TAG, "Error open file %s", SUBGHZ_LAST_SETTINGS_PATH); @@ -109,76 +122,22 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count furi_string_free(temp_str); - if(temp_frequency == 0 || !furi_hal_subghz_is_tx_allowed(temp_frequency)) { - FURI_LOG_W(TAG, "Last used frequency not found or can't be used!"); - - instance->frequency = SUBGHZ_LAST_SETTING_DEFAULT_FREQUENCY; - instance->preset_index = SUBGHZ_LAST_SETTING_DEFAULT_PRESET; - instance->frequency_analyzer_feedback_level = - SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_FEEDBACK_LEVEL; - instance->frequency_analyzer_trigger = SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_TRIGGER; - instance->timestamp_file_names = false; - instance->enable_hopping = false; - instance->delete_old_signals = false; - instance->ignore_filter = 0x00; - // See bin_raw_value in applications/main/subghz/scenes/subghz_scene_receiver_config.c - instance->filter = SubGhzProtocolFlag_Decodable; - instance->rssi = SUBGHZ_RAW_THRESHOLD_MIN; - } else { - instance->frequency = temp_frequency; - instance->frequency_analyzer_feedback_level = - frequency_analyzer_feedback_level_was_read ? - temp_frequency_analyzer_feedback_level : - SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_FEEDBACK_LEVEL; - - instance->frequency_analyzer_trigger = frequency_analyzer_trigger_was_read ? - temp_frequency_analyzer_trigger : - SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_TRIGGER; - - if(!preset_was_read) { - FURI_LOG_W(TAG, "Preset was not read. Set default"); - instance->preset_index = SUBGHZ_LAST_SETTING_DEFAULT_PRESET; - } else if(temp_preset > (uint32_t)preset_count - 1) { - FURI_LOG_W( - TAG, - "Last used preset out of range. Preset to set: %ld, Max index: %ld. Set default", - temp_preset, - (uint32_t)preset_count - 1); - instance->preset_index = SUBGHZ_LAST_SETTING_DEFAULT_PRESET; - } else { - instance->preset_index = temp_preset; - } - instance->timestamp_file_names = temp_timestamp_file_names; - - instance->delete_old_signals = temp_delete_old_sig; - - instance->rssi = rssi_was_read ? temp_rssi : SUBGHZ_RAW_THRESHOLD_MIN; - instance->enable_hopping = temp_enable_hopping; - instance->ignore_filter = ignore_filter_was_read ? temp_ignore_filter : 0x00; -#if SUBGHZ_LAST_SETTING_SAVE_BIN_RAW - instance->filter = filter_was_read ? temp_filter : SubGhzProtocolFlag_Decodable; -#else - if(filter_was_read) { - instance->filter = temp_filter != SubGhzProtocolFlag_Decodable ? - SubGhzProtocolFlag_Decodable : - temp_filter; - } else { - instance->filter = SubGhzProtocolFlag_Decodable; - } -#endif - } - flipper_format_file_close(fff_data_file); flipper_format_free(fff_data_file); furi_record_close(RECORD_STORAGE); + + if(instance->frequency == 0 || !furi_hal_subghz_is_tx_allowed(instance->frequency)) { + instance->frequency = SUBGHZ_LAST_SETTING_DEFAULT_FREQUENCY; + } + + if(instance->preset_index > (uint32_t)preset_count - 1) { + instance->preset_index = SUBGHZ_LAST_SETTING_DEFAULT_PRESET; + } } bool subghz_last_settings_save(SubGhzLastSettings* instance) { furi_assert(instance); -#if SUBGHZ_LAST_SETTING_SAVE_BIN_RAW != true - instance->filter = SubGhzProtocolFlag_Decodable; -#endif bool saved = false; Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* file = flipper_format_file_alloc(storage); @@ -195,55 +154,56 @@ bool subghz_last_settings_save(SubGhzLastSettings* instance) { if(!flipper_format_write_header_cstr( file, SUBGHZ_LAST_SETTING_FILE_TYPE, SUBGHZ_LAST_SETTING_FILE_VERSION)) break; - if(!flipper_format_insert_or_update_uint32( - file, SUBGHZ_LAST_SETTING_FIELD_PRESET, &instance->preset_index, 1)) { - break; - } - if(!flipper_format_insert_or_update_uint32( + if(!flipper_format_write_uint32( file, SUBGHZ_LAST_SETTING_FIELD_FREQUENCY, &instance->frequency, 1)) { break; } - if(!flipper_format_insert_or_update_uint32( + if(!flipper_format_write_uint32( + file, SUBGHZ_LAST_SETTING_FIELD_PRESET, &instance->preset_index, 1)) { + break; + } + if(!flipper_format_write_uint32( file, SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_FEEDBACK_LEVEL, &instance->frequency_analyzer_feedback_level, 1)) { break; } - if(!flipper_format_insert_or_update_float( + if(!flipper_format_write_float( file, SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_TRIGGER, &instance->frequency_analyzer_trigger, 1)) { break; } - if(!flipper_format_insert_or_update_bool( + if(!flipper_format_write_bool( file, - SUBGHZ_LAST_SETTING_FIELD_TIMESTAMP_FILE_NAMES, - &instance->timestamp_file_names, + SUBGHZ_LAST_SETTING_FIELD_PROTOCOL_FILE_NAMES, + &instance->protocol_file_names, 1)) { break; } - if(!flipper_format_insert_or_update_bool( + if(!flipper_format_write_bool( file, SUBGHZ_LAST_SETTING_FIELD_HOPPING_ENABLE, &instance->enable_hopping, 1)) { break; } - if(!flipper_format_insert_or_update_float( - file, SUBGHZ_LAST_SETTING_FIELD_RSSI_THRESHOLD, &instance->rssi, 1)) { - break; - } - if(!flipper_format_insert_or_update_uint32( + if(!flipper_format_write_uint32( file, SUBGHZ_LAST_SETTING_FIELD_IGNORE_FILTER, &instance->ignore_filter, 1)) { break; } - if(!flipper_format_insert_or_update_uint32( + if(!flipper_format_write_uint32( file, SUBGHZ_LAST_SETTING_FIELD_FILTER, &instance->filter, 1)) { break; } - if(!flipper_format_insert_or_update_bool( + if(!flipper_format_write_float( + file, SUBGHZ_LAST_SETTING_FIELD_RSSI_THRESHOLD, &instance->rssi, 1)) { + break; + } + if(!flipper_format_write_bool( file, SUBGHZ_LAST_SETTING_FIELD_DELETE_OLD, &instance->delete_old_signals, 1)) { break; } + saved = true; } while(0); @@ -257,42 +217,3 @@ bool subghz_last_settings_save(SubGhzLastSettings* instance) { return saved; } - -const char* LOG_ON = "ON"; -const char* LOG_OFF = "OFF"; - -static inline const char* - subghz_last_settings_log_filter_get_index(uint32_t filter, uint32_t flag) { - return READ_BIT(filter, flag) > 0 ? LOG_ON : LOG_OFF; -} - -static inline const char* bool_to_char(bool value) { - return value ? LOG_ON : LOG_OFF; -} - -void subghz_last_settings_log(SubGhzLastSettings* instance) { - furi_assert(instance); - - FURI_LOG_I( - TAG, - "Frequency: %03ld.%02ld, FeedbackLevel: %ld, FATrigger: %.2f, TimestampNames: %s,\n" - "Hopping: %s,\nPreset: %ld, RSSI: %.2f, " - "Starline: %s, Cars: %s, Magellan: %s, NiceFloR-S: %s, BinRAW: %s", - instance->frequency / 1000000 % 1000, - instance->frequency / 10000 % 100, - instance->frequency_analyzer_feedback_level, - (double)instance->frequency_analyzer_trigger, - bool_to_char(instance->timestamp_file_names), - bool_to_char(instance->enable_hopping), - instance->preset_index, - (double)instance->rssi, - subghz_last_settings_log_filter_get_index( - instance->ignore_filter, SubGhzProtocolFlag_StarLine), - subghz_last_settings_log_filter_get_index( - instance->ignore_filter, SubGhzProtocolFlag_AutoAlarms), - subghz_last_settings_log_filter_get_index( - instance->ignore_filter, SubGhzProtocolFlag_Magellan), - subghz_last_settings_log_filter_get_index( - instance->ignore_filter, SubGhzProtocolFlag_NiceFlorS), - subghz_last_settings_log_filter_get_index(instance->filter, SubGhzProtocolFlag_BinRAW)); -} diff --git a/applications/main/subghz/subghz_last_settings.h b/applications/main/subghz/subghz_last_settings.h index 084e76738..2b18861ff 100644 --- a/applications/main/subghz/subghz_last_settings.h +++ b/applications/main/subghz/subghz_last_settings.h @@ -7,8 +7,6 @@ #include #define SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_TRIGGER (-93.0f) -#define SUBGHZ_LAST_SETTING_SAVE_BIN_RAW true -#define SUBGHZ_LAST_SETTING_SAVE_PRESET true // 1 = "AM650" // "AM270", "AM650", "FM238", "FM476", #define SUBGHZ_LAST_SETTING_DEFAULT_PRESET 1 @@ -20,7 +18,7 @@ typedef struct { uint32_t preset_index; // AKA Modulation uint32_t frequency_analyzer_feedback_level; float frequency_analyzer_trigger; - bool timestamp_file_names; + bool protocol_file_names; bool enable_hopping; uint32_t ignore_filter; uint32_t filter; @@ -35,5 +33,3 @@ void subghz_last_settings_free(SubGhzLastSettings* instance); void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count); bool subghz_last_settings_save(SubGhzLastSettings* instance); - -void subghz_last_settings_log(SubGhzLastSettings* instance); diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 3c1830ffa..123b18bdd 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -32,12 +32,14 @@ static void desktop_loader_callback(const void* message, void* context) { Desktop* desktop = context; const LoaderEvent* event = message; - if(event->type == LoaderEventTypeApplicationStarted) { + if(event->type == LoaderEventTypeApplicationBeforeLoad) { desktop->animation_lock = api_lock_alloc_locked(); view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalBeforeAppStarted); api_lock_wait_unlock_and_free(desktop->animation_lock); desktop->animation_lock = NULL; - } else if(event->type == LoaderEventTypeApplicationStopped) { + } else if( + event->type == LoaderEventTypeApplicationLoadFailed || + event->type == LoaderEventTypeApplicationStopped) { view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalAfterAppFinished); } } diff --git a/applications/services/desktop/scenes/desktop_scene_main.c b/applications/services/desktop/scenes/desktop_scene_main.c index 877ef5307..74d34d113 100644 --- a/applications/services/desktop/scenes/desktop_scene_main.c +++ b/applications/services/desktop/scenes/desktop_scene_main.c @@ -152,7 +152,7 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { break; case DesktopMainEventOpenPowerOff: { - loader_start(desktop->loader, "Power", "off", NULL); + loader_start_detached_with_gui_error(desktop->loader, "Power", "off"); consumed = true; break; } diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index c701041c3..eb2dc3aec 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -47,34 +47,16 @@ LoaderStatus return result.value; } -LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const char* args) { - furi_check(loader); - furi_check(name); - - FuriString* error_message = furi_string_alloc(); - LoaderStatus status = loader_start(loader, name, args, error_message); - - if(status == LoaderStatusErrorUnknownApp && - loader_find_external_application_by_name(name) != NULL) { - // Special case for external apps - DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); - DialogMessage* message = dialog_message_alloc(); - dialog_message_set_header(message, "Update needed", 64, 3, AlignCenter, AlignTop); - dialog_message_set_buttons(message, NULL, NULL, NULL); - dialog_message_set_icon(message, &I_WarningDolphinFlip_45x42, 83, 22); - dialog_message_set_text( - message, "Update firmware\nto run this app", 3, 26, AlignLeft, AlignTop); - dialog_message_show(dialogs, message); - dialog_message_free(message); - furi_record_close(RECORD_DIALOGS); - } else if(status == LoaderStatusErrorUnknownApp || status == LoaderStatusErrorInternal) { - // TODO FL-3522: we have many places where we can emit a double start, ex: desktop, menu - // so i prefer to not show LoaderStatusErrorAppStarted error message for now +static void loader_show_gui_error(LoaderStatus status, FuriString* error_message) { + // TODO FL-3522: we have many places where we can emit a double start, ex: desktop, menu + // so i prefer to not show LoaderStatusErrorAppStarted error message for now + if(status == LoaderStatusErrorUnknownApp || status == LoaderStatusErrorInternal) { DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); DialogMessage* message = dialog_message_alloc(); dialog_message_set_header(message, "Error", 64, 0, AlignCenter, AlignTop); dialog_message_set_buttons(message, NULL, NULL, NULL); + furi_string_replace(error_message, ":", "\n"); furi_string_replace(error_message, "/ext/apps/", ""); furi_string_replace(error_message, ", ", "\n"); furi_string_replace(error_message, ": ", "\n"); @@ -86,11 +68,31 @@ LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const dialog_message_free(message); furi_record_close(RECORD_DIALOGS); } +} +LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const char* args) { + furi_check(loader); + furi_check(name); + + FuriString* error_message = furi_string_alloc(); + LoaderStatus status = loader_start(loader, name, args, error_message); + loader_show_gui_error(status, error_message); furi_string_free(error_message); return status; } +void loader_start_detached_with_gui_error(Loader* loader, const char* name, const char* args) { + furi_check(loader); + furi_check(name); + + LoaderMessage message; + + message.type = LoaderMessageTypeStartByNameDetachedWithGuiError; + message.start.name = name ? strdup(name) : NULL; + message.start.args = args ? strdup(args) : NULL; + furi_message_queue_put(loader->queue, &message, FuriWaitForever); +} + bool loader_lock(Loader* loader) { furi_check(loader); @@ -256,6 +258,9 @@ static void loader_start_internal_app( const FlipperInternalApplication* app, const char* args) { FURI_LOG_I(TAG, "Starting %s", app->name); + LoaderEvent event; + event.type = LoaderEventTypeApplicationBeforeLoad; + furi_pubsub_publish(loader->pubsub, &event); // store args furi_assert(loader->app.args == NULL); @@ -311,6 +316,9 @@ static LoaderStatus loader_start_external_app( FuriString* error_message, bool ignore_mismatch) { LoaderStatus status = loader_make_success_status(error_message); + LoaderEvent event; + event.type = LoaderEventTypeApplicationBeforeLoad; + furi_pubsub_publish(loader->pubsub, &event); do { loader->app.fap = flipper_application_alloc(storage, firmware_api_interface); @@ -390,6 +398,8 @@ static LoaderStatus loader_start_external_app( if(status != LoaderStatusOk) { flipper_application_free(loader->app.fap); loader->app.fap = NULL; + event.type = LoaderEventTypeApplicationLoadFailed; + furi_pubsub_publish(loader->pubsub, &event); } return status; @@ -567,6 +577,16 @@ int32_t loader_srv(void* p) { loader, message.start.name, message.start.args, message.start.error_message); api_lock_unlock(message.api_lock); break; + case LoaderMessageTypeStartByNameDetachedWithGuiError: { + FuriString* error_message = furi_string_alloc(); + LoaderStatus status = loader_do_start_by_name( + loader, message.start.name, message.start.args, error_message); + loader_show_gui_error(status, error_message); + if(message.start.name) free((void*)message.start.name); + if(message.start.args) free((void*)message.start.args); + furi_string_free(error_message); + break; + } case LoaderMessageTypeShowMenu: loader_do_menu_show(loader); break; diff --git a/applications/services/loader/loader.h b/applications/services/loader/loader.h index 63da7d3ca..36722ae10 100644 --- a/applications/services/loader/loader.h +++ b/applications/services/loader/loader.h @@ -20,6 +20,8 @@ typedef enum { } LoaderStatus; typedef enum { + LoaderEventTypeApplicationBeforeLoad, + LoaderEventTypeApplicationLoadFailed, LoaderEventTypeApplicationStarted, LoaderEventTypeApplicationStopped } LoaderEventType; @@ -48,6 +50,14 @@ LoaderStatus */ LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const char* args); +/** + * @brief Start application detached with GUI error message + * @param[in] instance loader instance + * @param[in] name application name + * @param[in] args application arguments + */ +void loader_start_detached_with_gui_error(Loader* loader, const char* name, const char* args); + /** * @brief Lock application start * @param[in] instance loader instance diff --git a/applications/services/loader/loader_i.h b/applications/services/loader/loader_i.h index 688b8fb66..daa5484d3 100644 --- a/applications/services/loader/loader_i.h +++ b/applications/services/loader/loader_i.h @@ -30,6 +30,7 @@ typedef enum { LoaderMessageTypeLock, LoaderMessageTypeUnlock, LoaderMessageTypeIsLocked, + LoaderMessageTypeStartByNameDetachedWithGuiError, } LoaderMessageType; typedef struct { diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 9fe9964f0..1a209663b 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -2172,6 +2172,7 @@ Function,+,loader_is_locked,_Bool,Loader* Function,+,loader_lock,_Bool,Loader* Function,+,loader_show_menu,void,Loader* Function,+,loader_start,LoaderStatus,"Loader*, const char*, const char*, FuriString*" +Function,+,loader_start_detached_with_gui_error,void,"Loader*, const char*, const char*" Function,+,loader_start_with_gui_error,LoaderStatus,"Loader*, const char*, const char*" Function,+,loader_unlock,void,Loader* Function,+,loading_alloc,Loading*, From 7787845479c083cbf895a994b2a0660e90aa48c3 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 5 Apr 2024 06:41:31 +0300 Subject: [PATCH 013/129] cleanup unused debug, set proper log levels --- .../lfrfid/scenes/lfrfid_scene_save_name.c | 2 +- .../nfc/plugins/supported_cards/all_in_one.c | 10 +++---- .../plugins/supported_cards/social_moscow.c | 4 +-- .../subghz/views/subghz_frequency_analyzer.c | 29 ++----------------- 4 files changed, 11 insertions(+), 34 deletions(-) diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_save_name.c b/applications/main/lfrfid/scenes/lfrfid_scene_save_name.c index c6845b44a..b8747a9c1 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_save_name.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_save_name.c @@ -30,7 +30,7 @@ void lfrfid_scene_save_name_on_enter(void* context) { LFRFID_KEY_NAME_SIZE, key_name_is_empty); - FURI_LOG_I("", "%s %s", furi_string_get_cstr(folder_path), app->text_store); + FURI_LOG_D("", "%s %s", furi_string_get_cstr(folder_path), app->text_store); ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( furi_string_get_cstr(folder_path), diff --git a/applications/main/nfc/plugins/supported_cards/all_in_one.c b/applications/main/nfc/plugins/supported_cards/all_in_one.c index 9e20b92d1..f85d7ad2e 100644 --- a/applications/main/nfc/plugins/supported_cards/all_in_one.c +++ b/applications/main/nfc/plugins/supported_cards/all_in_one.c @@ -20,8 +20,8 @@ static AllInOneLayoutType all_in_one_get_layout(const MfUltralightData* data) { const uint8_t layout_byte = data->page[5].data[2]; const uint8_t layout_half_byte = data->page[5].data[2] & 0x0F; - FURI_LOG_I(TAG, "Layout byte: %02x", layout_byte); - FURI_LOG_I(TAG, "Layout half-byte: %02x", layout_half_byte); + FURI_LOG_D(TAG, "Layout byte: %02x", layout_byte); + FURI_LOG_D(TAG, "Layout half-byte: %02x", layout_half_byte); switch(layout_half_byte) { // If it is A, the layout type is a type A layout @@ -32,7 +32,7 @@ static AllInOneLayoutType all_in_one_get_layout(const MfUltralightData* data) { case 0x02: return AllInOneLayoutType2; default: - FURI_LOG_I(TAG, "Unknown layout type: %d", layout_half_byte); + FURI_LOG_E(TAG, "Unknown layout type: %d", layout_half_byte); return AllInOneLayoutTypeUnknown; } } @@ -47,7 +47,7 @@ static bool all_in_one_parse(const NfcDevice* device, FuriString* parsed_data) { do { if(data->page[4].data[0] != 0x45 || data->page[4].data[1] != 0xD9) { - FURI_LOG_I(TAG, "Pass not verified"); + FURI_LOG_E(TAG, "Pass not verified"); break; } @@ -63,7 +63,7 @@ static bool all_in_one_parse(const NfcDevice* device, FuriString* parsed_data) { // If the layout is D, the ride count is stored in the second byte of page 9 ride_count = data->page[9].data[1]; } else { - FURI_LOG_I(TAG, "Unknown layout: %d", layout_type); + FURI_LOG_E(TAG, "Unknown layout: %d", layout_type); ride_count = 137; } diff --git a/applications/main/nfc/plugins/supported_cards/social_moscow.c b/applications/main/nfc/plugins/supported_cards/social_moscow.c index 805c77732..55ca6a1ec 100644 --- a/applications/main/nfc/plugins/supported_cards/social_moscow.c +++ b/applications/main/nfc/plugins/supported_cards/social_moscow.c @@ -84,7 +84,7 @@ void from_minutes_to_datetime(uint32_t minutes, DateTime* datetime, uint16_t sta bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { uint16_t transport_departament = bit_lib_get_bits_16(block->data, 0, 10); - FURI_LOG_I(TAG, "Transport departament: %x", transport_departament); + FURI_LOG_D(TAG, "Transport departament: %x", transport_departament); uint16_t layout_type = bit_lib_get_bits_16(block->data, 52, 4); if(layout_type == 0xE) { @@ -93,7 +93,7 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { layout_type = bit_lib_get_bits_16(block->data, 52, 14); } - FURI_LOG_I(TAG, "Layout type %x", layout_type); + FURI_LOG_D(TAG, "Layout type %x", layout_type); uint16_t card_view = 0; uint16_t card_type = 0; diff --git a/applications/main/subghz/views/subghz_frequency_analyzer.c b/applications/main/subghz/views/subghz_frequency_analyzer.c index bc472f233..f16a38d3e 100644 --- a/applications/main/subghz/views/subghz_frequency_analyzer.c +++ b/applications/main/subghz/views/subghz_frequency_analyzer.c @@ -228,9 +228,7 @@ uint32_t subghz_frequency_find_correct(uint32_t input) { uint32_t prev_freq = 0; uint32_t current = 0; uint32_t result = 0; -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "input: %ld", input); -#endif + for(size_t i = 0; i < sizeof(subghz_frequency_list); i++) { current = subghz_frequency_list[i]; if(current == input) { @@ -281,7 +279,7 @@ bool subghz_frequency_analyzer_input(InputEvent* event, void* context) { break; } subghz_frequency_analyzer_worker_set_trigger_level(instance->worker, trigger_level); - FURI_LOG_I(TAG, "trigger = %.1f", (double)trigger_level); + FURI_LOG_D(TAG, "trigger = %.1f", (double)trigger_level); need_redraw = true; } else if(event->type == InputTypePress && event->key == InputKeyUp) { if(instance->feedback_level == 0) { @@ -289,9 +287,7 @@ bool subghz_frequency_analyzer_input(InputEvent* event, void* context) { } else { instance->feedback_level--; } -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "feedback_level = %d", instance->feedback_level); -#endif + need_redraw = true; } else if( ((event->type == InputTypePress) || (event->type == InputTypeRepeat)) && @@ -324,13 +320,6 @@ bool subghz_frequency_analyzer_input(InputEvent* event, void* context) { } if(frequency_candidate > 0 && frequency_candidate != model->frequency_to_save) { -#ifdef FURI_DEBUG - FURI_LOG_D( - TAG, - "frequency_to_save: %ld, candidate: %ld", - model->frequency_to_save, - frequency_candidate); -#endif model->frequency_to_save = frequency_candidate; updated = true; } @@ -372,24 +361,12 @@ bool subghz_frequency_analyzer_input(InputEvent* event, void* context) { }, true); -#ifdef FURI_DEBUG - FURI_LOG_I( - TAG, - "updated: %d, long: %d, type: %d", - updated, - (event->type == InputTypeLong), - event->type); -#endif - if(updated) { instance->callback(SubGhzCustomEventViewFreqAnalOkShort, instance->context); } // First device receive short, then when user release button we get long if(event->type == InputTypeLong && frequency_to_save > 0) { -#ifdef FURI_DEBUG - FURI_LOG_I(TAG, "Long press!"); -#endif // Stop worker if(subghz_frequency_analyzer_worker_is_running(instance->worker)) { subghz_frequency_analyzer_worker_stop(instance->worker); From 22cf19dcef35011b321475507325842f4ef3ff3d Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 5 Apr 2024 06:42:05 +0300 Subject: [PATCH 014/129] format --- applications/drivers/subghz/cc1101_ext/cc1101_ext.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index f109b3cf9..56273d63e 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -181,7 +181,7 @@ static bool subghz_device_cc1101_ext_check_init(void) { } furi_hal_gpio_init( subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - + // Reset GDO2 (!TX/RX) to floating state cc1101_status = cc1101_write_reg( subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHighImpedance); @@ -189,7 +189,7 @@ static bool subghz_device_cc1101_ext_check_init(void) { //timeout or error break; } - + // Go to sleep cc1101_status = cc1101_shutdown(subghz_device_cc1101_ext->spi_bus_handle); if(cc1101_status.CHIP_RDYn != 0) { From ac628bfa2746f572df5e588ff472c09ab56fc125 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 5 Apr 2024 04:56:58 +0100 Subject: [PATCH 015/129] Ext power amp is always true now --- applications/drivers/subghz/cc1101_ext/cc1101_ext.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index 19dcf04eb..560a132d3 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -238,10 +238,7 @@ bool subghz_device_cc1101_ext_alloc(SubGhzDeviceConf* conf) { &furi_hal_spi_bus_handle_external_extra); // this is needed if multiple SPI devices are connected to the same bus but with different CS pins - if(momentum_settings.spi_cc1101_handle == SpiDefault && !furi_hal_subghz_get_ext_power_amp()) { - furi_hal_gpio_init_simple(&gpio_ext_pc3, GpioModeOutputPushPull); - furi_hal_gpio_write(&gpio_ext_pc3, true); - } else if(momentum_settings.spi_cc1101_handle == SpiExtra) { + if(momentum_settings.spi_cc1101_handle == SpiExtra) { furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeOutputPushPull); furi_hal_gpio_write(&gpio_ext_pa4, true); } From d4cd06816cd7b1b2dee1ebef8ea8654ff778a50b Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 5 Apr 2024 05:33:43 +0100 Subject: [PATCH 016/129] JS: Add badusb layout support --- .../examples/apps/Scripts/badusb_demo.js | 8 ++- .../system/js_app/modules/js_badusb.c | 50 ++++++++++++++----- 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/applications/system/js_app/examples/apps/Scripts/badusb_demo.js b/applications/system/js_app/examples/apps/Scripts/badusb_demo.js index 860502023..be94a64d2 100644 --- a/applications/system/js_app/examples/apps/Scripts/badusb_demo.js +++ b/applications/system/js_app/examples/apps/Scripts/badusb_demo.js @@ -3,7 +3,13 @@ let notify = require("notification"); let flipper = require("flipper"); let dialog = require("dialog"); -badusb.setup({ vid: 0xAAAA, pid: 0xBBBB, mfr_name: "Flipper", prod_name: "Zero" }); +badusb.setup({ + vid: 0xAAAA, + pid: 0xBBBB, + mfr_name: "Flipper", + prod_name: "Zero", + layout_path: "/ext/badusb/assets/layouts/en-US.kl" +}); dialog.message("BadUSB demo", "Press OK to start"); if (badusb.isConnected()) { diff --git a/applications/system/js_app/modules/js_badusb.c b/applications/system/js_app/modules/js_badusb.c index a380141e8..1722f6672 100644 --- a/applications/system/js_app/modules/js_badusb.c +++ b/applications/system/js_app/modules/js_badusb.c @@ -2,8 +2,11 @@ #include "../js_modules.h" #include +#define ASCII_TO_KEY(layout, x) (((uint8_t)x < 128) ? (layout[(uint8_t)x]) : HID_KEYBOARD_NONE) + typedef struct { FuriHalUsbHidConfig* hid_cfg; + uint16_t layout[128]; FuriHalUsbInterface* usb_if_prev; uint8_t key_hold_cnt; } JsBadusbInst; @@ -88,7 +91,11 @@ static void js_badusb_quit_free(JsBadusbInst* badusb) { } } -static bool setup_parse_params(struct mjs* mjs, mjs_val_t arg, FuriHalUsbHidConfig* hid_cfg) { +static bool setup_parse_params( + JsBadusbInst* badusb, + struct mjs* mjs, + mjs_val_t arg, + FuriHalUsbHidConfig* hid_cfg) { if(!mjs_is_object(arg)) { return false; } @@ -96,6 +103,7 @@ static bool setup_parse_params(struct mjs* mjs, mjs_val_t arg, FuriHalUsbHidConf mjs_val_t pid_obj = mjs_get(mjs, arg, "pid", ~0); mjs_val_t mfr_obj = mjs_get(mjs, arg, "mfr_name", ~0); mjs_val_t prod_obj = mjs_get(mjs, arg, "prod_name", ~0); + mjs_val_t layout_obj = mjs_get(mjs, arg, "layout_path", ~0); if(mjs_is_number(vid_obj) && mjs_is_number(pid_obj)) { hid_cfg->vid = mjs_get_int32(mjs, vid_obj); @@ -122,6 +130,22 @@ static bool setup_parse_params(struct mjs* mjs, mjs_val_t arg, FuriHalUsbHidConf strlcpy(hid_cfg->product, str_temp, sizeof(hid_cfg->product)); } + if(mjs_is_string(layout_obj)) { + size_t str_len = 0; + const char* str_temp = mjs_get_string(mjs, &layout_obj, &str_len); + File* file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + size_t size = sizeof(badusb->layout); + + if((str_len == 0) || (str_temp == NULL) || + !storage_file_open(file, str_temp, FSAM_READ, FSOM_OPEN_EXISTING) || + storage_file_read(file, badusb->layout, size) != size) { + memcpy(badusb->layout, hid_asciimap, MIN(sizeof(hid_asciimap), size)); + } + + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + } + return true; } @@ -144,7 +168,7 @@ static void js_badusb_setup(struct mjs* mjs) { } else if(num_args == 1) { badusb->hid_cfg = malloc(sizeof(FuriHalUsbHidConfig)); // Parse argument object - args_correct = setup_parse_params(mjs, mjs_arg(mjs, 0), badusb->hid_cfg); + args_correct = setup_parse_params(badusb, mjs, mjs_arg(mjs, 0), badusb->hid_cfg); } if(!args_correct) { mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); @@ -191,9 +215,9 @@ static void js_badusb_is_connected(struct mjs* mjs) { mjs_return(mjs, mjs_mk_boolean(mjs, is_connected)); } -uint16_t get_keycode_by_name(const char* key_name, size_t name_len) { +uint16_t get_keycode_by_name(JsBadusbInst* badusb, const char* key_name, size_t name_len) { if(name_len == 1) { // Single char - return (HID_ASCII_TO_KEY(key_name[0])); + return (ASCII_TO_KEY(badusb->layout, key_name[0])); } for(size_t i = 0; i < COUNT_OF(key_codes); i++) { @@ -210,7 +234,7 @@ uint16_t get_keycode_by_name(const char* key_name, size_t name_len) { return HID_KEYBOARD_NONE; } -static bool parse_keycode(struct mjs* mjs, size_t nargs, uint16_t* keycode) { +static bool parse_keycode(JsBadusbInst* badusb, struct mjs* mjs, size_t nargs, uint16_t* keycode) { uint16_t key_tmp = 0; for(size_t i = 0; i < nargs; i++) { mjs_val_t arg = mjs_arg(mjs, i); @@ -221,7 +245,7 @@ static bool parse_keycode(struct mjs* mjs, size_t nargs, uint16_t* keycode) { // String error return false; } - uint16_t str_key = get_keycode_by_name(key_name, name_len); + uint16_t str_key = get_keycode_by_name(badusb, key_name, name_len); if(str_key == HID_KEYBOARD_NONE) { // Unknown key code return false; @@ -259,7 +283,7 @@ static void js_badusb_press(struct mjs* mjs) { uint16_t keycode = HID_KEYBOARD_NONE; size_t num_args = mjs_nargs(mjs); if(num_args > 0) { - args_correct = parse_keycode(mjs, num_args, &keycode); + args_correct = parse_keycode(badusb, mjs, num_args, &keycode); } if(!args_correct) { mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); @@ -285,7 +309,7 @@ static void js_badusb_hold(struct mjs* mjs) { uint16_t keycode = HID_KEYBOARD_NONE; size_t num_args = mjs_nargs(mjs); if(num_args > 0) { - args_correct = parse_keycode(mjs, num_args, &keycode); + args_correct = parse_keycode(badusb, mjs, num_args, &keycode); } if(!args_correct) { mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); @@ -324,7 +348,7 @@ static void js_badusb_release(struct mjs* mjs) { mjs_return(mjs, MJS_UNDEFINED); return; } else { - args_correct = parse_keycode(mjs, num_args, &keycode); + args_correct = parse_keycode(badusb, mjs, num_args, &keycode); } if(!args_correct) { mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); @@ -347,14 +371,14 @@ static void ducky_numlock_on() { } // Simulate pressing a character using ALT+Numpad ASCII code -static void ducky_altchar(const char* ascii_code) { +static void ducky_altchar(JsBadusbInst* badusb, const char* ascii_code) { // Hold the ALT key furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT); // Press the corresponding numpad key for each digit of the ASCII code for(size_t i = 0; ascii_code[i] != '\0'; i++) { char digitChar[5] = {'N', 'U', 'M', ascii_code[i], '\0'}; // Construct the numpad key name - uint16_t numpad_keycode = get_keycode_by_name(digitChar, strlen(digitChar)); + uint16_t numpad_keycode = get_keycode_by_name(badusb, digitChar, strlen(digitChar)); if(numpad_keycode == HID_KEYBOARD_NONE) { continue; // Skip if keycode not found } @@ -420,9 +444,9 @@ static void badusb_print(struct mjs* mjs, bool ln, bool alt) { // Convert character to ascii numeric value char ascii_str[4]; snprintf(ascii_str, sizeof(ascii_str), "%u", (uint8_t)text_str[i]); - ducky_altchar(ascii_str); + ducky_altchar(badusb, ascii_str); } else { - uint16_t keycode = HID_ASCII_TO_KEY(text_str[i]); + uint16_t keycode = ASCII_TO_KEY(badusb->layout, text_str[i]); furi_hal_hid_kb_press(keycode); furi_hal_hid_kb_release(keycode); } From 6e4d32b941c23a3f4a1cf1c3e223c63018d51e5b Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 5 Apr 2024 08:01:53 +0300 Subject: [PATCH 017/129] js add badusb layout support by Willy-JL --- .../examples/apps/Scripts/badusb_demo.js | 8 ++- .../system/js_app/modules/js_badusb.c | 50 ++++++++++++++----- 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/applications/system/js_app/examples/apps/Scripts/badusb_demo.js b/applications/system/js_app/examples/apps/Scripts/badusb_demo.js index 860502023..be94a64d2 100644 --- a/applications/system/js_app/examples/apps/Scripts/badusb_demo.js +++ b/applications/system/js_app/examples/apps/Scripts/badusb_demo.js @@ -3,7 +3,13 @@ let notify = require("notification"); let flipper = require("flipper"); let dialog = require("dialog"); -badusb.setup({ vid: 0xAAAA, pid: 0xBBBB, mfr_name: "Flipper", prod_name: "Zero" }); +badusb.setup({ + vid: 0xAAAA, + pid: 0xBBBB, + mfr_name: "Flipper", + prod_name: "Zero", + layout_path: "/ext/badusb/assets/layouts/en-US.kl" +}); dialog.message("BadUSB demo", "Press OK to start"); if (badusb.isConnected()) { diff --git a/applications/system/js_app/modules/js_badusb.c b/applications/system/js_app/modules/js_badusb.c index a380141e8..1722f6672 100644 --- a/applications/system/js_app/modules/js_badusb.c +++ b/applications/system/js_app/modules/js_badusb.c @@ -2,8 +2,11 @@ #include "../js_modules.h" #include +#define ASCII_TO_KEY(layout, x) (((uint8_t)x < 128) ? (layout[(uint8_t)x]) : HID_KEYBOARD_NONE) + typedef struct { FuriHalUsbHidConfig* hid_cfg; + uint16_t layout[128]; FuriHalUsbInterface* usb_if_prev; uint8_t key_hold_cnt; } JsBadusbInst; @@ -88,7 +91,11 @@ static void js_badusb_quit_free(JsBadusbInst* badusb) { } } -static bool setup_parse_params(struct mjs* mjs, mjs_val_t arg, FuriHalUsbHidConfig* hid_cfg) { +static bool setup_parse_params( + JsBadusbInst* badusb, + struct mjs* mjs, + mjs_val_t arg, + FuriHalUsbHidConfig* hid_cfg) { if(!mjs_is_object(arg)) { return false; } @@ -96,6 +103,7 @@ static bool setup_parse_params(struct mjs* mjs, mjs_val_t arg, FuriHalUsbHidConf mjs_val_t pid_obj = mjs_get(mjs, arg, "pid", ~0); mjs_val_t mfr_obj = mjs_get(mjs, arg, "mfr_name", ~0); mjs_val_t prod_obj = mjs_get(mjs, arg, "prod_name", ~0); + mjs_val_t layout_obj = mjs_get(mjs, arg, "layout_path", ~0); if(mjs_is_number(vid_obj) && mjs_is_number(pid_obj)) { hid_cfg->vid = mjs_get_int32(mjs, vid_obj); @@ -122,6 +130,22 @@ static bool setup_parse_params(struct mjs* mjs, mjs_val_t arg, FuriHalUsbHidConf strlcpy(hid_cfg->product, str_temp, sizeof(hid_cfg->product)); } + if(mjs_is_string(layout_obj)) { + size_t str_len = 0; + const char* str_temp = mjs_get_string(mjs, &layout_obj, &str_len); + File* file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + size_t size = sizeof(badusb->layout); + + if((str_len == 0) || (str_temp == NULL) || + !storage_file_open(file, str_temp, FSAM_READ, FSOM_OPEN_EXISTING) || + storage_file_read(file, badusb->layout, size) != size) { + memcpy(badusb->layout, hid_asciimap, MIN(sizeof(hid_asciimap), size)); + } + + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + } + return true; } @@ -144,7 +168,7 @@ static void js_badusb_setup(struct mjs* mjs) { } else if(num_args == 1) { badusb->hid_cfg = malloc(sizeof(FuriHalUsbHidConfig)); // Parse argument object - args_correct = setup_parse_params(mjs, mjs_arg(mjs, 0), badusb->hid_cfg); + args_correct = setup_parse_params(badusb, mjs, mjs_arg(mjs, 0), badusb->hid_cfg); } if(!args_correct) { mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); @@ -191,9 +215,9 @@ static void js_badusb_is_connected(struct mjs* mjs) { mjs_return(mjs, mjs_mk_boolean(mjs, is_connected)); } -uint16_t get_keycode_by_name(const char* key_name, size_t name_len) { +uint16_t get_keycode_by_name(JsBadusbInst* badusb, const char* key_name, size_t name_len) { if(name_len == 1) { // Single char - return (HID_ASCII_TO_KEY(key_name[0])); + return (ASCII_TO_KEY(badusb->layout, key_name[0])); } for(size_t i = 0; i < COUNT_OF(key_codes); i++) { @@ -210,7 +234,7 @@ uint16_t get_keycode_by_name(const char* key_name, size_t name_len) { return HID_KEYBOARD_NONE; } -static bool parse_keycode(struct mjs* mjs, size_t nargs, uint16_t* keycode) { +static bool parse_keycode(JsBadusbInst* badusb, struct mjs* mjs, size_t nargs, uint16_t* keycode) { uint16_t key_tmp = 0; for(size_t i = 0; i < nargs; i++) { mjs_val_t arg = mjs_arg(mjs, i); @@ -221,7 +245,7 @@ static bool parse_keycode(struct mjs* mjs, size_t nargs, uint16_t* keycode) { // String error return false; } - uint16_t str_key = get_keycode_by_name(key_name, name_len); + uint16_t str_key = get_keycode_by_name(badusb, key_name, name_len); if(str_key == HID_KEYBOARD_NONE) { // Unknown key code return false; @@ -259,7 +283,7 @@ static void js_badusb_press(struct mjs* mjs) { uint16_t keycode = HID_KEYBOARD_NONE; size_t num_args = mjs_nargs(mjs); if(num_args > 0) { - args_correct = parse_keycode(mjs, num_args, &keycode); + args_correct = parse_keycode(badusb, mjs, num_args, &keycode); } if(!args_correct) { mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); @@ -285,7 +309,7 @@ static void js_badusb_hold(struct mjs* mjs) { uint16_t keycode = HID_KEYBOARD_NONE; size_t num_args = mjs_nargs(mjs); if(num_args > 0) { - args_correct = parse_keycode(mjs, num_args, &keycode); + args_correct = parse_keycode(badusb, mjs, num_args, &keycode); } if(!args_correct) { mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); @@ -324,7 +348,7 @@ static void js_badusb_release(struct mjs* mjs) { mjs_return(mjs, MJS_UNDEFINED); return; } else { - args_correct = parse_keycode(mjs, num_args, &keycode); + args_correct = parse_keycode(badusb, mjs, num_args, &keycode); } if(!args_correct) { mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); @@ -347,14 +371,14 @@ static void ducky_numlock_on() { } // Simulate pressing a character using ALT+Numpad ASCII code -static void ducky_altchar(const char* ascii_code) { +static void ducky_altchar(JsBadusbInst* badusb, const char* ascii_code) { // Hold the ALT key furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT); // Press the corresponding numpad key for each digit of the ASCII code for(size_t i = 0; ascii_code[i] != '\0'; i++) { char digitChar[5] = {'N', 'U', 'M', ascii_code[i], '\0'}; // Construct the numpad key name - uint16_t numpad_keycode = get_keycode_by_name(digitChar, strlen(digitChar)); + uint16_t numpad_keycode = get_keycode_by_name(badusb, digitChar, strlen(digitChar)); if(numpad_keycode == HID_KEYBOARD_NONE) { continue; // Skip if keycode not found } @@ -420,9 +444,9 @@ static void badusb_print(struct mjs* mjs, bool ln, bool alt) { // Convert character to ascii numeric value char ascii_str[4]; snprintf(ascii_str, sizeof(ascii_str), "%u", (uint8_t)text_str[i]); - ducky_altchar(ascii_str); + ducky_altchar(badusb, ascii_str); } else { - uint16_t keycode = HID_ASCII_TO_KEY(text_str[i]); + uint16_t keycode = ASCII_TO_KEY(badusb->layout, text_str[i]); furi_hal_hid_kb_press(keycode); furi_hal_hid_kb_release(keycode); } From 1e324a73140f4482acc39a93a0a95da439ed71a0 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 5 Apr 2024 08:02:25 +0300 Subject: [PATCH 018/129] fix favorites lockup --- applications/services/desktop/scenes/desktop_scene_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/services/desktop/scenes/desktop_scene_main.c b/applications/services/desktop/scenes/desktop_scene_main.c index 74d34d113..0c083a5d8 100644 --- a/applications/services/desktop/scenes/desktop_scene_main.c +++ b/applications/services/desktop/scenes/desktop_scene_main.c @@ -85,7 +85,7 @@ static void desktop_scene_main_open_app_or_profile(Desktop* desktop, FavoriteApp static void desktop_scene_main_start_favorite(Desktop* desktop, FavoriteApp* application) { if(strlen(application->name_or_path) > 0) { if(!desktop_scene_main_check_none(application->name_or_path)) { - loader_start_with_gui_error(desktop->loader, application->name_or_path, NULL); + loader_start_detached_with_gui_error(desktop->loader, application->name_or_path, NULL); } } else { loader_start(desktop->loader, LOADER_APPLICATIONS_NAME, NULL, NULL); From 309889e5817db05b16970ab594a7b7d426f19db6 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 5 Apr 2024 06:46:34 +0100 Subject: [PATCH 019/129] JS: Refactor storage to use array buffers --- .../js_app/examples/apps/Scripts/storage.js | 19 +++++- .../system/js_app/modules/js_storage.c | 68 +++++++++++++------ 2 files changed, 66 insertions(+), 21 deletions(-) diff --git a/applications/system/js_app/examples/apps/Scripts/storage.js b/applications/system/js_app/examples/apps/Scripts/storage.js index c19f0f003..e084066e2 100644 --- a/applications/system/js_app/examples/apps/Scripts/storage.js +++ b/applications/system/js_app/examples/apps/Scripts/storage.js @@ -1,19 +1,36 @@ let storage = require("storage"); let path = "/ext/storage.test"; +function arraybuf_to_string(arraybuf) { + let string = ""; + let data_view = Uint8Array(arraybuf); + for (let i = 0; i < data_view.length; i++) { + string += chr(data_view[i]); + } + return string; +} + print("File exists:", storage.exists(path)); print("Writing..."); +// write(path, data, offset) +// If offset is specified, the file is not cleared, content is kept and data is written at specified offset +// Takes both strings and array buffers storage.write(path, "Hello "); print("File exists:", storage.exists(path)); // Append will create the file even if it doesnt exist! +// Takes both strings and array buffers storage.append(path, "World!"); print("Reading..."); +// read(path, size, offset) +// If no size specified, total filesize is used +// If offset is specified, size is capped at (filesize - offset) let data = storage.read(path); -print(data); +// read returns an array buffer, to allow proper usage of raw binary data +print(arraybuf_to_string(data)); print("Removing...") storage.remove(path); diff --git a/applications/system/js_app/modules/js_storage.c b/applications/system/js_app/modules/js_storage.c index 05f8ffd40..91d1ab484 100644 --- a/applications/system/js_app/modules/js_storage.c +++ b/applications/system/js_app/modules/js_storage.c @@ -49,7 +49,6 @@ static bool get_path_arg(struct mjs* mjs, const char** path) { static void js_storage_read(struct mjs* mjs) { JsStorageInst* storage = get_this_ctx(mjs); - if(!check_arg_count(mjs, 1)) return; const char* path; if(!get_path_arg(mjs, &path)) return; @@ -62,15 +61,26 @@ static void js_storage_read(struct mjs* mjs) { } uint64_t size = storage_file_size(file); - if(size > 128 * 1024) { - ret_int_err(mjs, "File too large"); + mjs_val_t size_arg = mjs_arg(mjs, 1); + if(mjs_is_number(size_arg)) { + size = mjs_get_int32(mjs, size_arg); + } + + mjs_val_t seek_arg = mjs_arg(mjs, 2); + if(mjs_is_number(seek_arg)) { + storage_file_seek(file, mjs_get_int32(mjs, seek_arg), true); + size = MIN(size, storage_file_size(file) - storage_file_tell(file)); + } + + if(size > memmgr_heap_get_max_free_block()) { + ret_int_err(mjs, "Read size too large"); break; } uint8_t* data = malloc(size); size_t read = storage_file_read(file, data, size); if(read == size) { - mjs_return(mjs, mjs_mk_string(mjs, (const char*)data, size, true)); + mjs_return(mjs, mjs_mk_array_buf(mjs, (char*)data, size)); } else { ret_int_err(mjs, "File read failed"); } @@ -81,27 +91,41 @@ static void js_storage_read(struct mjs* mjs) { static void js_storage_write(struct mjs* mjs) { JsStorageInst* storage = get_this_ctx(mjs); - if(!check_arg_count(mjs, 2)) return; const char* path; if(!get_path_arg(mjs, &path)) return; - mjs_val_t data_obj = mjs_arg(mjs, 1); - if(!mjs_is_string(data_obj)) { - ret_bad_args(mjs, "Data must be a string"); + mjs_val_t data_arg = mjs_arg(mjs, 1); + if(!mjs_is_typed_array(data_arg) && !mjs_is_string(data_arg)) { + ret_bad_args(mjs, "Data must be string, arraybuf or dataview"); return; } + if(mjs_is_data_view(data_arg)) { + data_arg = mjs_dataview_get_buf(mjs, data_arg); + } size_t data_len = 0; - const char* data = mjs_get_string(mjs, &data_obj, &data_len); - if((data_len == 0) || (data == NULL)) { - ret_bad_args(mjs, "Bad data argument"); - return; + const char* data = NULL; + if(mjs_is_string(data_arg)) { + data = mjs_get_string(mjs, &data_arg, &data_len); + } else if(mjs_is_typed_array(data_arg)) { + data = mjs_array_buf_get_ptr(mjs, data_arg, &data_len); } + mjs_val_t seek_arg = mjs_arg(mjs, 2); + File* file = storage_file_alloc(storage->api); - if(!storage_file_open(file, path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) { + if(!storage_file_open( + file, + path, + FSAM_WRITE, + mjs_is_number(seek_arg) ? FSOM_OPEN_ALWAYS : FSOM_CREATE_ALWAYS)) { ret_int_err(mjs, storage_file_get_error_desc(file)); + } else { + if(mjs_is_number(seek_arg)) { + storage_file_seek(file, mjs_get_int32(mjs, seek_arg), true); + } + size_t write = storage_file_write(file, data, data_len); mjs_return(mjs, mjs_mk_boolean(mjs, write == data_len)); } @@ -115,16 +139,20 @@ static void js_storage_append(struct mjs* mjs) { const char* path; if(!get_path_arg(mjs, &path)) return; - mjs_val_t data_obj = mjs_arg(mjs, 1); - if(!mjs_is_string(data_obj)) { - ret_bad_args(mjs, "Data must be a string"); + mjs_val_t data_arg = mjs_arg(mjs, 1); + if(!mjs_is_typed_array(data_arg) && !mjs_is_string(data_arg)) { + ret_bad_args(mjs, "Data must be string, arraybuf or dataview"); return; } + if(mjs_is_data_view(data_arg)) { + data_arg = mjs_dataview_get_buf(mjs, data_arg); + } size_t data_len = 0; - const char* data = mjs_get_string(mjs, &data_obj, &data_len); - if((data_len == 0) || (data == NULL)) { - ret_bad_args(mjs, "Bad data argument"); - return; + const char* data = NULL; + if(mjs_is_string(data_arg)) { + data = mjs_get_string(mjs, &data_arg, &data_len); + } else if(mjs_is_typed_array(data_arg)) { + data = mjs_array_buf_get_ptr(mjs, data_arg, &data_len); } File* file = storage_file_alloc(storage->api); From 5f80b90a5aaf5ba9ee9a11077d38cd121854fea6 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 5 Apr 2024 06:56:50 +0100 Subject: [PATCH 020/129] JS: Add storage copy() move() mkdir() --- .../js_app/examples/apps/Scripts/storage.js | 3 + .../system/js_app/modules/js_storage.c | 65 ++++++++++++++++--- 2 files changed, 60 insertions(+), 8 deletions(-) diff --git a/applications/system/js_app/examples/apps/Scripts/storage.js b/applications/system/js_app/examples/apps/Scripts/storage.js index e084066e2..fd2ba4a4f 100644 --- a/applications/system/js_app/examples/apps/Scripts/storage.js +++ b/applications/system/js_app/examples/apps/Scripts/storage.js @@ -38,6 +38,9 @@ storage.remove(path); print("Done") // There's also: +// storage.copy(old_path, new_path); +// storage.move(old_path, new_path); +// storage.mkdir(path); // storage.virtualInit(path); // storage.virtualMount(); // storage.virtualQuit(); \ No newline at end of file diff --git a/applications/system/js_app/modules/js_storage.c b/applications/system/js_app/modules/js_storage.c index 91d1ab484..e8548bbe0 100644 --- a/applications/system/js_app/modules/js_storage.c +++ b/applications/system/js_app/modules/js_storage.c @@ -32,8 +32,8 @@ static bool check_arg_count(struct mjs* mjs, size_t count) { return true; } -static bool get_path_arg(struct mjs* mjs, const char** path) { - mjs_val_t path_obj = mjs_arg(mjs, 0); +static bool get_path_arg(struct mjs* mjs, const char** path, size_t index) { + mjs_val_t path_obj = mjs_arg(mjs, index); if(!mjs_is_string(path_obj)) { ret_bad_args(mjs, "Path must be a string"); return false; @@ -51,7 +51,7 @@ static void js_storage_read(struct mjs* mjs) { JsStorageInst* storage = get_this_ctx(mjs); const char* path; - if(!get_path_arg(mjs, &path)) return; + if(!get_path_arg(mjs, &path, 0)) return; File* file = storage_file_alloc(storage->api); do { @@ -93,7 +93,7 @@ static void js_storage_write(struct mjs* mjs) { JsStorageInst* storage = get_this_ctx(mjs); const char* path; - if(!get_path_arg(mjs, &path)) return; + if(!get_path_arg(mjs, &path, 0)) return; mjs_val_t data_arg = mjs_arg(mjs, 1); if(!mjs_is_typed_array(data_arg) && !mjs_is_string(data_arg)) { @@ -137,7 +137,7 @@ static void js_storage_append(struct mjs* mjs) { if(!check_arg_count(mjs, 2)) return; const char* path; - if(!get_path_arg(mjs, &path)) return; + if(!get_path_arg(mjs, &path, 0)) return; mjs_val_t data_arg = mjs_arg(mjs, 1); if(!mjs_is_typed_array(data_arg) && !mjs_is_string(data_arg)) { @@ -170,7 +170,7 @@ static void js_storage_exists(struct mjs* mjs) { if(!check_arg_count(mjs, 1)) return; const char* path; - if(!get_path_arg(mjs, &path)) return; + if(!get_path_arg(mjs, &path, 0)) return; mjs_return(mjs, mjs_mk_boolean(mjs, storage_common_exists(storage->api, path))); } @@ -180,17 +180,63 @@ static void js_storage_remove(struct mjs* mjs) { if(!check_arg_count(mjs, 1)) return; const char* path; - if(!get_path_arg(mjs, &path)) return; + if(!get_path_arg(mjs, &path, 0)) return; mjs_return(mjs, mjs_mk_boolean(mjs, storage_simply_remove(storage->api, path))); } +static void js_storage_copy(struct mjs* mjs) { + JsStorageInst* storage = get_this_ctx(mjs); + if(!check_arg_count(mjs, 2)) return; + + const char* old_path; + if(!get_path_arg(mjs, &old_path, 0)) return; + + const char* new_path; + if(!get_path_arg(mjs, &new_path, 1)) return; + + FS_Error error = storage_common_copy(storage->api, old_path, new_path); + if(error == FSE_OK) { + mjs_return(mjs, MJS_UNDEFINED); + } else { + ret_int_err(mjs, storage_error_get_desc(error)); + } +} + +static void js_storage_move(struct mjs* mjs) { + JsStorageInst* storage = get_this_ctx(mjs); + if(!check_arg_count(mjs, 2)) return; + + const char* old_path; + if(!get_path_arg(mjs, &old_path, 0)) return; + + const char* new_path; + if(!get_path_arg(mjs, &new_path, 1)) return; + + FS_Error error = storage_common_rename(storage->api, old_path, new_path); + if(error == FSE_OK) { + mjs_return(mjs, MJS_UNDEFINED); + } else { + ret_int_err(mjs, storage_error_get_desc(error)); + } +} + +static void js_storage_mkdir(struct mjs* mjs) { + JsStorageInst* storage = get_this_ctx(mjs); + if(!check_arg_count(mjs, 1)) return; + + const char* path; + if(!get_path_arg(mjs, &path, 0)) return; + + mjs_return(mjs, mjs_mk_boolean(mjs, storage_simply_mkdir(storage->api, path))); +} + static void js_storage_virtual_init(struct mjs* mjs) { JsStorageInst* storage = get_this_ctx(mjs); if(!check_arg_count(mjs, 1)) return; const char* path; - if(!get_path_arg(mjs, &path)) return; + if(!get_path_arg(mjs, &path, 0)) return; if(storage->virtual) { ret_int_err(mjs, "Virtual already setup"); @@ -259,6 +305,9 @@ static void* js_storage_create(struct mjs* mjs, mjs_val_t* object) { mjs_set(mjs, storage_obj, "append", ~0, MJS_MK_FN(js_storage_append)); mjs_set(mjs, storage_obj, "exists", ~0, MJS_MK_FN(js_storage_exists)); mjs_set(mjs, storage_obj, "remove", ~0, MJS_MK_FN(js_storage_remove)); + mjs_set(mjs, storage_obj, "copy", ~0, MJS_MK_FN(js_storage_copy)); + mjs_set(mjs, storage_obj, "move", ~0, MJS_MK_FN(js_storage_move)); + mjs_set(mjs, storage_obj, "mkdir", ~0, MJS_MK_FN(js_storage_mkdir)); mjs_set(mjs, storage_obj, "virtualInit", ~0, MJS_MK_FN(js_storage_virtual_init)); mjs_set(mjs, storage_obj, "virtualMount", ~0, MJS_MK_FN(js_storage_virtual_mount)); mjs_set(mjs, storage_obj, "virtualQuit", ~0, MJS_MK_FN(js_storage_virtual_quit)); From 344b624efcfe41cb62c5958e3a21f78c65e177af Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 5 Apr 2024 08:57:34 +0300 Subject: [PATCH 021/129] fix apps loading logic --- .../services/desktop/scenes/desktop_scene_main.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/applications/services/desktop/scenes/desktop_scene_main.c b/applications/services/desktop/scenes/desktop_scene_main.c index 0c083a5d8..d6c334d7e 100644 --- a/applications/services/desktop/scenes/desktop_scene_main.c +++ b/applications/services/desktop/scenes/desktop_scene_main.c @@ -68,17 +68,15 @@ static inline bool desktop_scene_main_check_none(const char* str) { static void desktop_scene_main_open_app_or_profile(Desktop* desktop, FavoriteApp* application) { bool load_ok = false; if(strlen(application->name_or_path) > 0) { - if(desktop_scene_main_check_none(application->name_or_path)) { - // skip loading - load_ok = true; - } else if( - loader_start(desktop->loader, application->name_or_path, NULL, NULL) == - LoaderStatusOk) { - load_ok = true; + if(!desktop_scene_main_check_none(application->name_or_path)) { + // Load app + loader_start_detached_with_gui_error(desktop->loader, application->name_or_path, NULL); } + load_ok = true; } + // In case of "default" setting if(!load_ok) { - loader_start(desktop->loader, "Passport", NULL, NULL); + loader_start_detached_with_gui_error(desktop->loader, "Passport", NULL); } } @@ -88,7 +86,7 @@ static void desktop_scene_main_start_favorite(Desktop* desktop, FavoriteApp* app loader_start_detached_with_gui_error(desktop->loader, application->name_or_path, NULL); } } else { - loader_start(desktop->loader, LOADER_APPLICATIONS_NAME, NULL, NULL); + loader_start_detached_with_gui_error(desktop->loader, LOADER_APPLICATIONS_NAME, NULL); } } From 0ebdc485f05dbc89a94aecd5ac8c7246ce24bf03 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 5 Apr 2024 07:22:04 +0100 Subject: [PATCH 022/129] RFID: Fix success title icon overlap --- applications/main/lfrfid/scenes/lfrfid_scene_read_success.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_read_success.c b/applications/main/lfrfid/scenes/lfrfid_scene_read_success.c index eb773f41b..b0e373ea5 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_read_success.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_read_success.c @@ -15,8 +15,10 @@ void lfrfid_scene_read_success_on_enter(void* context) { } else { furi_string_printf(display_text, "\e#%s\e#", protocol); } + widget_add_text_box_element( + widget, 16, 2, 112, 14, AlignLeft, AlignTop, furi_string_get_cstr(display_text), true); - furi_string_cat(display_text, "\nHex: "); + furi_string_set(display_text, "Hex: "); const size_t data_size = protocol_dict_get_data_size(app->dict, app->protocol_id); uint8_t* data = malloc(data_size); @@ -40,7 +42,7 @@ void lfrfid_scene_read_success_on_enter(void* context) { furi_string_free(rendered_data); widget_add_text_box_element( - widget, 0, 0, 128, 52, AlignLeft, AlignTop, furi_string_get_cstr(display_text), true); + widget, 0, 16, 128, 52, AlignLeft, AlignTop, furi_string_get_cstr(display_text), true); widget_add_button_element(widget, GuiButtonTypeLeft, "Retry", lfrfid_widget_callback, app); widget_add_button_element(widget, GuiButtonTypeRight, "More", lfrfid_widget_callback, app); From 758ecb2825a991f7e725f02ee0c23b8dbc0700df Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 5 Apr 2024 08:13:54 +0100 Subject: [PATCH 023/129] Sync apps --- applications/external | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/external b/applications/external index 5890d0834..4620da675 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit 5890d08342cf621f5dba8160b8ee87d53c055988 +Subproject commit 4620da675580965116d9f577db21f5023aa87c0f From 40b97546f54f3ed9962a34ebf47661d8c4116ca0 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 5 Apr 2024 08:22:45 +0100 Subject: [PATCH 024/129] Fix build --- applications/external | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/external b/applications/external index 4620da675..07710ce2b 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit 4620da675580965116d9f577db21f5023aa87c0f +Subproject commit 07710ce2b0ed38387d62e65d381309eb34270fc2 From 05745ac7a616c39a76e0f841b335fa84bdf45b7b Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 5 Apr 2024 20:25:22 +0100 Subject: [PATCH 025/129] Oops (#75 #79) --- applications/system/js_app/modules/js_storage.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/applications/system/js_app/modules/js_storage.c b/applications/system/js_app/modules/js_storage.c index e8548bbe0..67c17c486 100644 --- a/applications/system/js_app/modules/js_storage.c +++ b/applications/system/js_app/modules/js_storage.c @@ -276,11 +276,6 @@ static void js_storage_virtual_mount(struct mjs* mjs) { return; } - if(storage->virtual) { - storage_file_free(storage->virtual); - storage->virtual = NULL; - } - mjs_return(mjs, MJS_UNDEFINED); } @@ -293,6 +288,11 @@ static void js_storage_virtual_quit(struct mjs* mjs) { return; } + if(storage->virtual) { + storage_file_free(storage->virtual); + storage->virtual = NULL; + } + mjs_return(mjs, MJS_UNDEFINED); } From 86dcd781757e851052c07dcd4032b774d3f1741c Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 5 Apr 2024 20:26:42 +0100 Subject: [PATCH 026/129] Oops (semaphore vs apilock) This reverts commit f89f775d787f51fb147175b2b9156f94e07316d2. --- applications/services/desktop/desktop.c | 7 +++---- applications/services/desktop/desktop_i.h | 3 +-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index a042dc56b..38914f43a 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -34,10 +34,8 @@ static void desktop_loader_callback(const void* message, void* context) { const LoaderEvent* event = message; if(event->type == LoaderEventTypeApplicationBeforeLoad) { - desktop->animation_lock = api_lock_alloc_locked(); view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalBeforeAppStarted); - api_lock_wait_unlock_and_free(desktop->animation_lock); - desktop->animation_lock = NULL; + furi_check(furi_semaphore_acquire(desktop->animation_semaphore, 3000) == FuriStatusOk); } else if( event->type == LoaderEventTypeApplicationLoadFailed || event->type == LoaderEventTypeApplicationStopped) { @@ -125,7 +123,7 @@ static bool desktop_custom_event_callback(void* context, uint32_t event) { animation_manager_unload_and_stall_animation(desktop->animation_manager); } desktop_auto_lock_inhibit(desktop); - api_lock_unlock(desktop->animation_lock); + furi_semaphore_release(desktop->animation_semaphore); return true; case DesktopGlobalAfterAppFinished: animation_manager_load_and_continue_animation(desktop->animation_manager); @@ -282,6 +280,7 @@ void desktop_set_stealth_mode_state(Desktop* desktop, bool enabled) { Desktop* desktop_alloc(void) { Desktop* desktop = malloc(sizeof(Desktop)); + desktop->animation_semaphore = furi_semaphore_alloc(1, 0); desktop->animation_manager = animation_manager_alloc(); desktop->gui = furi_record_open(RECORD_GUI); desktop->scene_thread = furi_thread_alloc(); diff --git a/applications/services/desktop/desktop_i.h b/applications/services/desktop/desktop_i.h index c06ba40a2..1c1598c79 100644 --- a/applications/services/desktop/desktop_i.h +++ b/applications/services/desktop/desktop_i.h @@ -20,7 +20,6 @@ #include #include -#include #define STATUS_BAR_Y_SHIFT 13 @@ -81,7 +80,7 @@ struct Desktop { bool in_transition : 1; - FuriApiLock animation_lock; + FuriSemaphore* animation_semaphore; Keybinds keybinds; From c82593525fc62bbd18f34f959f6f5f923dd7e3c1 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 5 Apr 2024 01:48:01 +0100 Subject: [PATCH 027/129] Desktop: Unload animations before FAP is loaded --- applications/services/desktop/desktop.c | 6 ++++-- applications/services/loader/loader.c | 8 ++++++++ applications/services/loader/loader.h | 2 ++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 49aa04e35..c5a334a45 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -32,10 +32,12 @@ static void desktop_loader_callback(const void* message, void* context) { Desktop* desktop = context; const LoaderEvent* event = message; - if(event->type == LoaderEventTypeApplicationStarted) { + if(event->type == LoaderEventTypeApplicationBeforeLoad) { view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalBeforeAppStarted); furi_check(furi_semaphore_acquire(desktop->animation_semaphore, 3000) == FuriStatusOk); - } else if(event->type == LoaderEventTypeApplicationStopped) { + } else if( + event->type == LoaderEventTypeApplicationLoadFailed || + event->type == LoaderEventTypeApplicationStopped) { view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalAfterAppFinished); } } diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 5daf99d1d..553be6818 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -255,6 +255,9 @@ static void loader_start_internal_app( const FlipperInternalApplication* app, const char* args) { FURI_LOG_I(TAG, "Starting %s", app->name); + LoaderEvent event; + event.type = LoaderEventTypeApplicationBeforeLoad; + furi_pubsub_publish(loader->pubsub, &event); // store args furi_assert(loader->app.args == NULL); @@ -309,6 +312,9 @@ static LoaderStatus loader_start_external_app( const char* args, FuriString* error_message) { LoaderStatus status = loader_make_success_status(error_message); + LoaderEvent event; + event.type = LoaderEventTypeApplicationBeforeLoad; + furi_pubsub_publish(loader->pubsub, &event); do { loader->app.fap = flipper_application_alloc(storage, firmware_api_interface); @@ -356,6 +362,8 @@ static LoaderStatus loader_start_external_app( if(status != LoaderStatusOk) { flipper_application_free(loader->app.fap); loader->app.fap = NULL; + event.type = LoaderEventTypeApplicationLoadFailed; + furi_pubsub_publish(loader->pubsub, &event); } return status; diff --git a/applications/services/loader/loader.h b/applications/services/loader/loader.h index cca65628f..5e132a9ac 100644 --- a/applications/services/loader/loader.h +++ b/applications/services/loader/loader.h @@ -18,6 +18,8 @@ typedef enum { } LoaderStatus; typedef enum { + LoaderEventTypeApplicationBeforeLoad, + LoaderEventTypeApplicationLoadFailed, LoaderEventTypeApplicationStarted, LoaderEventTypeApplicationStopped } LoaderEventType; From 092f529ed5a891244f181726168b8f8783a9b443 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 5 Apr 2024 21:34:24 +0100 Subject: [PATCH 028/129] Loader: Add API to start app detached (returns instantly, queues app start) --- applications/services/loader/loader.c | 35 ++++++++++++++++++++----- applications/services/loader/loader.h | 14 +++++++--- applications/services/loader/loader_i.h | 1 + 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 553be6818..c552b5776 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -47,13 +47,7 @@ LoaderStatus return result.value; } -LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const char* args) { - furi_check(loader); - furi_check(name); - - FuriString* error_message = furi_string_alloc(); - LoaderStatus status = loader_start(loader, name, args, error_message); - +static void loader_show_gui_error(LoaderStatus status, FuriString* error_message) { if(status == LoaderStatusErrorUnknownApp && loader_find_external_application_by_name(name) != NULL) { // Special case for external apps @@ -75,6 +69,7 @@ LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const dialog_message_set_header(message, "Error", 64, 0, AlignCenter, AlignTop); dialog_message_set_buttons(message, NULL, NULL, NULL); + furi_string_replace(error_message, ":", "\n"); furi_string_replace(error_message, "/ext/apps/", ""); furi_string_replace(error_message, ", ", "\n"); furi_string_replace(error_message, ": ", "\n"); @@ -86,11 +81,31 @@ LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const dialog_message_free(message); furi_record_close(RECORD_DIALOGS); } +} +LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const char* args) { + furi_check(loader); + furi_check(name); + + FuriString* error_message = furi_string_alloc(); + LoaderStatus status = loader_start(loader, name, args, error_message); + loader_show_gui_error(status, error_message); furi_string_free(error_message); return status; } +void loader_start_detached_with_gui_error(Loader* loader, const char* name, const char* args) { + furi_check(loader); + furi_check(name); + + LoaderMessage message = { + .type = LoaderMessageTypeStartByNameDetachedWithGuiError, + .start.name = name ? strdup(name) : NULL, + .start.args = args ? strdup(args) : NULL, + }; + furi_message_queue_put(loader->queue, &message, FuriWaitForever); +} + bool loader_lock(Loader* loader) { furi_check(loader); @@ -536,6 +551,12 @@ int32_t loader_srv(void* p) { loader, message.start.name, message.start.args, message.start.error_message); api_lock_unlock(message.api_lock); break; + case LoaderMessageTypeStartByNameDetachedWithGuiError: { + loader_start_with_gui_error(loader, message.start.name, message.start.args); + if(message.start.name) free((void*)message.start.name); + if(message.start.args) free((void*)message.start.args); + break; + } case LoaderMessageTypeShowMenu: loader_do_menu_show(loader); break; diff --git a/applications/services/loader/loader.h b/applications/services/loader/loader.h index 5e132a9ac..d57ba7da1 100644 --- a/applications/services/loader/loader.h +++ b/applications/services/loader/loader.h @@ -34,7 +34,7 @@ typedef struct { * @param[in] name application name or id * @param[in] args application arguments * @param[out] error_message detailed error message, can be NULL - * @return LoaderStatus + * @return LoaderStatus */ LoaderStatus loader_start(Loader* instance, const char* name, const char* args, FuriString* error_message); @@ -44,11 +44,19 @@ LoaderStatus * @param[in] instance loader instance * @param[in] name application name or id * @param[in] args application arguments - * @return LoaderStatus + * @return LoaderStatus */ LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const char* args); -/** +/** + * @brief Start application detached with GUI error message + * @param[in] instance loader instance + * @param[in] name application name or id + * @param[in] args application arguments + */ +void loader_start_detached_with_gui_error(Loader* loader, const char* name, const char* args); + +/** * @brief Lock application start * @param[in] instance loader instance * @return true on success diff --git a/applications/services/loader/loader_i.h b/applications/services/loader/loader_i.h index 688b8fb66..daa5484d3 100644 --- a/applications/services/loader/loader_i.h +++ b/applications/services/loader/loader_i.h @@ -30,6 +30,7 @@ typedef enum { LoaderMessageTypeLock, LoaderMessageTypeUnlock, LoaderMessageTypeIsLocked, + LoaderMessageTypeStartByNameDetachedWithGuiError, } LoaderMessageType; typedef struct { From 5f7e6980170ee61246ffc566863190c7204aef88 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 5 Apr 2024 21:36:41 +0100 Subject: [PATCH 029/129] Desktop: Fix early animation unload deadlocks --- .../services/desktop/scenes/desktop_scene_main.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/applications/services/desktop/scenes/desktop_scene_main.c b/applications/services/desktop/scenes/desktop_scene_main.c index a659ff4e3..955d64fe2 100644 --- a/applications/services/desktop/scenes/desktop_scene_main.c +++ b/applications/services/desktop/scenes/desktop_scene_main.c @@ -64,21 +64,17 @@ static void static void desktop_scene_main_open_app_or_profile(Desktop* desktop, FavoriteApp* application) { bool load_ok = false; if(strlen(application->name_or_path) > 0) { - if(loader_start(desktop->loader, application->name_or_path, NULL, NULL) == - LoaderStatusOk) { - load_ok = true; - } - } - if(!load_ok) { - loader_start(desktop->loader, "Passport", NULL, NULL); + loader_start_detached_with_gui_error(desktop->loader, application->name_or_path, NULL); + } else { + loader_start_detached_with_gui_error(desktop->loader, "Passport", NULL); } } static void desktop_scene_main_start_favorite(Desktop* desktop, FavoriteApp* application) { if(strlen(application->name_or_path) > 0) { - loader_start_with_gui_error(desktop->loader, application->name_or_path, NULL); + loader_start_detached_with_gui_error(desktop->loader, application->name_or_path); } else { - loader_start(desktop->loader, LOADER_APPLICATIONS_NAME, NULL, NULL); + loader_start_detached_with_gui_error(desktop->loader, LOADER_APPLICATIONS_NAME, NULL); } } @@ -141,7 +137,7 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { break; case DesktopMainEventOpenPowerOff: { - loader_start(desktop->loader, "Power", "off", NULL); + loader_start_detached_with_gui_error(desktop->loader, "Power", "off"); consumed = true; break; } From 0290996beae4f08f5586c2ab3a845d59cbccdc36 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 5 Apr 2024 21:53:45 +0100 Subject: [PATCH 030/129] Update symbols --- targets/f18/api_symbols.csv | 3 ++- targets/f7/api_symbols.csv | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index f6199445d..38914ad4f 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.4,, +Version,+,60.5,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1748,6 +1748,7 @@ Function,+,loader_is_locked,_Bool,Loader* Function,+,loader_lock,_Bool,Loader* Function,+,loader_show_menu,void,Loader* Function,+,loader_start,LoaderStatus,"Loader*, const char*, const char*, FuriString*" +Function,+,loader_start_detached_with_gui_error,void,"Loader*, const char*, const char*" Function,+,loader_start_with_gui_error,LoaderStatus,"Loader*, const char*, const char*" Function,+,loader_unlock,void,Loader* Function,+,loading_alloc,Loading*, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 6e65b9471..ee3ece431 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.4,, +Version,+,60.5,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -2128,6 +2128,7 @@ Function,+,loader_is_locked,_Bool,Loader* Function,+,loader_lock,_Bool,Loader* Function,+,loader_show_menu,void,Loader* Function,+,loader_start,LoaderStatus,"Loader*, const char*, const char*, FuriString*" +Function,+,loader_start_detached_with_gui_error,void,"Loader*, const char*, const char*" Function,+,loader_start_with_gui_error,LoaderStatus,"Loader*, const char*, const char*" Function,+,loader_unlock,void,Loader* Function,+,loading_alloc,Loading*, From 8e57a4f089a96082c52ff250f5d517795ede7573 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 5 Apr 2024 22:00:34 +0100 Subject: [PATCH 031/129] Fix build and cleanup --- .../services/desktop/scenes/desktop_scene_main.c | 3 +-- applications/services/loader/loader.c | 16 +++++++--------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/applications/services/desktop/scenes/desktop_scene_main.c b/applications/services/desktop/scenes/desktop_scene_main.c index 955d64fe2..41b1c3e75 100644 --- a/applications/services/desktop/scenes/desktop_scene_main.c +++ b/applications/services/desktop/scenes/desktop_scene_main.c @@ -62,7 +62,6 @@ static void #endif static void desktop_scene_main_open_app_or_profile(Desktop* desktop, FavoriteApp* application) { - bool load_ok = false; if(strlen(application->name_or_path) > 0) { loader_start_detached_with_gui_error(desktop->loader, application->name_or_path, NULL); } else { @@ -72,7 +71,7 @@ static void desktop_scene_main_open_app_or_profile(Desktop* desktop, FavoriteApp static void desktop_scene_main_start_favorite(Desktop* desktop, FavoriteApp* application) { if(strlen(application->name_or_path) > 0) { - loader_start_detached_with_gui_error(desktop->loader, application->name_or_path); + loader_start_detached_with_gui_error(desktop->loader, application->name_or_path, NULL); } else { loader_start_detached_with_gui_error(desktop->loader, LOADER_APPLICATIONS_NAME, NULL); } diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index c552b5776..8221f4eb0 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -47,7 +47,13 @@ LoaderStatus return result.value; } -static void loader_show_gui_error(LoaderStatus status, FuriString* error_message) { +LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const char* args) { + furi_check(loader); + furi_check(name); + + FuriString* error_message = furi_string_alloc(); + LoaderStatus status = loader_start(loader, name, args, error_message); + if(status == LoaderStatusErrorUnknownApp && loader_find_external_application_by_name(name) != NULL) { // Special case for external apps @@ -81,15 +87,7 @@ static void loader_show_gui_error(LoaderStatus status, FuriString* error_message dialog_message_free(message); furi_record_close(RECORD_DIALOGS); } -} -LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const char* args) { - furi_check(loader); - furi_check(name); - - FuriString* error_message = furi_string_alloc(); - LoaderStatus status = loader_start(loader, name, args, error_message); - loader_show_gui_error(status, error_message); furi_string_free(error_message); return status; } From fb8d6d54a319b12006b33db7be968a04e7803686 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 5 Apr 2024 22:16:34 +0100 Subject: [PATCH 032/129] Fix --no-build --- applications/services/loader/loader.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 47e751657..c82017950 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -24,14 +24,14 @@ static const char* loader_find_external_application_by_name(const char* app_name, FlipperApplicationFlag* flags) { for(size_t i = 0; i < FLIPPER_EXTERNAL_APPS_COUNT; i++) { if(strcmp(FLIPPER_EXTERNAL_APPS[i].name, app_name) == 0) { - *flags = FLIPPER_EXTERNAL_APPS[i].flags; + if(flags) *flags = FLIPPER_EXTERNAL_APPS[i].flags; return FLIPPER_EXTERNAL_APPS[i].path; } } for(size_t i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) { if(strcmp(FLIPPER_SETTINGS_APPS[i].name, app_name) == 0) { - *flags = FLIPPER_SETTINGS_APPS[i].flags; + if(flags) *flags = FLIPPER_SETTINGS_APPS[i].flags; return FLIPPER_SETTINGS_APPS[i].path; } } @@ -68,7 +68,7 @@ LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const LoaderStatus status = loader_start(loader, name, args, error_message); if(status == LoaderStatusErrorUnknownApp && - loader_find_external_application_by_name(name) != NULL) { + loader_find_external_application_by_name(name, NULL) != NULL) { // Special case for external apps DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); DialogMessage* message = dialog_message_alloc(); From a95519ea865e47250f5b32aa8215425bbcb6d3e2 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 5 Apr 2024 01:48:01 +0100 Subject: [PATCH 033/129] Desktop: Unload animations before FAP is loaded --- applications/services/desktop/desktop.c | 6 ++++-- applications/services/loader/loader.c | 8 ++++++++ applications/services/loader/loader.h | 2 ++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 49aa04e35..c5a334a45 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -32,10 +32,12 @@ static void desktop_loader_callback(const void* message, void* context) { Desktop* desktop = context; const LoaderEvent* event = message; - if(event->type == LoaderEventTypeApplicationStarted) { + if(event->type == LoaderEventTypeApplicationBeforeLoad) { view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalBeforeAppStarted); furi_check(furi_semaphore_acquire(desktop->animation_semaphore, 3000) == FuriStatusOk); - } else if(event->type == LoaderEventTypeApplicationStopped) { + } else if( + event->type == LoaderEventTypeApplicationLoadFailed || + event->type == LoaderEventTypeApplicationStopped) { view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalAfterAppFinished); } } diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 5daf99d1d..553be6818 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -255,6 +255,9 @@ static void loader_start_internal_app( const FlipperInternalApplication* app, const char* args) { FURI_LOG_I(TAG, "Starting %s", app->name); + LoaderEvent event; + event.type = LoaderEventTypeApplicationBeforeLoad; + furi_pubsub_publish(loader->pubsub, &event); // store args furi_assert(loader->app.args == NULL); @@ -309,6 +312,9 @@ static LoaderStatus loader_start_external_app( const char* args, FuriString* error_message) { LoaderStatus status = loader_make_success_status(error_message); + LoaderEvent event; + event.type = LoaderEventTypeApplicationBeforeLoad; + furi_pubsub_publish(loader->pubsub, &event); do { loader->app.fap = flipper_application_alloc(storage, firmware_api_interface); @@ -356,6 +362,8 @@ static LoaderStatus loader_start_external_app( if(status != LoaderStatusOk) { flipper_application_free(loader->app.fap); loader->app.fap = NULL; + event.type = LoaderEventTypeApplicationLoadFailed; + furi_pubsub_publish(loader->pubsub, &event); } return status; diff --git a/applications/services/loader/loader.h b/applications/services/loader/loader.h index cca65628f..5e132a9ac 100644 --- a/applications/services/loader/loader.h +++ b/applications/services/loader/loader.h @@ -18,6 +18,8 @@ typedef enum { } LoaderStatus; typedef enum { + LoaderEventTypeApplicationBeforeLoad, + LoaderEventTypeApplicationLoadFailed, LoaderEventTypeApplicationStarted, LoaderEventTypeApplicationStopped } LoaderEventType; From ef72f81581cee8b49ea00603ee3d591909a69156 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 6 Apr 2024 00:01:00 +0100 Subject: [PATCH 034/129] Loader: Add API to start detached (returns instantly, queues event) --- applications/services/loader/loader.c | 39 ++++++++++++++++++++----- applications/services/loader/loader.h | 14 +++++++-- applications/services/loader/loader_i.h | 1 + targets/f18/api_symbols.csv | 3 +- targets/f7/api_symbols.csv | 3 +- 5 files changed, 48 insertions(+), 12 deletions(-) diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 553be6818..a6bc26422 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -47,13 +47,8 @@ LoaderStatus return result.value; } -LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const char* args) { - furi_check(loader); - furi_check(name); - - FuriString* error_message = furi_string_alloc(); - LoaderStatus status = loader_start(loader, name, args, error_message); - +static void + loader_show_gui_error(LoaderStatus status, const char* name, FuriString* error_message) { if(status == LoaderStatusErrorUnknownApp && loader_find_external_application_by_name(name) != NULL) { // Special case for external apps @@ -86,11 +81,31 @@ LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const dialog_message_free(message); furi_record_close(RECORD_DIALOGS); } +} +LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const char* args) { + furi_check(loader); + furi_check(name); + + FuriString* error_message = furi_string_alloc(); + LoaderStatus status = loader_start(loader, name, args, error_message); + loader_show_gui_error(status, name, error_message); furi_string_free(error_message); return status; } +void loader_start_detached_with_gui_error(Loader* loader, const char* name, const char* args) { + furi_check(loader); + furi_check(name); + + LoaderMessage message = { + .type = LoaderMessageTypeStartByNameDetachedWithGuiError, + .start.name = name ? strdup(name) : NULL, + .start.args = args ? strdup(args) : NULL, + }; + furi_message_queue_put(loader->queue, &message, FuriWaitForever); +} + bool loader_lock(Loader* loader) { furi_check(loader); @@ -536,6 +551,16 @@ int32_t loader_srv(void* p) { loader, message.start.name, message.start.args, message.start.error_message); api_lock_unlock(message.api_lock); break; + case LoaderMessageTypeStartByNameDetachedWithGuiError: { + FuriString* error_message = furi_string_alloc(); + LoaderStatus status = loader_do_start_by_name( + loader, message.start.name, message.start.args, error_message); + loader_show_gui_error(status, message.start.name, error_message); + if(message.start.name) free((void*)message.start.name); + if(message.start.args) free((void*)message.start.args); + furi_string_free(error_message); + break; + } case LoaderMessageTypeShowMenu: loader_do_menu_show(loader); break; diff --git a/applications/services/loader/loader.h b/applications/services/loader/loader.h index 5e132a9ac..d57ba7da1 100644 --- a/applications/services/loader/loader.h +++ b/applications/services/loader/loader.h @@ -34,7 +34,7 @@ typedef struct { * @param[in] name application name or id * @param[in] args application arguments * @param[out] error_message detailed error message, can be NULL - * @return LoaderStatus + * @return LoaderStatus */ LoaderStatus loader_start(Loader* instance, const char* name, const char* args, FuriString* error_message); @@ -44,11 +44,19 @@ LoaderStatus * @param[in] instance loader instance * @param[in] name application name or id * @param[in] args application arguments - * @return LoaderStatus + * @return LoaderStatus */ LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const char* args); -/** +/** + * @brief Start application detached with GUI error message + * @param[in] instance loader instance + * @param[in] name application name or id + * @param[in] args application arguments + */ +void loader_start_detached_with_gui_error(Loader* loader, const char* name, const char* args); + +/** * @brief Lock application start * @param[in] instance loader instance * @return true on success diff --git a/applications/services/loader/loader_i.h b/applications/services/loader/loader_i.h index 688b8fb66..daa5484d3 100644 --- a/applications/services/loader/loader_i.h +++ b/applications/services/loader/loader_i.h @@ -30,6 +30,7 @@ typedef enum { LoaderMessageTypeLock, LoaderMessageTypeUnlock, LoaderMessageTypeIsLocked, + LoaderMessageTypeStartByNameDetachedWithGuiError, } LoaderMessageType; typedef struct { diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index f6199445d..38914ad4f 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.4,, +Version,+,60.5,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1748,6 +1748,7 @@ Function,+,loader_is_locked,_Bool,Loader* Function,+,loader_lock,_Bool,Loader* Function,+,loader_show_menu,void,Loader* Function,+,loader_start,LoaderStatus,"Loader*, const char*, const char*, FuriString*" +Function,+,loader_start_detached_with_gui_error,void,"Loader*, const char*, const char*" Function,+,loader_start_with_gui_error,LoaderStatus,"Loader*, const char*, const char*" Function,+,loader_unlock,void,Loader* Function,+,loading_alloc,Loading*, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 6e65b9471..ee3ece431 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.4,, +Version,+,60.5,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -2128,6 +2128,7 @@ Function,+,loader_is_locked,_Bool,Loader* Function,+,loader_lock,_Bool,Loader* Function,+,loader_show_menu,void,Loader* Function,+,loader_start,LoaderStatus,"Loader*, const char*, const char*, FuriString*" +Function,+,loader_start_detached_with_gui_error,void,"Loader*, const char*, const char*" Function,+,loader_start_with_gui_error,LoaderStatus,"Loader*, const char*, const char*" Function,+,loader_unlock,void,Loader* Function,+,loading_alloc,Loading*, From 48a2211548471ca4b4b6db539d44ed3f7f3555c3 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 6 Apr 2024 00:01:06 +0100 Subject: [PATCH 035/129] Desktop: Fix early animation unload deadlocks --- .../desktop/scenes/desktop_scene_main.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/applications/services/desktop/scenes/desktop_scene_main.c b/applications/services/desktop/scenes/desktop_scene_main.c index a659ff4e3..41b1c3e75 100644 --- a/applications/services/desktop/scenes/desktop_scene_main.c +++ b/applications/services/desktop/scenes/desktop_scene_main.c @@ -62,23 +62,18 @@ static void #endif static void desktop_scene_main_open_app_or_profile(Desktop* desktop, FavoriteApp* application) { - bool load_ok = false; if(strlen(application->name_or_path) > 0) { - if(loader_start(desktop->loader, application->name_or_path, NULL, NULL) == - LoaderStatusOk) { - load_ok = true; - } - } - if(!load_ok) { - loader_start(desktop->loader, "Passport", NULL, NULL); + loader_start_detached_with_gui_error(desktop->loader, application->name_or_path, NULL); + } else { + loader_start_detached_with_gui_error(desktop->loader, "Passport", NULL); } } static void desktop_scene_main_start_favorite(Desktop* desktop, FavoriteApp* application) { if(strlen(application->name_or_path) > 0) { - loader_start_with_gui_error(desktop->loader, application->name_or_path, NULL); + loader_start_detached_with_gui_error(desktop->loader, application->name_or_path, NULL); } else { - loader_start(desktop->loader, LOADER_APPLICATIONS_NAME, NULL, NULL); + loader_start_detached_with_gui_error(desktop->loader, LOADER_APPLICATIONS_NAME, NULL); } } @@ -141,7 +136,7 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { break; case DesktopMainEventOpenPowerOff: { - loader_start(desktop->loader, "Power", "off", NULL); + loader_start_detached_with_gui_error(desktop->loader, "Power", "off"); consumed = true; break; } From b8295a2daca283ebb79763e29537d83eda434f16 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 6 Apr 2024 00:29:23 +0100 Subject: [PATCH 036/129] JS: Fix default layout handling --- .../system/js_app/modules/js_badusb.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/applications/system/js_app/modules/js_badusb.c b/applications/system/js_app/modules/js_badusb.c index 1722f6672..42e677842 100644 --- a/applications/system/js_app/modules/js_badusb.c +++ b/applications/system/js_app/modules/js_badusb.c @@ -133,17 +133,20 @@ static bool setup_parse_params( if(mjs_is_string(layout_obj)) { size_t str_len = 0; const char* str_temp = mjs_get_string(mjs, &layout_obj, &str_len); - File* file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); - size_t size = sizeof(badusb->layout); - - if((str_len == 0) || (str_temp == NULL) || - !storage_file_open(file, str_temp, FSAM_READ, FSOM_OPEN_EXISTING) || - storage_file_read(file, badusb->layout, size) != size) { - memcpy(badusb->layout, hid_asciimap, MIN(sizeof(hid_asciimap), size)); + if((str_len == 0) || (str_temp == NULL)) { + return false; } - + File* file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + bool layout_loaded = storage_file_open(file, str_temp, FSAM_READ, FSOM_OPEN_EXISTING) && + storage_file_read(file, badusb->layout, sizeof(badusb->layout)) == + sizeof(badusb->layout); storage_file_free(file); furi_record_close(RECORD_STORAGE); + if(!layout_loaded) { + return false; + } + } else { + memcpy(badusb->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(badusb->layout))); } return true; From 7869869fb9dc6db1cc5a68bda7de2227cf00cc1c Mon Sep 17 00:00:00 2001 From: Nick Shaw Date: Fri, 5 Apr 2024 22:25:59 -0400 Subject: [PATCH 037/129] Storage: Default volume label DOLPHIN added to virtual storage --- applications/services/storage/storages/storage_ext.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/applications/services/storage/storages/storage_ext.c b/applications/services/storage/storages/storage_ext.c index 0bb313335..dfd13f00b 100644 --- a/applications/services/storage/storages/storage_ext.c +++ b/applications/services/storage/storages/storage_ext.c @@ -739,6 +739,9 @@ FS_Error storage_process_virtual_format(StorageData* storage) { SDData* sd_data = storage->data; uint8_t* work = malloc(_MAX_SS); SDError error = f_mkfs(sd_data->path, FM_ANY, 0, work, _MAX_SS); + storage_process_virtual_mount(storage); + f_setlabel("DOLPHIN"); + storage_process_virtual_unmount(storage); free(work); if(error != FR_OK) return FSE_INTERNAL; return FSE_OK; From 294df5d1d4f11ba9f212b7ecd8d0bebd6a562f98 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 6 Apr 2024 05:40:53 +0300 Subject: [PATCH 038/129] desktop animations unload apilock revert + js layout fix and lfrfid ui fix by Willy-JL --- .../lfrfid/scenes/lfrfid_scene_read_success.c | 6 ++-- applications/services/desktop/desktop.c | 7 ++-- applications/services/desktop/desktop_i.h | 3 +- applications/services/loader/loader.c | 36 +++++++++++++------ .../system/js_app/modules/js_badusb.c | 19 +++++----- 5 files changed, 44 insertions(+), 27 deletions(-) diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_read_success.c b/applications/main/lfrfid/scenes/lfrfid_scene_read_success.c index eb773f41b..b0e373ea5 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_read_success.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_read_success.c @@ -15,8 +15,10 @@ void lfrfid_scene_read_success_on_enter(void* context) { } else { furi_string_printf(display_text, "\e#%s\e#", protocol); } + widget_add_text_box_element( + widget, 16, 2, 112, 14, AlignLeft, AlignTop, furi_string_get_cstr(display_text), true); - furi_string_cat(display_text, "\nHex: "); + furi_string_set(display_text, "Hex: "); const size_t data_size = protocol_dict_get_data_size(app->dict, app->protocol_id); uint8_t* data = malloc(data_size); @@ -40,7 +42,7 @@ void lfrfid_scene_read_success_on_enter(void* context) { furi_string_free(rendered_data); widget_add_text_box_element( - widget, 0, 0, 128, 52, AlignLeft, AlignTop, furi_string_get_cstr(display_text), true); + widget, 0, 16, 128, 52, AlignLeft, AlignTop, furi_string_get_cstr(display_text), true); widget_add_button_element(widget, GuiButtonTypeLeft, "Retry", lfrfid_widget_callback, app); widget_add_button_element(widget, GuiButtonTypeRight, "More", lfrfid_widget_callback, app); diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 123b18bdd..a0f64990f 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -33,10 +33,8 @@ static void desktop_loader_callback(const void* message, void* context) { const LoaderEvent* event = message; if(event->type == LoaderEventTypeApplicationBeforeLoad) { - desktop->animation_lock = api_lock_alloc_locked(); view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalBeforeAppStarted); - api_lock_wait_unlock_and_free(desktop->animation_lock); - desktop->animation_lock = NULL; + furi_check(furi_semaphore_acquire(desktop->animation_semaphore, 3000) == FuriStatusOk); } else if( event->type == LoaderEventTypeApplicationLoadFailed || event->type == LoaderEventTypeApplicationStopped) { @@ -130,7 +128,7 @@ static bool desktop_custom_event_callback(void* context, uint32_t event) { animation_manager_unload_and_stall_animation(desktop->animation_manager); } desktop_auto_lock_inhibit(desktop); - api_lock_unlock(desktop->animation_lock); + furi_semaphore_release(desktop->animation_semaphore); return true; case DesktopGlobalAfterAppFinished: animation_manager_load_and_continue_animation(desktop->animation_manager); @@ -280,6 +278,7 @@ void desktop_set_stealth_mode_state(Desktop* desktop, bool enabled) { Desktop* desktop_alloc(void) { Desktop* desktop = malloc(sizeof(Desktop)); + desktop->animation_semaphore = furi_semaphore_alloc(1, 0); desktop->animation_manager = animation_manager_alloc(); desktop->gui = furi_record_open(RECORD_GUI); desktop->scene_thread = furi_thread_alloc(); diff --git a/applications/services/desktop/desktop_i.h b/applications/services/desktop/desktop_i.h index 06ca70bda..c0b29f922 100644 --- a/applications/services/desktop/desktop_i.h +++ b/applications/services/desktop/desktop_i.h @@ -20,7 +20,6 @@ #include #include -#include #define STATUS_BAR_Y_SHIFT 13 @@ -82,7 +81,7 @@ struct Desktop { bool in_transition : 1; - FuriApiLock animation_lock; + FuriSemaphore* animation_semaphore; }; Desktop* desktop_alloc(void); diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index eb2dc3aec..87d077de4 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -47,10 +47,24 @@ LoaderStatus return result.value; } -static void loader_show_gui_error(LoaderStatus status, FuriString* error_message) { - // TODO FL-3522: we have many places where we can emit a double start, ex: desktop, menu - // so i prefer to not show LoaderStatusErrorAppStarted error message for now - if(status == LoaderStatusErrorUnknownApp || status == LoaderStatusErrorInternal) { +static void + loader_show_gui_error(LoaderStatus status, const char* name, FuriString* error_message) { + if(status == LoaderStatusErrorUnknownApp && + loader_find_external_application_by_name(name) != NULL) { + // Special case for external apps + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_header(message, "Update needed", 64, 3, AlignCenter, AlignTop); + dialog_message_set_buttons(message, NULL, NULL, NULL); + dialog_message_set_icon(message, &I_WarningDolphinFlip_45x42, 83, 22); + dialog_message_set_text( + message, "Update firmware\nto run this app", 3, 26, AlignLeft, AlignTop); + dialog_message_show(dialogs, message); + dialog_message_free(message); + furi_record_close(RECORD_DIALOGS); + } else if(status == LoaderStatusErrorUnknownApp || status == LoaderStatusErrorInternal) { + // TODO FL-3522: we have many places where we can emit a double start, ex: desktop, menu + // so i prefer to not show LoaderStatusErrorAppStarted error message for now DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); DialogMessage* message = dialog_message_alloc(); dialog_message_set_header(message, "Error", 64, 0, AlignCenter, AlignTop); @@ -76,7 +90,7 @@ LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const FuriString* error_message = furi_string_alloc(); LoaderStatus status = loader_start(loader, name, args, error_message); - loader_show_gui_error(status, error_message); + loader_show_gui_error(status, name, error_message); furi_string_free(error_message); return status; } @@ -85,11 +99,11 @@ void loader_start_detached_with_gui_error(Loader* loader, const char* name, cons furi_check(loader); furi_check(name); - LoaderMessage message; - - message.type = LoaderMessageTypeStartByNameDetachedWithGuiError; - message.start.name = name ? strdup(name) : NULL; - message.start.args = args ? strdup(args) : NULL; + LoaderMessage message = { + .type = LoaderMessageTypeStartByNameDetachedWithGuiError, + .start.name = name ? strdup(name) : NULL, + .start.args = args ? strdup(args) : NULL, + }; furi_message_queue_put(loader->queue, &message, FuriWaitForever); } @@ -581,7 +595,7 @@ int32_t loader_srv(void* p) { FuriString* error_message = furi_string_alloc(); LoaderStatus status = loader_do_start_by_name( loader, message.start.name, message.start.args, error_message); - loader_show_gui_error(status, error_message); + loader_show_gui_error(status, message.start.name, error_message); if(message.start.name) free((void*)message.start.name); if(message.start.args) free((void*)message.start.args); furi_string_free(error_message); diff --git a/applications/system/js_app/modules/js_badusb.c b/applications/system/js_app/modules/js_badusb.c index 1722f6672..42e677842 100644 --- a/applications/system/js_app/modules/js_badusb.c +++ b/applications/system/js_app/modules/js_badusb.c @@ -133,17 +133,20 @@ static bool setup_parse_params( if(mjs_is_string(layout_obj)) { size_t str_len = 0; const char* str_temp = mjs_get_string(mjs, &layout_obj, &str_len); - File* file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); - size_t size = sizeof(badusb->layout); - - if((str_len == 0) || (str_temp == NULL) || - !storage_file_open(file, str_temp, FSAM_READ, FSOM_OPEN_EXISTING) || - storage_file_read(file, badusb->layout, size) != size) { - memcpy(badusb->layout, hid_asciimap, MIN(sizeof(hid_asciimap), size)); + if((str_len == 0) || (str_temp == NULL)) { + return false; } - + File* file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + bool layout_loaded = storage_file_open(file, str_temp, FSAM_READ, FSOM_OPEN_EXISTING) && + storage_file_read(file, badusb->layout, sizeof(badusb->layout)) == + sizeof(badusb->layout); storage_file_free(file); furi_record_close(RECORD_STORAGE); + if(!layout_loaded) { + return false; + } + } else { + memcpy(badusb->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(badusb->layout))); } return true; From 52d97b9329dcd8f1e3bc34c659beac14b1c57fa0 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 6 Apr 2024 06:44:08 +0300 Subject: [PATCH 039/129] update readme and fix hex uppercase --- CHANGELOG.md | 5 +- ReadMe.md | 50 ++++++++++--------- .../main/subghz/scenes/subghz_scene_set_cnt.c | 4 +- .../main/subghz/scenes/subghz_scene_set_fix.c | 2 +- .../subghz/scenes/subghz_scene_set_seed.c | 2 +- 5 files changed, 34 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f0aadb40..41e3ff76d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -104,13 +104,14 @@ |cloudtips|only RU payments accepted|https://pay.cloudtips.ru/p/7b3e9d65| |YooMoney|only RU payments accepted|https://yoomoney.ru/fundraise/XA49mgQLPA0.221209| |USDT|(TRC20)|`TSXcitMSnWXUFqiUfEXrTVpVewXy2cYhrs`| -|BCH||`qquxfyzntuqufy2dx0hrfr4sndp0tucvky4sw8qyu3`| |ETH|(BSC/ERC20-Tokens)|`darkflippers.eth` (or `0xFebF1bBc8229418FF2408C07AF6Afa49152fEc6a`)| |BTC||`bc1q0np836jk9jwr4dd7p6qv66d04vamtqkxrecck9`| +|SOL|(Solana/Tokens)|`DSgwouAEgu8iP5yr7EHHDqMNYWZxAqXWsTEeqCAXGLj8`| |DOGE||`D6R6gYgBn5LwTNmPyvAQR6bZ9EtGgFCpvv`| |LTC||`ltc1q3ex4ejkl0xpx3znwrmth4lyuadr5qgv8tmq8z9`| +|BCH||`qquxfyzntuqufy2dx0hrfr4sndp0tucvky4sw8qyu3`| |XMR|(Monero)| `41xUz92suUu1u5Mu4qkrcs52gtfpu9rnZRdBpCJ244KRHf6xXSvVFevdf2cnjS7RAeYr5hn9MsEfxKoFDRSctFjG5fv1Mhn`| -|TON||`EQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmpGf`| +|TON||`UQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmsxa`| #### Thanks to our sponsors who supported project in the past and special thanks to sponsors who supports us on regular basis: ClaraCrazy, Pathfinder [Count Zero cDc], callmezimbra, Quen0n, MERRON, grvpvl (lvpvrg), art_col, ThurstonWaffles, Moneron, UterGrooll, LUCFER, Northpirate, zloepuzo, T.Rat, Alexey B., ionelife, ... diff --git a/ReadMe.md b/ReadMe.md index be41a8c1c..1567936bd 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,6 +1,6 @@

-fzCUSTOM +Unleashed Firmware Logo

@@ -20,15 +20,15 @@ ### Welcome to the Flipper Zero Unleashed Firmware repo! -#### **This firmware is a fork from** [flipperdevices/flipperzero-firmware](https://github.com/flipperdevices/flipperzero-firmware) +#### **This firmware is a fork from original (OFW) firmware** [flipperdevices/flipperzero-firmware](https://github.com/flipperdevices/flipperzero-firmware)
-### Most stable custom firmware focused on new features and improvements of original firmware components, with almost no UI changes +### Most stable custom firmware focused on new features and improvements of original firmware components, keeping compatibility with original firmware API and Apps
-##### This software is for experimental purposes only and is not meant for any illegal activity/purposes.
We do not condone illegal activity and strongly encourage keeping transmissions to legal/valid uses allowed by law.
Also, this software is made without any support from Flipper Devices and is in no way related to the official devs. +##### This software is for experimental purposes only and is not meant for any illegal activity/purposes.
We do not condone illegal activity and strongly encourage keeping transmissions to legal/valid uses allowed by law.
Also, this software is made without any support from Flipper Devices and is in no way related to the official team.
@@ -36,7 +36,7 @@ ## FAQ (frequently asked questions) [Follow this link to find answers to most asked questions](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/FAQ.md) -## Dev builds (unstable) +## Dev builds (unstable) (built automatically from dev branch) - https://dev.unleashedflip.com/ - https://t.me/kotnehleb ## Releases in Telegram @@ -45,19 +45,20 @@ # What's changed - **Sub-GHz** *lib & hal* - Regional TX restrictions removed - - Extra Sub-GHz frequencies + - Extra Sub-GHz frequencies added - Frequency range can be extended in settings file (Warning: It can damage Flipper's hardware) - Many rolling code [protocols](https://github.com/DarkFlippers/unleashed-firmware#current-modified-and-new-sub-ghz-protocols-list) now have the ability to save & send captured signals - FAAC SLH (Spa) & BFT Mitto (keeloq secure with seed) manual creation - External CC1101 module support [(by quen0n)](https://github.com/DarkFlippers/unleashed-firmware/pull/307) - **Sub-GHz** *Main App* - - Save last used frequency [(by derskythe)](https://github.com/DarkFlippers/unleashed-firmware/pull/77) + - Save last used settings [(by derskythe)](https://github.com/DarkFlippers/unleashed-firmware/pull/77) - New frequency analyzer [(by ClusterM)](https://github.com/DarkFlippers/unleashed-firmware/pull/43) - Press OK in frequency analyzer to use detected frequency in Read modes [(by derskythe)](https://github.com/DarkFlippers/unleashed-firmware/pull/77) - Long press OK button in Sub-GHz Frequency analyzer to switch to Read menu [(by derskythe)](https://github.com/DarkFlippers/unleashed-firmware/pull/79) - New option to use timestamps + protocol name when you saving file, instead of random name or timestamp only - Enable in `Radio Settings -> Protocol Names = ON` - Read mode UI improvements (shows time when signal was received) (by @wosk) - External CC1101 module support (Hardware SPI used) + - External CC1101 module amplifier control (or LED control) support (enabled by default) - **Hold right in received signal list to delete selected signal** - **Custom buttons for Keeloq / Alutech AT4N / Nice Flor S / Somfy Telis / Security+ 2.0 / CAME Atomo** - now you can use arrow buttons to send signal with different button code - `Add manually` menu extended with new protocols @@ -75,12 +76,11 @@ - Recompiled IR TV Universal Remote for ALL buttons - Universal remotes for Projectors, Fans, A/Cs and Audio(soundbars, etc.) -> Also always updated and verified by our team - Infrared -> `RCA` Protocol - - Infrared -> Debug TX PIN output settings + - Infrared -> External IR modules support (with autodetect by OFW) - **NFC/RFID/iButton** - * LFRFID/iButton Fuzzer plugins - * Extra Mifare Classic keys + * LFRFID and iButton Fuzzer plugins + * Extra Mifare Classic keys in system dict * EMV Protocol + Public data parser (by @Leptopt1los and @wosk) - * NFC/LFRFID - Stop emulation after 5 mins to avoid antenna damage (by @Leptopt1los) * NFC `Add manually` -> Mifare Classic with custom UID * NFC parsers: Umarsh, Zolotaya Korona, Kazan, Metromoney, Moscow Social Card, Troika (reworked) and [many others](https://github.com/DarkFlippers/unleashed-firmware/tree/dev/applications/main/nfc/plugins/supported_cards) (by @Leptopt1los and @assasinfil) - **Quality of life & other features** @@ -91,7 +91,7 @@ - Battery percentage display with different styles `Settings -> Desktop -> Battery View` - More games in Dummy Mode -> click or hold any of arrow buttons - Lock device with pin(or regular lock if pin not set) by holding UP button on main screen [(by an4tur0r)](https://github.com/DarkFlippers/unleashed-firmware/pull/107) - - **BadBT** plugin (BT version of BadKB) [(by Willy-JL, ClaraCrazy, XFW contributors)](https://github.com/ClaraCrazy/Flipper-Xtreme/tree/dev/applications/main/bad_kb) - (See in Applications->Tools) - (aka BadUSB via Bluetooth) + - **BadKB** plugin [(by Willy-JL, ClaraCrazy, XFW contributors)](https://github.com/Flipper-XFW/Xtreme-Firmware/tree/dev/applications/main/bad_kb) - (See in Applications->Tools) - (aka BadUSB via Bluetooth) - BadUSB -> Keyboard layouts [(by rien > dummy-decoy)](https://github.com/dummy-decoy/flipperzero-firmware/tree/dummy_decoy/bad_usb_keyboard_layout) - Custom community plugins and games added + all known working apps can be downloaded in extra pack in every release - Other small fixes and changes throughout @@ -100,16 +100,16 @@ Also check the [changelog in releases](https://github.com/DarkFlippers/unleashed-firmware/releases) for latest updates! ### Current modified and new Sub-GHz protocols list: -Thanks to Official team (to their SubGHz Developer, Skorp) for implementing decoders for these protocols in OFW. +Thanks to Official team (to their SubGHz Developer, Skorp) for implementing support (decoder + encoder / or decode only) for these protocols in OFW. Keeloq [Not ALL systems supported for decode or emulation!] - [Supported manufacturers list](https://pastes.io/raw/unuj9bhe4m) -Encoders or sending made by @xMasterX: -- Nero Radio 57bit (+ 56bit encoder improvements) -- CAME 12bit/24bit encoder fixes (Fixes now merged in OFW) +Encoders or emulation support made by @xMasterX: +- Nero Radio 57bit (+ 56bit support) +- CAME 12bit/24bit encoder fixes (Fixes are now merged in OFW) - Keeloq: Dea Mio, Genius Bravo, GSN, HCS101, AN-Motors, JCM Tech, MHouse, Nice Smilo, DTM Neo, FAAC RC,XT, Mutancode, Normstahl, Beninca + Allmatic, Stilmatic, CAME Space, Aprimatic (model TR and similar), Centurion Nova (thanks Carlos !) -Encoders or sending made by @Eng1n33r(first implementation in Q2 2022) & @xMasterX (current version): +Encoders or emulation made by @Eng1n33r(first implementation in Q2 2022) and @xMasterX (current version): - CAME Atomo -> Update! check out new [instructions](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md) - Nice Flor S -> How to create new remote - [instructions](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md) - FAAC SLH (Spa) -> Update!!! Check out new [instructions](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md) @@ -117,7 +117,7 @@ Encoders or sending made by @Eng1n33r(first implementation in Q2 2022) & @xMaste - Star Line - Security+ v1 & v2 (encoders was made in OFW) -Encoders made by @assasinfil & @xMasterX: +Encoders made by @assasinfil and @xMasterX: - Somfy Telis -> How to create new remote - [instructions](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md) - Somfy Keytis - KingGates Stylo 4k @@ -126,10 +126,9 @@ Encoders made by @assasinfil & @xMasterX: ## Please support development of the project The majority of this project is developed and maintained by me, @xMasterX. -I'm unemployed, and the only income I receive is from your donations. Our team is small and the guys are working on this project as much as they can solely based on the enthusiasm they have for this project and the community. -- @Leptopt1los - NFC, RFID, IR Assets (only ACs), Plugins, and many other things -- @gid9798 - SubGHz, Plugins, many other things +- @Leptopt1los - NFC, RFID, Plugins, and many other things +- @gid9798 - SubGHz, Plugins, many other things - currently offline :( - @assasinfil - SubGHz protocols, NFC parsers - @Svaarich - UI design and animations - @amec0e - Infrared assets @@ -146,13 +145,14 @@ You can support us by using links or addresses below: |cloudtips|only RU payments accepted|https://pay.cloudtips.ru/p/7b3e9d65| |YooMoney|only RU payments accepted|https://yoomoney.ru/fundraise/XA49mgQLPA0.221209| |USDT|(TRC20)|`TSXcitMSnWXUFqiUfEXrTVpVewXy2cYhrs`| -|BCH||`qquxfyzntuqufy2dx0hrfr4sndp0tucvky4sw8qyu3`| |ETH|(BSC/ERC20-Tokens)|`darkflippers.eth` (or `0xFebF1bBc8229418FF2408C07AF6Afa49152fEc6a`)| |BTC||`bc1q0np836jk9jwr4dd7p6qv66d04vamtqkxrecck9`| +|SOL|(Solana/Tokens)|`DSgwouAEgu8iP5yr7EHHDqMNYWZxAqXWsTEeqCAXGLj8`| |DOGE||`D6R6gYgBn5LwTNmPyvAQR6bZ9EtGgFCpvv`| |LTC||`ltc1q3ex4ejkl0xpx3znwrmth4lyuadr5qgv8tmq8z9`| +|BCH||`qquxfyzntuqufy2dx0hrfr4sndp0tucvky4sw8qyu3`| |XMR|(Monero)| `41xUz92suUu1u5Mu4qkrcs52gtfpu9rnZRdBpCJ244KRHf6xXSvVFevdf2cnjS7RAeYr5hn9MsEfxKoFDRSctFjG5fv1Mhn`| -|TON||`EQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmpGf`| +|TON||`UQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmsxa`| ## Community apps included @@ -168,6 +168,8 @@ See full list and sources here: [xMasterX/all-the-plugins](https://github.com/xM ## [How to install](/documentation/HowToInstall.md) - [versions info](/CHANGELOG.md#recommended-update-option---web-updater): `n`,` `,`e`... ## Firmware & Development +### - **Developer Documentation** - [developer.flipper.net](https://developer.flipper.net/flipperzero/doxygen) + ### - **[How to build](/documentation/HowToBuild.md#how-to-build-by-yourself) | [Project-structure](#project-structure)** ### - **CLion IDE** - How to setup workspace for flipper firmware development [by Savely Krasovsky](https://krasovs.ky/2022/11/01/flipper-zero-clion.html) @@ -246,6 +248,8 @@ See full list and sources here: [xMasterX/all-the-plugins](https://github.com/xM * Official Docs: [docs.flipper.net](https://docs.flipper.net/) * Official Forum: [forum.flipperzero.one](https://forum.flipperzero.one/) +* Update! Developer Documentation [developer.flipper.net](https://developer.flipper.net/flipperzero/doxygen) + # Project structure - `applications` - Applications and services used in firmware diff --git a/applications/main/subghz/scenes/subghz_scene_set_cnt.c b/applications/main/subghz/scenes/subghz_scene_set_cnt.c index 4952c2b14..b202d6d91 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_cnt.c +++ b/applications/main/subghz/scenes/subghz_scene_set_cnt.c @@ -18,7 +18,7 @@ void subghz_scene_set_cnt_on_enter(void* context) { switch(state) { case SetTypeBFTClone: - byte_input_set_header_text(byte_input, "Enter COUNTER in Hex"); + byte_input_set_header_text(byte_input, "Enter COUNTER in hex"); byte_input_set_result_callback( byte_input, subghz_scene_set_cnt_byte_input_callback, @@ -29,7 +29,7 @@ void subghz_scene_set_cnt_on_enter(void* context) { break; case SetTypeFaacSLH_Manual_433: case SetTypeFaacSLH_Manual_868: - byte_input_set_header_text(byte_input, "Enter COUNTER in Hex 20 bits"); + byte_input_set_header_text(byte_input, "Enter COUNTER in hex 20 bits"); byte_input_set_result_callback( byte_input, subghz_scene_set_cnt_byte_input_callback, diff --git a/applications/main/subghz/scenes/subghz_scene_set_fix.c b/applications/main/subghz/scenes/subghz_scene_set_fix.c index b58f39ddd..7564fb24d 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_fix.c +++ b/applications/main/subghz/scenes/subghz_scene_set_fix.c @@ -13,7 +13,7 @@ void subghz_scene_set_fix_on_enter(void* context) { // Setup view ByteInput* byte_input = subghz->byte_input; - byte_input_set_header_text(byte_input, "Enter FIX in Hex"); + byte_input_set_header_text(byte_input, "Enter FIX in hex"); byte_input_set_result_callback( byte_input, subghz_scene_set_fix_byte_input_callback, diff --git a/applications/main/subghz/scenes/subghz_scene_set_seed.c b/applications/main/subghz/scenes/subghz_scene_set_seed.c index a22daf919..c8301745f 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_seed.c +++ b/applications/main/subghz/scenes/subghz_scene_set_seed.c @@ -14,7 +14,7 @@ void subghz_scene_set_seed_on_enter(void* context) { // Setup view ByteInput* byte_input = subghz->byte_input; - byte_input_set_header_text(byte_input, "Enter SEED in Hex"); + byte_input_set_header_text(byte_input, "Enter SEED in hex"); byte_input_set_result_callback( byte_input, subghz_scene_set_seed_byte_input_callback, From d1c75dc57bcda1865cb0ffe797bab746ecde0f01 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 6 Apr 2024 07:08:55 +0300 Subject: [PATCH 040/129] update changelog --- CHANGELOG.md | 119 ++++++++++++++++----------------------------------- 1 file changed, 36 insertions(+), 83 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41e3ff76d..0066ec7e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,88 +1,41 @@ ## New changes -* NFC: **EMV Fixes and imporvements** (old saved files may be not compatible now) (by @wosk & @Leptopt1los | PR #702) -* NFC: **Parsers refactoring** (by @Leptopt1los) -* NFC: **Kazan parser improved** - token parse option added (by @Leptopt1los) -* NFC: **Update ndef parser**, mf classic dict changes (by @Willy-JL) -* RFID: Test swap of em4100 t5577 blocks (details in issue 3463 OFW) -* RFID: Fix RAW read crash (by @Willy-JL) -* Infrared: Update universal remote assets (by @amec0e | PR #718 #719) -* SubGHz: Add 430.50 mhz (by @MizumasuShoichi | PR #721) -* SubGHz: **Magellan Event Code Update** (by @wooferguy | PR #713) -* SubGHz: Reduce subghz add manually scene flash size (by @Willy-JL) -* SubGHz: Fix led blink on decode raw > signal info (by @Willy-JL) -* HID App: apply fix for ms teams on macos (by @cpressland) -* HID App: merge official fw hid app keyboard changes -* Misc: Use non prefixed names for regular files with random name -* Misc: Revert usb cdc config changes to verify issue (storage timeout during firmware update) (OFW 3452) -* Misc: Fixes for text box and uart echo demo app, remove duplicated emv parser plugin (by @Willy-JL) -* Expansion `is_connected` API to check for VGM (by @HaxSam) -* New JavaScript Modules `UsbDisk`,`badusb.quit() + altstring`,`SubGHz`,`Submenu`,`BleBeacon`,`Keyboard`,`Math`,`GPIO`, `textbox` (by @Willy-JL, @Spooks4576, @Sil333033, @oldip) -* Apps: **BadBT renamed and moved from Apps-Bluetooth to Apps-Tools as BadKB** -* Apps: Added **FindMy Flipper** app -* Apps: NFC Magic - **Gen4 improvements** +* NFC: CharlieCard parser (by @zacharyweiss) +* SubGHz: Enabled tx-rx state on unused gpio pin by default (**external amp option was removed and is enabled by default now**) +* SubGHz: Status output !TX/RX on the GDO2 CC1101 pin (by @quen0n | PR #742) +* SubGHz: Reworked saved settings (by @xMasterX and @Willy-JL) +* Desktop: Fixes for animation unload (by @Willy-JL) +* Misc: Added `void` due to `-Wstrict-prototypes` +* Misc: Some code cleanup and proper log levels in nfc parsers +* Infrared: Allow external apps to use infrared settings (by @Willy-JL) +* JS & HAL: Various fixes and FURI_HAL_RANDOM_MAX define added (by @Willy-JL) +* JS: BadUSB layout support (by @Willy-JL) +* JS: Module `widget` and path globals (by @jamisonderek) +* Apps: NFC Magic - **Gen2 writing support** (by @Astrrra) +* Apps: MFKey - fixed crashes (by @noproto) * Apps: **Check out Apps updates by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) -* OFW: BLE/GAP fixes -* OFW: Add support for Pioneer SR IR remotes -* OFW: fbt/ufbt: Ensure POSIX paths are passed to GDB on all platforms -* OFW: Add support for DEFAULT_STRING_DELAY in Bad USB App -* OFW: Adding F13-F24 function key support to BadUSB -* OFW PR 3532: NFC UI fixes (by gornekich) -* OFW PR 3504: NFC: **Slix privacy password reveal and Desfire detect fix** (by gornekich) -* OFW: **Infrared fixes and more** -* OFW: NFC Parsers cosmetic fixes -* OFW: NFC wording fixes -* OFW: **Fix iButton emulation regression** -* OFW: Add the Freedom_2_dolphins animation -* OFW: Infrared: Add Fujitsu ASYG24KMTB -* OFW: Asynchronous Infrared remote manipulation -* OFW: Fix troika 4K keys -* OFW: Archive: Fix item focus after aborting the Delete operation -* OFW: Troyka parser improvements (by UL Team) -* OFW: NFC: Fix washcity plugin verify function being to greedy -* OFW: Parser for Santiago, Chile BIP transit card -* OFW: WiFi board: fixed update script on Windows (unfortunately also Mac and Linux) -* OFW: Gui: reset canvas orientation and frame when entering direct draw mode -* OFW: FBT/uFBT: Enable C++20/GNU23 in VSCode IntelliSense -* OFW: Toolchain fixes -* OFW: Quote $FBT_TOOLCHAIN_PATH to avoid splitting -* OFW: **ble: profile rework** -* OFW: lfrfid/em4100: added support for different bit rates (16clk was added back into UL, still not reading properly) -* OFW: T5577 lib: write with mask function added -* OFW: Archive: fixed Apps tab ext filter -* OFW: FuriHalRtc refactor: new datetime lib (by UL Team) -* OFW: bit_lib and nfc_util refactor (by UL Team) -* OFW: Gui text box: fix formatted string memory reservation -* OFW: JS debug disabled, archive and file browser fixes -* OFW: VSCode integration fixes for new toolchain -* OFW: **FIX ISO15693 emulation** -* OFW: JS serial module renamed, uart channel selection -* OFW: mjs: minor fixes -* OFW: **JavaScript runner** -* OFW: Fixed MyKey check LockID -* OFW: Check universal remote files before loading -* OFW: NFC: fix retry scene navigation logic -* OFW: Expansion module service improvements -* OFW: New toolchain with gcc 12 (+ aarch64 support!) -* OFW: HID app: keyboard modifiers fix -* OFW: CLI: cat command crash workaround -* OFW: NFC: Custom UID entry when adding manually -* OFW: Added NFC plugin; Some parser -* OFW: **Slix disable privacy** (Unlock SLIX-L) -* OFW: NFC: **Add support for Gallagher access control** (MIFARE Classic only) -* OFW: furi/core/timer: resolve timer handle use-after-free post deletion -* OFW: FuriHal: various GPIO improvements -* OFW: GUI: canvas commit callback has been moved to canvas. Direct Draw apps can now be streamed via RPC. -* OFW: nfc app: fix incorrect protocol detection in save scene (by UL Team) -* OFW: NFC: **MFC Unlock with Dictionary** -* OFW: ITSO Parser (UK) -* OFW: NFC: fix application opening from browser -* OFW: Rework more info scene for Ultralight cards -* OFW: NFC UI refactor -* OFW: Add an NFC parser for the San Francisco Bay Area "Clipper" transit card. -* OFW: Fix nfc_protocol_support_scene_save_name_on_event crash -* OFW: NFC: Display unread Mifare Classic bytes as question marks -* OFW: Troika layout fixes -* OFW: NFC: MF Classic parsers read() fix (dictionary attack skip) +* OFW: Desktop: ensure that animation is unloaded before app start (fixes some out of memory crashes) +* OFW: Hide unlock with reader for MFU-C +* OFW: fbt: fixed missing FBT_FAP_DEBUG_ELF_ROOT to dist env +* OFW: fbt: added -Wstrict-prototypes for main firmware +* OFW: Mifare Ultralight naming fix +* OFW: IR: Remember OTG state +* OFW: Bad USB: fix crash when selecting a keyboard layout +* OFW: L1_Mods animation update : adding VGM visual +* OFW: RFID Improvements +* OFW: Fixed plugins and UI +* OFW: NFC: Fix mf desfire detect +* OFW: infrared_transmit.h was missing `#pragma once` +* OFW: Show the wrong PIN Attempt count on the login screen +* OFW: SavedStruct: Introduce saved_struct_get_metadata +* OFW: JS CLI command +* OFW: Add ChromeOS Bad USB demo +* OFW: Configurable Infrared TX output (previous UL version is replaced with OFW version, new features added "AutoDetect" and saving settings) +* OFW: BadUSB: BLE, media keys, Fn/Globe key commands +* OFW: NFC: Slix privacy password reveal and Desfire detect fix +* OFW: github: additional pre-upload checks for doxygen workflow +* OFW: NFC UI fixes +* OFW: Gui: unicode support, new canvas API +* OFW: Api Symbols: replace asserts with checks

#### Known NFC post-refactor regressions list: - Mifare Mini clones reading is broken (original mini working fine) (OFW) From aee2120dceb43fdd2ea631a898818d5bcb0da10b Mon Sep 17 00:00:00 2001 From: Nick Shaw Date: Sat, 6 Apr 2024 10:04:33 -0400 Subject: [PATCH 041/129] Storage: Virtual volume label is now set from Image Name --- applications/services/storage/storage_glue.c | 6 ++++++ applications/services/storage/storage_glue.h | 1 + applications/services/storage/storages/storage_ext.c | 7 ++++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/applications/services/storage/storage_glue.c b/applications/services/storage/storage_glue.c index 41da6c3f4..63bb017fd 100644 --- a/applications/services/storage/storage_glue.c +++ b/applications/services/storage/storage_glue.c @@ -156,3 +156,9 @@ size_t storage_open_files_count(StorageData* storage) { size_t count = StorageFileList_size(storage->files); return count; } + +const char* storage_file_get_path(File* file, StorageData* storage) { + if(!storage_has_file(file, storage)) return ""; + StorageFile* storage_file_ref = storage_get_file(file, storage); + return furi_string_get_cstr(storage_file_ref->path); +} \ No newline at end of file diff --git a/applications/services/storage/storage_glue.h b/applications/services/storage/storage_glue.h index fbc08ebbf..9a19ae815 100644 --- a/applications/services/storage/storage_glue.h +++ b/applications/services/storage/storage_glue.h @@ -35,6 +35,7 @@ void storage_file_init(StorageFile* obj); void storage_file_init_set(StorageFile* obj, const StorageFile* src); void storage_file_set(StorageFile* obj, const StorageFile* src); void storage_file_clear(StorageFile* obj); +const char* storage_file_get_path(File* file, StorageData* storage); void storage_data_init(StorageData* storage); StorageStatus storage_data_status(StorageData* storage); diff --git a/applications/services/storage/storages/storage_ext.c b/applications/services/storage/storages/storage_ext.c index dfd13f00b..0114d8dcc 100644 --- a/applications/services/storage/storages/storage_ext.c +++ b/applications/services/storage/storages/storage_ext.c @@ -1,6 +1,7 @@ #include "fatfs.h" #include "../filesystem_api_internal.h" #include "storage_ext.h" +#include "storage/storage_glue.h" #include #include "sd_notify.h" #include @@ -740,7 +741,11 @@ FS_Error storage_process_virtual_format(StorageData* storage) { uint8_t* work = malloc(_MAX_SS); SDError error = f_mkfs(sd_data->path, FM_ANY, 0, work, _MAX_SS); storage_process_virtual_mount(storage); - f_setlabel("DOLPHIN"); + const char* path = storage_file_get_path(mnt_image, mnt_image_storage); + char* label = basename(path); + int len = strlen(label); + label[len - 4] = '\0'; // truncate the .img extension + f_setlabel(label); storage_process_virtual_unmount(storage); free(work); if(error != FR_OK) return FSE_INTERNAL; From d05080820d3222ff62f24677ae85001e1deb1a46 Mon Sep 17 00:00:00 2001 From: Nick Shaw Date: Sat, 6 Apr 2024 11:21:23 -0400 Subject: [PATCH 042/129] Storage: Updated virtual storage label to use strlcpy --- applications/services/storage/storages/storage_ext.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/applications/services/storage/storages/storage_ext.c b/applications/services/storage/storages/storage_ext.c index 0114d8dcc..f219005a8 100644 --- a/applications/services/storage/storages/storage_ext.c +++ b/applications/services/storage/storages/storage_ext.c @@ -742,9 +742,9 @@ FS_Error storage_process_virtual_format(StorageData* storage) { SDError error = f_mkfs(sd_data->path, FM_ANY, 0, work, _MAX_SS); storage_process_virtual_mount(storage); const char* path = storage_file_get_path(mnt_image, mnt_image_storage); - char* label = basename(path); - int len = strlen(label); - label[len - 4] = '\0'; // truncate the .img extension + const char* name = basename(path); + char label[strlen(name) - 3]; + strlcpy(label, name, sizeof(label)); f_setlabel(label); storage_process_virtual_unmount(storage); free(work); From 29c3813f683649f3ec7692e03bc7b94587da6e59 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 6 Apr 2024 23:16:29 +0100 Subject: [PATCH 043/129] Improve error handling, use correct drive id --- applications/services/storage/storage_glue.c | 2 +- .../services/storage/storages/storage_ext.c | 22 +++++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/applications/services/storage/storage_glue.c b/applications/services/storage/storage_glue.c index 63bb017fd..727af765f 100644 --- a/applications/services/storage/storage_glue.c +++ b/applications/services/storage/storage_glue.c @@ -158,7 +158,7 @@ size_t storage_open_files_count(StorageData* storage) { } const char* storage_file_get_path(File* file, StorageData* storage) { - if(!storage_has_file(file, storage)) return ""; StorageFile* storage_file_ref = storage_get_file(file, storage); + if(!storage_file_ref) return ""; return furi_string_get_cstr(storage_file_ref->path); } \ No newline at end of file diff --git a/applications/services/storage/storages/storage_ext.c b/applications/services/storage/storages/storage_ext.c index f219005a8..fb61f8086 100644 --- a/applications/services/storage/storages/storage_ext.c +++ b/applications/services/storage/storages/storage_ext.c @@ -5,6 +5,7 @@ #include #include "sd_notify.h" #include +#include typedef FIL SDFile; typedef DIR SDDir; @@ -740,15 +741,22 @@ FS_Error storage_process_virtual_format(StorageData* storage) { SDData* sd_data = storage->data; uint8_t* work = malloc(_MAX_SS); SDError error = f_mkfs(sd_data->path, FM_ANY, 0, work, _MAX_SS); - storage_process_virtual_mount(storage); - const char* path = storage_file_get_path(mnt_image, mnt_image_storage); - const char* name = basename(path); - char label[strlen(name) - 3]; - strlcpy(label, name, sizeof(label)); - f_setlabel(label); - storage_process_virtual_unmount(storage); free(work); if(error != FR_OK) return FSE_INTERNAL; + + if(storage_process_virtual_mount(storage) == FSE_OK) { + // Image file path + const char* img_path = storage_file_get_path(mnt_image, mnt_image_storage); + // Image file name + FuriString* img_name = furi_string_alloc(); + path_extract_filename_no_ext(img_path, img_name); + // Label with drive id prefix + char* label = storage_ext_drive_path(storage, furi_string_get_cstr(img_name)); + furi_string_free(img_name); + f_setlabel(label); + free(label); + storage_process_virtual_unmount(storage); + } return FSE_OK; #endif } From 9d43b3ccfd36f81c1150fccb688817b7b9d6ae24 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 6 Apr 2024 23:17:19 +0100 Subject: [PATCH 044/129] Fix FATfs drive IDs (0=/ext 1=/mnt) --- applications/services/storage/storage.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/applications/services/storage/storage.c b/applications/services/storage/storage.c index f2997ffcf..b51c0df6c 100644 --- a/applications/services/storage/storage.c +++ b/applications/services/storage/storage.c @@ -43,10 +43,12 @@ Storage* storage_app_alloc(void) { } #ifndef FURI_RAM_EXEC - storage_mnt_init(&app->storage[ST_MNT]); storage_int_init(&app->storage[ST_INT]); #endif storage_ext_init(&app->storage[ST_EXT]); +#ifndef FURI_RAM_EXEC + storage_mnt_init(&app->storage[ST_MNT]); +#endif // sd icon gui app->sd_gui.enabled = false; From aea1d0be9f89b72e638fb239bdcaccd1597f17de Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 6 Apr 2024 23:20:18 +0100 Subject: [PATCH 045/129] Cleanup import --- applications/services/storage/storages/storage_ext.c | 1 - 1 file changed, 1 deletion(-) diff --git a/applications/services/storage/storages/storage_ext.c b/applications/services/storage/storages/storage_ext.c index fb61f8086..971f1df17 100644 --- a/applications/services/storage/storages/storage_ext.c +++ b/applications/services/storage/storages/storage_ext.c @@ -1,7 +1,6 @@ #include "fatfs.h" #include "../filesystem_api_internal.h" #include "storage_ext.h" -#include "storage/storage_glue.h" #include #include "sd_notify.h" #include From fc4bfba8a3d1dc4501cff021bc92f24e4f99e682 Mon Sep 17 00:00:00 2001 From: Kuronons <110337784+Kuronons@users.noreply.github.com> Date: Sun, 7 Apr 2024 12:39:21 +0200 Subject: [PATCH 046/129] Update of NFC emulation pict Visual edits, perspective enhancement and re-centering on y axis. -> NFC code edit to follow --- .../Icons/NFC/NFC_dolphin_emulation_51x64.png | Bin 2479 -> 2156 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/packs/Momentum/Icons/NFC/NFC_dolphin_emulation_51x64.png b/assets/packs/Momentum/Icons/NFC/NFC_dolphin_emulation_51x64.png index c8d8ff8ef41f1657a925c174525b50b86fbf3cfb..853e95202aa792748cde67d9ada9adce2b02f9a9 100644 GIT binary patch delta 800 zcmZ24{6=7c1ruA^Myq%xF7prrQ!4{QD^v5yixfhrek-5K_l$8uEO)Qg=&2^K_ z&C_(#OiWC5lT0m*bj?!J%q%PuO^p)MCOa@|tE8nSnV1?F80n^%7@O!?n3|aBCK@JL z>L!_+C7CBDrKK7eT2Ag~R#7oYGcz(ZHZaz;NJ~xBO))n~)J;rFP0=+rPct!0HL$R> zG`5_4owQl^qf*u}<^G4$C?p3W*Zxr!}OB?ag@!(nfT-=LAF7idO zUHBsGwal7h=FYn;%IS}k)>{=jFS{TR@%^X3%~P9YBUEmFSLQ2kdgC|QMki@v%0u&f z#d_^lsp(%-^P_Ua+%}|ao}VEc{Cdw1C%f6xoy57H`AEMzd+mez=g9cJrzfNMRgEJv zTu)b>sA+rhusEf1)3upr%!+Sj`b{v)-xZ}ZPsc{6ryw@PYH2Qe$oU`le(~r?9^pGx zz!q}yfc=--JqDf7q}y4ExMK+UZYOdf-rP^5$1l&twUoc{HhU&8m_$e~#X_UK^sdVa|HJW!KZL zF1z#W?CFxHn?((!z4v-%Nd9Je*JJ+i?&le2xwc2traZD=P_DrqV6)Y2@^RIpPY*o% rp?Z6s)?E$Bo0~PHH+}EbP0l+XkK3?V!^ delta 983 zcma)2eN0tl9L9SuA38A21=42ez1J+|mGgd_^S#D&t&3xXv$jP9HDW)2b8RLMd1V(v`0f}H)kwm5$5SzQLPA8K@QCAEB zNHUUuC=(s128DpDgzH>kGDO6@Y~B98XKcN&*J{bmO1PPo2#_|8%Oe@$`X;IUl7c!r-k{r(45K znQM0U#)Jl2E9(OvAFNqACLP}sem2AZMPv0)zjxNIU}~+s~eA<76TKRJW1)<6epFQ%zg>OriJ=C|qSi5cb%waR{x_4+> z*OlQ=p?4vV3pE}2>P+v^0aSbK^VGn*4;~izH^k>}K6s#|u>F2g)68y1b)dbcD&L7q z@8SEd4Gotf^Q=`{U)q<{q9?`Iw|kOP(stYp*ECFR;W$0mGqh*lkBJMOO8F#Mc%%Q7 z2m5{Xp0$0}8G#!5{xyA9UTb<@d-2r!D@D^HsV&t3o!$WrTQ_<{S67l(!Jr~ Date: Sun, 7 Apr 2024 12:42:23 +0200 Subject: [PATCH 047/129] Change y value for graphic asset in nfc_protocol_support.c Set y=0 for NFC_dolphin_emulation_51x64 asset so it is coherent with the other uses of this pict. --- .../main/nfc/helpers/protocol_support/nfc_protocol_support.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c index 5f41a0d89..3cfbebf67 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -574,7 +574,7 @@ static void nfc_protocol_support_scene_emulate_on_enter(NfcApp* instance) { FuriString* temp_str = furi_string_alloc(); const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); - widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_51x64); + widget_add_icon_element(widget, 0, 0, &I_NFC_dolphin_emulation_51x64); if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateUid)) { widget_add_string_element( From c983ac6ca2d17827a89de0bd44ac1cabcb1f12ac Mon Sep 17 00:00:00 2001 From: Kuronons <110337784+Kuronons@users.noreply.github.com> Date: Sun, 7 Apr 2024 12:44:09 +0200 Subject: [PATCH 048/129] MNTM Pack - adding iButton (success) yappy asset --- .../iButton/iButtonDolphinVerySuccess_92x55.png | Bin 0 -> 3372 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/packs/Momentum/Icons/iButton/iButtonDolphinVerySuccess_92x55.png diff --git a/assets/packs/Momentum/Icons/iButton/iButtonDolphinVerySuccess_92x55.png b/assets/packs/Momentum/Icons/iButton/iButtonDolphinVerySuccess_92x55.png new file mode 100644 index 0000000000000000000000000000000000000000..0ae1403da711f2f1b14367828910781505af0eac GIT binary patch literal 3372 zcmcIn4^R}>89$L`6l0>XL1Y5iEG7kUZ};~0ZrRlnfr9{YNP&1@{P%A6-QC8!+w1Of zpil)&EU7|cN}|>LQLIcSsah2sz!_~+Qq8CtG*i;riNsnp$!Ke*G1F!;?c4h&O+}cb zxw+eWyZ62C_kF+j`@Z+?#ftK$rzWK)84QN0CB^ni^t}vy*hvZK`p~idSJBtxU~#Q# zFie@De1wZMsZ?gDIn1CJc|p$4!9zb~Nj4K_UNmq%m$ zF^OYg6K%B(&(i_18b<|IAS)2F8Yx1cC^N=#Mw6AX(Cl1{rf7;Zp|^!Fu{@K*n`tci z!V#Jxy7@|bQ4|Z^*>JC>1$mOJudg@On~k#KAx#{|krYkRG=VG#bxlA64MafAh zRZyg$CdmOz_Xu2aoo2&Pq>&W-!MNLi8l?#lMmB&TX);oJN?}7$h}#A06kixz6iDcU zei+bHWNV7s2EDQ-t6q5&*YThK3jk5;aKth08H?W^hfuY`5K1FT$UUvq>NP<~Rzg*- zQv_HTLSbgavQf24c$d#{Oh@M7$w9tAfk2a$YFYNhR=FaUFGkZw6PD?e0-{{6X6b0r z3D6ESXv5KxGX!NOD5jb+@#xK1vh@KKF?GnIr2wUu!d>K;mpqi8iWX9hmRdgWL`ObpLCihJif)U@h=fI;-GoRnEWx>e z6`{FvXe(eT7fa74^}0fnu*fLlh`c`{0zf<$OL_!rqPSJ?NAU=-u)ujB=!I*;1zmJd z#G{X;d#um6n264=_d?|KK4OfZtFl|G2MWydptT%R?BuAy%YhJ7MuZ;(3PAl5Dyj{4 zE3zMp3lr013Xjy!03kuKz)iTU3{7x?)r^$K znuQ#b#X^Cc7{wzgiWDKun^~T+#)|NpP!y$K@PdE`qTPo47RfNQ*#&9H5-y8NAh>Wc z42^DCmqm1&7#f&eqbKvVj`AHRBaEqlF&16dMO-AKlO{q%8li!aI*W&Zg%$Vz!5dR? z<7=7`_WxIL-`OICJ65FKkYDdak$ZcD$s)Zv!{}#Pl&bG@%Aiz zzWi->0lja86CZITiakQ-EPWV1;GvuN5fKJZr7GwE_-p5uMFvB{#u9s8wfCc&+b?x& zpdPe;_3zws$DV0Bb!W5igYIY3=Tsk_R{n1N)qka(eB!Y4cv^1e(8Ne;RjK=j@vPtH+5GE`k7WEk=lFVK{$SaDOXrq^!c)UtPki`(VP#w5lKlhS52Y_X z$eh^M*fcz^?fvW1jR)J?rtf`xZhHOZyvvC%98LorSwFtH!#U@BXRZ_-_|UoT7nKRh zwk1UuS04%W-0J_sFAFldW>r^ZrcNovS|0zx@zLM*hT1nJRXQ# zTiNFF=0!ihwh5bc{YFK?lHGg!N4EMC;dV#riWy>y$K@F;Za%#&@B99`tv7a0AN=%> zw`bd0A3I;=!*}vMXFo4omN?XQ=W3Cfc^jPnU_k@3r*g+{SN2@H7|8s1?!=NZXVY_2 zGW)p3Yx$Sv{AJa;x(AX5VD1Y-N3!$1^(`0L7tPEJT}z(SQ(LjLC*$T%*DY>L`^n44 z9;qEvv$m#mc0QTs=#~yRmwxb@4K2&gEOb0|t~>dr_qlDpZ0DsNk1lK_Q(r5d9jctx zlCu1bId66iq&23j7+RsM+>`x9)}-xH6i^TCCC9bcw@x%1TdEf>zzU2QX;Z_ndO2cLT5 z^rDo`gFa*JA?M`YnxW782WEF=yw&&Fk;b<#G$o$BHx|K-9}m4c^Jlr! Yi+*+DAAMc=-ycIsVYz)@{;Jmh0DE literal 0 HcmV?d00001 From e92ff0020f125355904f9538d15fcdcb1966fd3f Mon Sep 17 00:00:00 2001 From: Kuronons <110337784+Kuronons@users.noreply.github.com> Date: Sun, 7 Apr 2024 12:48:49 +0200 Subject: [PATCH 049/129] MNTM Pack - Adding "wait" yappy asset Note - Only used so far in : iButton / Wardriver / SimonSays --- .../Momentum/Icons/Dolphin/DolphinWait_61x59.png | Bin 0 -> 3671 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/packs/Momentum/Icons/Dolphin/DolphinWait_61x59.png diff --git a/assets/packs/Momentum/Icons/Dolphin/DolphinWait_61x59.png b/assets/packs/Momentum/Icons/Dolphin/DolphinWait_61x59.png new file mode 100644 index 0000000000000000000000000000000000000000..c4ca5a174eda02a9e50aaeec9285d2988b8d182d GIT binary patch literal 3671 zcmd5<4R93I9Y3f}3Bq7uY#org-Oxdxd%JIMZ+CCWfk`eQha6xCNCuetZujlomb=}} z?&fl#g40M1vNrjiqf{-^AWUVbrG*e@Y*OfyfmYP2Op?C6kLAV* zBr~Nm-t66e{r=y-_y51!x_s%P$rGnf^!a>~qjiybd=BI9{0Zan@1n*#_Ty8TS=VU$ ze78+=zvFxzd+)>-Lt4XXXLW3eBpYczRE#+CXVWG|`+OA(vnG_+BZr8igrAa7VNhZJ(*0-_=-VFhXAKq8w?>9&*&lR3Q-zIGqe zB$0zS>%(NFYmitSTTWCP79w~*L&*#iAcT+~@LZ4;en7Ac%g_KHK?(>GCrSa9D11n4 z%~DjUK2lq-h3~>-(s4|QrdwKC{4D{$VI^n~3WaEfrCFB32+D5N9hjwbyS%6&f^6B+ zOh+?x!qo`lMza$pv8VYE(x#_Yw+m^)38S;nq=BDtL&^aa*~6L5Rw`#)k!h4dX{0+g z#sUwEvlNSY@DbIf(;nJ(YBJcPLTn?V?S@tp(e=nSnk^aCWU$5ZqGPsGkFKR=4B{B> z37bj7G3=yqog{d#y;PV|wS}N#SPh1eDpq-UQ47HWKTF*6fTk-(i#^A6u^@mV&_Q7m zmmE+Gi;o7Dm6)Iez&yZ643me(Fo_iCKo87O3_gSghRN}RpT}USBG^zgwRKdeB1@`a zr6EqSmWByLn|dN=Ef$lay6r$+M$t%^#IE`^O_7)Y5`$3SC;=eXSB{}z2!<#n1k{iK zAPNDYC?7H8X1DyVe10V;hKzN1d6NVdDTrkt#l%sN5)>{(K>-9Pzy|=Q00y7LqPAt0 zh8H7Dm1=dpQm{sZL<|*p3Nflk0gj7PaX!dW0wW;A3qceYWRkdMx7tVm{6JIEnE%J2uyG8A=yWUPw+tJ%Mn0rpfCD zBO7`KStYeFp#^bQMwT5WRm(^d9{mZ&AfRccG#R>8q%*ow=zJc5u5U*{mR6FsQY)Sd z#7(p}YLS4*B2{E$TqKsG;)r7@sBi&_Q{1ZWw;r~=5+TGve zdo}&lHlDwa*Jys6ljf8~foi_oCvudmh(v>&<*O}+`L$7N|#nD-b zhq^n#>_Y3Wf(dU3EEsuGLy-xvf-F!W+%hOp=2SdkaaR#l0FcTAd5#4YxfG|gYr2Xt z!=Q}f0K~Dhs^B5PVL>4-QmPsk0FVKPc)mnBrzh_eePi!)$dXMgHl2HfcgF6ewE^9Oytal_F_Wka&>L`T$mmzY%3Jbd5m!_S?0aIo^juEy7oY?#nE z?d5&F_i#I2X*=-Oo@Gr_e!uL=Z`MrD#7~{x+w}?f4ZY`=KiSC>C%;qn(%jFDT`z2! z+C3}TGc|kZw(vklMY3!1qlcTe-QHc1tbMZomMPtnt{gl!VZSxCyN#cD*X`Y@3tNU? zJ2_wf;M3`q?XQx@PweUBzSvgVvA18pt7>Q23*+uEJKsO?-Btg7=#}^F55d9SL2WT? zls@?R+9TAQ`8%(k+O+X+7bd;8;K<}ZzIO3m``5F&uMF&}=X;MI{QDow&bF1e{&?>8 zrn~uFQ!bTphkjMp@vmo;YCd8}hFVcFrEE>1AdL<5%q1GY3AmYX8Ycht7ZY zqwMqQ(6jfS-P60~>|o|npZ3N(=METeFWx-kXMJX1?&bHM_(pGP-SI@GNq_d~_Q7de zXS`0_`Nm&{qMaZ2-1-aWO8+M>PM^2^)n^`C`_P%wd;cWvJkvgK>C~CPJO6mt*!ab~ z-j6pvazAnO&{J1uc8Ps=a}BGGzIk=xyN{r2idJ&DCnmkodK z=urC>bK&O;BjvtT$Lcp98-BRzv%dCmE6cZvovn>j;FfilPY(R{D#G&>~eESa* X`=+k#FNfcE|N2F1mPTH#da&)kn@##p literal 0 HcmV?d00001 From 27cbcc4bd2f1a40d2bb6836217d357e075b55d09 Mon Sep 17 00:00:00 2001 From: Kuronons <110337784+Kuronons@users.noreply.github.com> Date: Sun, 7 Apr 2024 14:08:21 +0200 Subject: [PATCH 050/129] MNTM Pack - Adding Warning yappy assets (normal & flip vers.) --- .../Icons/Dolphin/WarningDolphinFlip_45x42.png | Bin 0 -> 2108 bytes .../Icons/Dolphin/WarningDolphin_45x42.png | Bin 0 -> 2113 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/packs/Momentum/Icons/Dolphin/WarningDolphinFlip_45x42.png create mode 100644 assets/packs/Momentum/Icons/Dolphin/WarningDolphin_45x42.png diff --git a/assets/packs/Momentum/Icons/Dolphin/WarningDolphinFlip_45x42.png b/assets/packs/Momentum/Icons/Dolphin/WarningDolphinFlip_45x42.png new file mode 100644 index 0000000000000000000000000000000000000000..5595b606c797b2ff9d871d1fc6bf1a9d4eb6374d GIT binary patch literal 2108 zcmcIlYitx%7~LY#QbU6QA_9q%Awn&ko%b^=xZT;(MYdGfNJ)I%z4z{R(%qTq%#_`d z5QS=tip7|Q5Q6#xQRD|2Bu0XP2u4f{KEM~o#2BK9XdwY(f+%=rU!Z~7KW=7r?#z7m zeCIpgnR}nM0a=#uD2WXZC!3*Z0_s4UJ~c8orM=m_+Sg?Jg9HT3^1{sHsS(>VZ1OqWT9wwyv z(!Er|@i3)CIRnta*g20dn#vir=P-bj@K^?)wKJ+X%upFBT5kb}&3Su;2 zS}mdIHehYPCKC~iEd!V3qg+^0Jusb~IJ8)7MEgNY=~24j3%U_mW2$Q&2l2-5rYLa$ArdWvOpu`G=&F}Ea^za6<%b% z>A;~-iEw_^w#b15qoJmuqFheR0foVez%z;>iHxplDubY|N?6tb6k#20m1mJJ2NbSH z4Ot)y5ZXQu?7#;Mg@zin9cw&^m>ejVUsbUYmcoPs5d#DRoGHL zCZH2EzZ&K{xu66*9M2P_|I=LT&B*&;{lwHK*ZBXDdQDc?59~Z93nxp5#jxQLQ*8?S z?|xnDcil5_;^wfMeocl>kZ%aG^u`wDvzn%fqQ)RGt1==-td*5zg+V!-Qy|Jp8i%Va z3f48Ox-q$AJkh1gy26P(&nFXcRhIRnpvcKClPu2q&1Wi#qRD2$R3+7{%{+s@)fg{*xPR-JW!jd$tuL(@edx2nMHjc> zkqh6g7|l$1@5IBSopaCb{x#8&+|jyO&;4~v(}`7h{u^TlX1$Y|e|#|b^!~Qzj-LM6 z+jDXR{kHJ7wwYfZe{#kn_oW{_zw0gklX>Ub$M#+Q=HpG5ceUR=GWG2XU$p-;YxEF1 zRj{}8Ha(5TzSg&iy9>qNy~2xohg;{(Ik!e@v7i63`OH_-xjRqKUU0?Lp1rhgqu+6A zbm0B%<-={!{@gLre4!?k2H@`o!Fx=l$$sTiG=VP1y1{OEHI{*Lx literal 0 HcmV?d00001 diff --git a/assets/packs/Momentum/Icons/Dolphin/WarningDolphin_45x42.png b/assets/packs/Momentum/Icons/Dolphin/WarningDolphin_45x42.png new file mode 100644 index 0000000000000000000000000000000000000000..b254b6bca2550a948c98692c328b7ff9b0b2acb9 GIT binary patch literal 2113 zcmcIlYitx%7@hJ%9;LNWqLmOc8PxFT>^yefu;6xgOEq9_K1)C7$|3DT-0nm|naBP2#?G(;grON>N~-q{yup!ScOnVmZ` z=brC;=lkZ~o!wn)n&vH>M^RK$x-;2Bt}Hp1&6!31-`t!UBA2;t=LVmm8gCEJ25QgV zR*JgioYmVG^kp72pi_td3_u+*t5?x0O9mk*-~iC2Lf-a`Qj96{8e|+k<`}vR2?kuKpMj%|ltEC)rVrMebru ze-O9^#}$jkNHH35yc{R!y3X+;CyFdVu>P_g9T1Iw}LkP(>9P!MB? z(n<;iw@z#ORhx)mTnV_G5aGj=YQRipdT60gNBco)6NysQdqZNsci6>T5B8lQ58~7& zYzK>L!Q6f)aQuGfCfe$+|Dh1UdO&x`%a`pzki&UgAlyE|3T1t+k?=4G9Iw}L^0h*B z*CNuQ7!l|t>n$5OMSp3is5${BL4acnshY&{1eJRG0Ij!SfYphV%gf0hk%s zK~{EnCeTdANZWn@Y>3mz7(+xwEDIS(Q)MLUkd+ayu~{e~R)5NiSnv!;R}D?+TPFpCsZRmxcswud~6d^sS0 zJ!_~65k^@BA^>8P6#(f;F)N9zt_eIV%281lO@Jhc@LY?VaPp3qaS(Ag%FqZJS;s2? z(mz%KE|nD1nRBJglLN1Xo8=HhN9-Upkemp-$?|BuqEw!(g3=P+3~IVMbo3zwKm zQMiBC>sq~QJrgJ099Gk>+RzSibwgI&xJ3n1)3j(*V^P#p*(fhT)|6$1MOmCxATlM5 z#}yTM>l#vN-EEpQ$K{CMOd~RZ^4XnH_Sx zUEw8J5jtcdR6|ob z8Sm|zxcu_@<_UU!;_6@fzrOo$`t`Q1OA9|e_{*1#+Y*xpcl>(pwRcA+x9H~%J!Z$J z{Jpb}oIN$`i$z!32c$)#A6__ie%G^Y2c*J+%!>yOt@`-qdoHw2eg4tbd(FAetij8* zM_Y{3e=fRf`}knroHLI+tXr|G{uq6yr=j`E6FbCL zS2vHWkk`C7*;<(3^d}qoP6-R2=QiK*RM6NwcKPDbsn52Y9sEjLvPbtR9sZ`}4b#9h4{ literal 0 HcmV?d00001 From c31b60c7b713b9f42eb06552a424c1291fc4fb5e Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+Willy-JL@users.noreply.github.com> Date: Sun, 7 Apr 2024 14:21:25 +0100 Subject: [PATCH 051/129] IR: Fix crash on duty_cycle=1 (#3568) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * IR: Fix crash on duty_cycle=1 * Infrared: use float around duty_cycle Co-authored-by: あく --- lib/infrared/worker/infrared_worker.c | 2 +- targets/f7/furi_hal/furi_hal_infrared.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/infrared/worker/infrared_worker.c b/lib/infrared/worker/infrared_worker.c index a867542e0..89f351eb9 100644 --- a/lib/infrared/worker/infrared_worker.c +++ b/lib/infrared/worker/infrared_worker.c @@ -612,7 +612,7 @@ void infrared_worker_set_raw_signal( furi_check(timings); furi_check(timings_cnt > 0); furi_check((frequency <= INFRARED_MAX_FREQUENCY) && (frequency >= INFRARED_MIN_FREQUENCY)); - furi_check((duty_cycle < 1.0f) && (duty_cycle > 0.0f)); + furi_check((duty_cycle <= 1.0f) && (duty_cycle > 0.0f)); size_t max_copy_num = COUNT_OF(instance->signal.raw.timings) - 1; furi_check(timings_cnt <= max_copy_num); diff --git a/targets/f7/furi_hal/furi_hal_infrared.c b/targets/f7/furi_hal/furi_hal_infrared.c index 6f2210cc1..a0b166fad 100644 --- a/targets/f7/furi_hal/furi_hal_infrared.c +++ b/targets/f7/furi_hal/furi_hal_infrared.c @@ -357,7 +357,7 @@ static void furi_hal_infrared_configure_tim_pwm_tx(uint32_t freq, float duty_cyc if(infrared_tx_output == FuriHalInfraredTxPinInternal) { LL_TIM_OC_SetCompareCH3( INFRARED_DMA_TIMER, - ((LL_TIM_GetAutoReload(INFRARED_DMA_TIMER) + 1) * (1 - duty_cycle))); + ((LL_TIM_GetAutoReload(INFRARED_DMA_TIMER) + 1) * (1.0f - duty_cycle))); LL_TIM_OC_EnablePreload(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3); /* LL_TIM_OCMODE_PWM2 set by DMA */ LL_TIM_OC_SetMode(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_FORCED_INACTIVE); @@ -368,7 +368,7 @@ static void furi_hal_infrared_configure_tim_pwm_tx(uint32_t freq, float duty_cyc } else if(infrared_tx_output == FuriHalInfraredTxPinExtPA7) { LL_TIM_OC_SetCompareCH1( INFRARED_DMA_TIMER, - ((LL_TIM_GetAutoReload(INFRARED_DMA_TIMER) + 1) * (1 - duty_cycle))); + ((LL_TIM_GetAutoReload(INFRARED_DMA_TIMER) + 1) * (1.0f - duty_cycle))); LL_TIM_OC_EnablePreload(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1); /* LL_TIM_OCMODE_PWM2 set by DMA */ LL_TIM_OC_SetMode(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_FORCED_INACTIVE); @@ -609,7 +609,7 @@ static void furi_hal_infrared_async_tx_free_resources(void) { } void furi_hal_infrared_async_tx_start(uint32_t freq, float duty_cycle) { - if((duty_cycle > 1) || (duty_cycle <= 0) || (freq > INFRARED_MAX_FREQUENCY) || + if((duty_cycle > 1.0f) || (duty_cycle <= 0.0f) || (freq > INFRARED_MAX_FREQUENCY) || (freq < INFRARED_MIN_FREQUENCY) || (infrared_tim_tx.data_callback == NULL)) { furi_crash(); } From 6b120a3b09c08cb4c4f7976ab756e0fb67e9cb3b Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+Willy-JL@users.noreply.github.com> Date: Sun, 7 Apr 2024 14:49:00 +0100 Subject: [PATCH 052/129] Furi: Add "out of memory" and "malloc(0)" crash messages (#3574) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- furi/core/memmgr_heap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/furi/core/memmgr_heap.c b/furi/core/memmgr_heap.c index 24bd327fd..3f62b518c 100644 --- a/furi/core/memmgr_heap.c +++ b/furi/core/memmgr_heap.c @@ -486,7 +486,7 @@ void* pvPortMalloc(size_t xWantedSize) { configASSERT((((size_t)pvReturn) & (size_t)portBYTE_ALIGNMENT_MASK) == 0); - furi_check(pvReturn); + furi_check(pvReturn, xWantedSize ? "out of memory" : "malloc(0)"); pvReturn = memset(pvReturn, 0, to_wipe); return pvReturn; } From 16b34c6e4de4d983ca938b0803b04339067fc767 Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+Willy-JL@users.noreply.github.com> Date: Sun, 7 Apr 2024 15:11:23 +0100 Subject: [PATCH 053/129] Explain RNG differences, add FURI_HAL_RANDOM_MAX (#3565) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Explain RNG differences, add FURI_HAL_RANDOM_MAX * Mark FURI_HAL_RANDOM_MAX unsigned Co-authored-by: あく --- targets/furi_hal_include/furi_hal_random.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/targets/furi_hal_include/furi_hal_random.h b/targets/furi_hal_include/furi_hal_random.h index 051b6f928..fab62083f 100644 --- a/targets/furi_hal_include/furi_hal_random.h +++ b/targets/furi_hal_include/furi_hal_random.h @@ -6,12 +6,16 @@ extern "C" { #endif +#define FURI_HAL_RANDOM_MAX 0xFFFFFFFFU + /** Initialize random subsystem */ void furi_hal_random_init(void); /** Get random value + * furi_hal_random_get() gives up to FURI_HAL_RANDOM_MAX + * rand() and random() give up to RAND_MAX * - * @return random value + * @return 32 bit random value (up to FURI_HAL_RANDOM_MAX) */ uint32_t furi_hal_random_get(void); From 9e72d23aec290bb32bcb1dda6a6d22278340dc32 Mon Sep 17 00:00:00 2001 From: Kuronons <110337784+Kuronons@users.noreply.github.com> Date: Sun, 7 Apr 2024 16:35:33 +0200 Subject: [PATCH 054/129] MNTM Pack - Adding Save yappy asset --- .../Icons/Dolphin/DolphinSaved_92x58.png | Bin 0 -> 2336 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/packs/Momentum/Icons/Dolphin/DolphinSaved_92x58.png diff --git a/assets/packs/Momentum/Icons/Dolphin/DolphinSaved_92x58.png b/assets/packs/Momentum/Icons/Dolphin/DolphinSaved_92x58.png new file mode 100644 index 0000000000000000000000000000000000000000..4b3aedaa991717bf81b3b18812af95a6d456b83d GIT binary patch literal 2336 zcmcImeQXnD9PUg8V|)pWLjzT4ihZtK>$&e&15MK%H9KHh83wRh$2O1mj4 zOCU}JOeCP-`0xWi{vc+eqL3JYL^4JWs%SRi7I)AzczAdX2iHg~=EuFw0t z&+qqpe)qOBR9`!>u(;6Sa7?TV_#5DL89e!dyWn>>@@@cL#>E3Iro%CQntkRuw!e0d z!!edto0_fW;G?{x$DD$!i@=$R#Ua|^D62@t1*sKShzP=}=0z_b{0v1@*^4e=gG4Z1 z4I*lw-2jd4^-WTHtHjA@MLANI;vqo{SOSuYMKzO8dC@d456AXn97WO)tJRD8>;;kL zU0(&Qy#S#Iioi*Duo&s)84piWNalmW)eKqT8~hJv)`E9l zG-6qC9>Iut> zLZ!nY#Ns(x&CH|;CJavrah!A#c1T%ZFgQ9i7R#Yct7bJ^C8KvlVza3&4)6wG>Ip*v zHLHPU&B`t&OS!r6gb__gDoZ$sf*6FEP#Bq8Djv}--HhnBi6i%OE}A%BZ2-a2jV4`> zW(yU{vLF=YB$3i3swV46bDpgzGXeMo3wTjjH3lQ#;cg;Xp5l0ddx)TTf*6Jdp$}xi z68;W$@f3tHe*x>Vs@bReYQoaBW%{ELtjdg{Dv~xGFG#l6 zy{Kdt8OUhnu{uKb?o8(}Z88EN=_th?hMBrzB?SZc!Z6eSXfFJA;!R=o=+Z?7)>`5u zOcYoGlihaVC62({3a1c)NRy(6%ZC2HNYA8IiU?X5z!xWu{)b@h$i=s-!!3RFZ|X2| zFu47`9CjFftNCu&G}{0^*qXLA%}hfZmRJln9)*!412942Sd8Vmd|{`M^1R@6zlcn^?ojVcXt>0&Wpi=zaCi5pD6avFWI?vY7r-$Y`s=eRb2A= z-o~vJv-9&;oW7Fip_;dL4DwT2N{ Date: Sun, 7 Apr 2024 23:47:48 +0900 Subject: [PATCH 055/129] Move crypto1 to helpers, add it to the public API (#3567) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Move crypto1 to helpers, add it to the public API * F18 API version bump Co-authored-by: あく --- lib/nfc/SConscript | 1 + .../{protocols/mf_classic => helpers}/crypto1.c | 0 .../{protocols/mf_classic => helpers}/crypto1.h | 0 .../protocols/mf_classic/mf_classic_listener_i.h | 2 +- lib/nfc/protocols/mf_classic/mf_classic_poller_i.h | 2 +- targets/f18/api_symbols.csv | 2 +- targets/f7/api_symbols.csv | 14 +++++++++++++- 7 files changed, 17 insertions(+), 4 deletions(-) rename lib/nfc/{protocols/mf_classic => helpers}/crypto1.c (100%) rename lib/nfc/{protocols/mf_classic => helpers}/crypto1.h (100%) diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index 41332362c..82317918b 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -48,6 +48,7 @@ env.Append( File("helpers/iso14443_crc.h"), File("helpers/iso13239_crc.h"), File("helpers/nfc_data_generator.h"), + File("helpers/crypto1.h"), ], ) diff --git a/lib/nfc/protocols/mf_classic/crypto1.c b/lib/nfc/helpers/crypto1.c similarity index 100% rename from lib/nfc/protocols/mf_classic/crypto1.c rename to lib/nfc/helpers/crypto1.c diff --git a/lib/nfc/protocols/mf_classic/crypto1.h b/lib/nfc/helpers/crypto1.h similarity index 100% rename from lib/nfc/protocols/mf_classic/crypto1.h rename to lib/nfc/helpers/crypto1.h diff --git a/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h b/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h index 5269743b5..af22b5234 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h @@ -3,7 +3,7 @@ #include "mf_classic_listener.h" #include #include -#include "crypto1.h" +#include #ifdef __cplusplus extern "C" { diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index a5af31530..14a7c61fd 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -3,7 +3,7 @@ #include "mf_classic_poller.h" #include #include -#include "crypto1.h" +#include #ifdef __cplusplus extern "C" { diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index f6199445d..72116ccfd 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.4,, +Version,+,60.5,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 6e65b9471..4ddff921b 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.4,, +Version,+,60.5,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -123,6 +123,7 @@ Header,+,lib/music_worker/music_worker.h,, Header,+,lib/nanopb/pb.h,, Header,+,lib/nanopb/pb_decode.h,, Header,+,lib/nanopb/pb_encode.h,, +Header,+,lib/nfc/helpers/crypto1.h,, Header,+,lib/nfc/helpers/iso13239_crc.h,, Header,+,lib/nfc/helpers/iso14443_crc.h,, Header,+,lib/nfc/helpers/nfc_data_generator.h,, @@ -853,6 +854,16 @@ Function,-,coshl,long double,long double Function,-,cosl,long double,long double Function,+,crc32_calc_buffer,uint32_t,"uint32_t, const void*, size_t" Function,+,crc32_calc_file,uint32_t,"File*, const FileCrcProgressCb, void*" +Function,+,crypto1_alloc,Crypto1*, +Function,+,crypto1_bit,uint8_t,"Crypto1*, uint8_t, int" +Function,+,crypto1_byte,uint8_t,"Crypto1*, uint8_t, int" +Function,+,crypto1_decrypt,void,"Crypto1*, const BitBuffer*, BitBuffer*" +Function,+,crypto1_encrypt,void,"Crypto1*, uint8_t*, const BitBuffer*, BitBuffer*" +Function,+,crypto1_encrypt_reader_nonce,void,"Crypto1*, uint64_t, uint32_t, uint8_t*, uint8_t*, BitBuffer*, _Bool" +Function,+,crypto1_free,void,Crypto1* +Function,+,crypto1_init,void,"Crypto1*, uint64_t" +Function,+,crypto1_reset,void,Crypto1* +Function,+,crypto1_word,uint32_t,"Crypto1*, uint32_t, int" Function,-,ctermid,char*,char* Function,-,cuserid,char*,char* Function,+,datetime_datetime_to_timestamp,uint32_t,DateTime* @@ -2782,6 +2793,7 @@ Function,+,powf,float,"float, float" Function,-,powl,long double,"long double, long double" Function,+,pretty_format_bytes_hex_canonical,void,"FuriString*, size_t, const char*, const uint8_t*, size_t" Function,-,printf,int,"const char*, ..." +Function,+,prng_successor,uint32_t,"uint32_t, uint32_t" Function,+,property_value_out,void,"PropertyValueContext*, const char*, unsigned int, ..." Function,+,protocol_dict_alloc,ProtocolDict*,"const ProtocolBase**, size_t" Function,+,protocol_dict_decoders_feed,ProtocolId,"ProtocolDict*, _Bool, uint32_t" From f9681c65deb4f2113840c0bbacf922744d0291fa Mon Sep 17 00:00:00 2001 From: Kuronons <110337784+Kuronons@users.noreply.github.com> Date: Sun, 7 Apr 2024 19:48:43 +0200 Subject: [PATCH 056/129] MNTM Pack - Adding Success yappy asset --- .../Icons/Dolphin/DolphinSuccess_91x55.png | Bin 0 -> 4226 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/packs/Momentum/Icons/Dolphin/DolphinSuccess_91x55.png diff --git a/assets/packs/Momentum/Icons/Dolphin/DolphinSuccess_91x55.png b/assets/packs/Momentum/Icons/Dolphin/DolphinSuccess_91x55.png new file mode 100644 index 0000000000000000000000000000000000000000..997b7a5e6a2cbec87479f643128d5c638657a4f9 GIT binary patch literal 4226 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 literal 0 HcmV?d00001 From 9d0ad86d3b4482a618711e74053cd88dbe3f012f Mon Sep 17 00:00:00 2001 From: Kuronons <110337784+Kuronons@users.noreply.github.com> Date: Sun, 7 Apr 2024 20:53:11 +0200 Subject: [PATCH 057/129] Re-upload of Success asset - oops Wrong sized vers previously uploaded --- .../Icons/Dolphin/DolphinSuccess_91x55.png | Bin 4226 -> 3254 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/packs/Momentum/Icons/Dolphin/DolphinSuccess_91x55.png b/assets/packs/Momentum/Icons/Dolphin/DolphinSuccess_91x55.png index 997b7a5e6a2cbec87479f643128d5c638657a4f9..90b5befec1cbe845604dfaedfb87e8eaf2da9b59 100644 GIT binary patch delta 821 zcmZot+$O2m8Q|y6%O%Cdz`(%k>ERLtq@#h@oP!BSzDZj6exu?OCJrM5D^o)&Bg@IW z%*qIk1+#@pijldcxuKG9D~$CgIy%lVFfe6#x;Tbp+997;TgUHV zv-Z@PH$Gg664-Km@4oeAf-y?dPpB4kzEQr!w=Yasvp#!o;%m1X^Uf`*+48p2&gg3Y ztk%aL8H46JZJzg)UFzZjmX3_Lzu_)*hps+1|I423(>?h`mQvtvjxgPc$LhJ(GalA0 z+*q?N)^UlrqGr3P>(v`uqmdSmHD`wLP}URgZqYxei?X|Xct+fiKhwBfSLvTv_Vx1P;z6HHv8e5|eL zX_3o1M#o0^mR6%JEt~7M7RMU*)Ze&OAMu%K;@sC#Zh!I?O;z5w!E466>;p4evbrwT zZwcSFP`Y8E%KM*hvXB1j+{w1*(xyGDm)QuuwyfTiKh#xu#RAWl+oP- zlJmC)@jhQ8GBJZaC;XV=Zbn0;9nw=GB|m@Gv0o%OYl^?h6%DH}Wu=J`S2)#N72R^| z(|Z0P*l1u9%{Nn`sf!ECau>*<#V*%Y(p4U zcal<=%F4*gmKiH5mThT^tq(kKpEU(TgG2lgA$~!%HfE47FF|HO zdl7z!;B092X&*S$FT)24^N0FmWSz+hgu*X~-33 znbeUE9yb9jf&Gs`psxaB(Gf>4Oe~2`w^D^hm$gS^5=RO5e;eeM;5 zojo?z*VoHT%HC6sfWY<#t3i?Gb{=|DVP|p?tT%3Gkh`hg=n+Pk&S~dSVOR1&t6j>0 z)@bWVIv`-W>F2KbD{n+UFZbFyTHzRp9)3AQUyf3?Qo&oHsEMOEY$)UzIhXDYb0dU zkl3*mQ8B}HC3JkJa5P3&&SnvVw9d2q?{CJH=MKsfMlKsk@LJOmDSQ^B+^buew)jZ} z*Yww$!3=bg(!Om^z>!0*J8ByUmI6h=$#jU30Q~LsIkD_>0r6%#Weu5$MdkHo6X^g| zh#R?E;%J2ZmeK}C=18~=k;I!*4edPw(w1~4%Qn>Iax6>^7L}XOw7DU3S+IGO zJX|#2#bi8hs5a8{O_mm9u1Q0myr2#Y{wjo zga&lI0=BPcuc0?Cyxp}=zI%8~Jk$eV0L(zh{!@GP-2sZ6&Bya6lcg063N^KhezG9c zF*A42KwywW=#=1l`2!C$D@T%-%xa7>#OJ)CIv36nn?+hDmjO6~p;FdwD|vCylMPJv zwF0i0v)#D1L_Y2c2T)8uF$T3Bo#@oC2)yTt#tmv1Nz6v%5{)@8c%osNyQJ}F-in|G zc6bbi5}WPHpeh$QC}VFv%+PjCaJss8e|K6o&BXr7755v0$7F?d^>@JMBEpdX3*Tz% zP|j#(5u)ObTMCVkUNUYUx2i1A>4QM+1{c;nHFyTk+62p0SO4AfXjC{AD5jQ>zai8x z1-tKEgWNx^+$(A7t*zM{qs_TeTvN4NlJqg;hvP8=*g$-*ZM43gVj?y@3^pTxV8QLRoVTzHvFA zLYM3xYu(JOfvK$bIITxb6Zv}-^7N~BS z`oeU!VIm0y-d;07C^Q?uwJ}+k@cA6+kBb*V3E{}LLcaDqlTV1=+&Fq+#>We3Y}PH? zzk$ab3Ri;`mMn0Jl9?k_+q6e)sbqR3R|*eUi!FDB1e7UD+SmxT1_IN_xQn9H8@N9W z_Yu=VJaL0F+{5#Y=HQjeUj$3|RT5g*-Ku6qn)^BEcHWa|_lNzmjpTAGn(8Nh%lhKW zq5Q+6T@-$}oC|dAFpmC%iM%EvQCdPfdyXfY_Z;(t$%C&g)9a3u8i>DpVYiTqXkz*f zb1755$Db>9MPqnmZIyhUdB*p}7e>=`6;gF>HJ|V7;Zph+T6T%m)~IqaCSr^?(}bjP zIX+B+^~?8p!{qh#ryo>?!Sj}wZLi4E(E=sOxCLm(^2yw^0#ub_EC2r7*Ramwf?f(! omY2jk#8(Fi9Q8}x1gmt)v$&m@D2O Date: Sun, 7 Apr 2024 20:42:17 +0100 Subject: [PATCH 058/129] MNTM: Refactor device name to Spoof submenu --- .../scenes/momentum_app_scene_config.h | 3 +- .../scenes/momentum_app_scene_misc.c | 15 +++--- .../scenes/momentum_app_scene_misc_spoof.c | 52 +++++++++++++++++++ ...c => momentum_app_scene_misc_spoof_name.c} | 20 +++---- 4 files changed, 73 insertions(+), 17 deletions(-) create mode 100644 applications/main/momentum_app/scenes/momentum_app_scene_misc_spoof.c rename applications/main/momentum_app/scenes/{momentum_app_scene_misc_rename.c => momentum_app_scene_misc_spoof_name.c} (70%) diff --git a/applications/main/momentum_app/scenes/momentum_app_scene_config.h b/applications/main/momentum_app/scenes/momentum_app_scene_config.h index 77eb46432..419d9b27d 100644 --- a/applications/main/momentum_app/scenes/momentum_app_scene_config.h +++ b/applications/main/momentum_app/scenes/momentum_app_scene_config.h @@ -18,6 +18,7 @@ ADD_SCENE(momentum_app, misc, Misc) ADD_SCENE(momentum_app, misc_screen, MiscScreen) ADD_SCENE(momentum_app, misc_screen_color, MiscScreenColor) ADD_SCENE(momentum_app, misc_dolphin, MiscDolphin) +ADD_SCENE(momentum_app, misc_spoof, MiscSpoof) +ADD_SCENE(momentum_app, misc_spoof_name, MiscSpoofName) ADD_SCENE(momentum_app, misc_vgm, MiscVgm) ADD_SCENE(momentum_app, misc_vgm_color, MiscVgmColor) -ADD_SCENE(momentum_app, misc_rename, MiscRename) diff --git a/applications/main/momentum_app/scenes/momentum_app_scene_misc.c b/applications/main/momentum_app/scenes/momentum_app_scene_misc.c index ba9c1077e..64b9900f2 100644 --- a/applications/main/momentum_app/scenes/momentum_app_scene_misc.c +++ b/applications/main/momentum_app/scenes/momentum_app_scene_misc.c @@ -3,8 +3,8 @@ enum VarItemListIndex { VarItemListIndexScreen, VarItemListIndexDolphin, + VarItemListIndexSpoof, VarItemListIndexVgm, - VarItemListIndexChangeDeviceName, VarItemListIndexChargeCap, VarItemListIndexShowMomentumIntro, }; @@ -37,10 +37,11 @@ void momentum_app_scene_misc_on_enter(void* context) { item = variable_item_list_add(var_item_list, "Dolphin", 0, NULL, app); variable_item_set_current_value_text(item, ">"); - item = variable_item_list_add(var_item_list, "VGM Options", 0, NULL, app); + item = variable_item_list_add(var_item_list, "Spoofing Options", 0, NULL, app); variable_item_set_current_value_text(item, ">"); - variable_item_list_add(var_item_list, "Change Device Name", 0, NULL, app); + item = variable_item_list_add(var_item_list, "VGM Options", 0, NULL, app); + variable_item_set_current_value_text(item, ">"); char cap_str[6]; value_index = momentum_settings.charge_cap / CHARGE_CAP_INTV; @@ -81,14 +82,14 @@ bool momentum_app_scene_misc_on_event(void* context, SceneManagerEvent event) { scene_manager_set_scene_state(app->scene_manager, MomentumAppSceneMiscDolphin, 0); scene_manager_next_scene(app->scene_manager, MomentumAppSceneMiscDolphin); break; + case VarItemListIndexSpoof: + scene_manager_set_scene_state(app->scene_manager, MomentumAppSceneMiscSpoof, 0); + scene_manager_next_scene(app->scene_manager, MomentumAppSceneMiscSpoof); + break; case VarItemListIndexVgm: scene_manager_set_scene_state(app->scene_manager, MomentumAppSceneMiscVgm, 0); scene_manager_next_scene(app->scene_manager, MomentumAppSceneMiscVgm); break; - case VarItemListIndexChangeDeviceName: - scene_manager_set_scene_state(app->scene_manager, MomentumAppSceneMiscRename, 0); - scene_manager_next_scene(app->scene_manager, MomentumAppSceneMiscRename); - break; case VarItemListIndexShowMomentumIntro: { for(int i = 0; i < 10; i++) { if(storage_common_copy( diff --git a/applications/main/momentum_app/scenes/momentum_app_scene_misc_spoof.c b/applications/main/momentum_app/scenes/momentum_app_scene_misc_spoof.c new file mode 100644 index 000000000..7f7823376 --- /dev/null +++ b/applications/main/momentum_app/scenes/momentum_app_scene_misc_spoof.c @@ -0,0 +1,52 @@ +#include "../momentum_app.h" + +enum VarItemListIndex { + VarItemListIndexFlipperName, // TODO: Split into name, mac, serial +}; + +void momentum_app_scene_misc_spoof_var_item_list_callback(void* context, uint32_t index) { + MomentumApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void momentum_app_scene_misc_spoof_on_enter(void* context) { + MomentumApp* app = context; + VariableItemList* var_item_list = app->var_item_list; + VariableItem* item; + + item = variable_item_list_add(var_item_list, "Flipper Name", 0, NULL, app); + variable_item_set_current_value_text(item, app->device_name); + + variable_item_list_set_enter_callback( + var_item_list, momentum_app_scene_misc_spoof_var_item_list_callback, app); + + variable_item_list_set_selected_item( + var_item_list, + scene_manager_get_scene_state(app->scene_manager, MomentumAppSceneMiscSpoof)); + + view_dispatcher_switch_to_view(app->view_dispatcher, MomentumAppViewVarItemList); +} + +bool momentum_app_scene_misc_spoof_on_event(void* context, SceneManagerEvent event) { + MomentumApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(app->scene_manager, MomentumAppSceneMiscSpoof, event.event); + consumed = true; + switch(event.event) { + case VarItemListIndexFlipperName: + scene_manager_next_scene(app->scene_manager, MomentumAppSceneMiscSpoofName); + break; + default: + break; + } + } + + return consumed; +} + +void momentum_app_scene_misc_spoof_on_exit(void* context) { + MomentumApp* app = context; + variable_item_list_reset(app->var_item_list); +} diff --git a/applications/main/momentum_app/scenes/momentum_app_scene_misc_rename.c b/applications/main/momentum_app/scenes/momentum_app_scene_misc_spoof_name.c similarity index 70% rename from applications/main/momentum_app/scenes/momentum_app_scene_misc_rename.c rename to applications/main/momentum_app/scenes/momentum_app_scene_misc_spoof_name.c index 5f0127dfd..1ba6aed7b 100644 --- a/applications/main/momentum_app/scenes/momentum_app_scene_misc_rename.c +++ b/applications/main/momentum_app/scenes/momentum_app_scene_misc_spoof_name.c @@ -4,7 +4,7 @@ enum TextInputIndex { TextInputResultOk, }; -static void momentum_app_scene_misc_rename_text_input_callback(void* context) { +static void momentum_app_scene_misc_spoof_name_text_input_callback(void* context) { MomentumApp* app = context; app->save_name = true; @@ -12,8 +12,10 @@ static void momentum_app_scene_misc_rename_text_input_callback(void* context) { view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); } -static bool - momentum_app_scene_misc_rename_validator(const char* text, FuriString* error, void* context) { +static bool momentum_app_scene_misc_spoof_name_validator( + const char* text, + FuriString* error, + void* context) { UNUSED(context); for(; *text; ++text) { @@ -27,19 +29,19 @@ static bool return true; } -void momentum_app_scene_misc_rename_on_enter(void* context) { +void momentum_app_scene_misc_spoof_name_on_enter(void* context) { MomentumApp* app = context; TextInput* text_input = app->text_input; - text_input_set_header_text(text_input, "Leave empty for default"); + text_input_set_header_text(text_input, "Leave empty for real name"); - text_input_set_validator(text_input, momentum_app_scene_misc_rename_validator, NULL); + text_input_set_validator(text_input, momentum_app_scene_misc_spoof_name_validator, NULL); text_input_set_minimum_length(text_input, 0); text_input_set_result_callback( text_input, - momentum_app_scene_misc_rename_text_input_callback, + momentum_app_scene_misc_spoof_name_text_input_callback, app, app->device_name, FURI_HAL_VERSION_ARRAY_NAME_LENGTH, @@ -48,7 +50,7 @@ void momentum_app_scene_misc_rename_on_enter(void* context) { view_dispatcher_switch_to_view(app->view_dispatcher, MomentumAppViewTextInput); } -bool momentum_app_scene_misc_rename_on_event(void* context, SceneManagerEvent event) { +bool momentum_app_scene_misc_spoof_name_on_event(void* context, SceneManagerEvent event) { MomentumApp* app = context; bool consumed = false; @@ -66,7 +68,7 @@ bool momentum_app_scene_misc_rename_on_event(void* context, SceneManagerEvent ev return consumed; } -void momentum_app_scene_misc_rename_on_exit(void* context) { +void momentum_app_scene_misc_spoof_name_on_exit(void* context) { MomentumApp* app = context; text_input_reset(app->text_input); } From 73e8c632d7facb7ac79ef53410c627bfec399a7e Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sun, 7 Apr 2024 20:43:12 +0100 Subject: [PATCH 059/129] MNTM: Add flipper Shell Color spoofing support --- .../scenes/momentum_app_scene_misc_spoof.c | 25 +++++++++++++++++++ lib/momentum/momentum.h | 2 ++ lib/momentum/settings.c | 2 ++ targets/f7/furi_hal/furi_hal_version.c | 7 +++++- targets/furi_hal_include/furi_hal_version.h | 1 + 5 files changed, 36 insertions(+), 1 deletion(-) diff --git a/applications/main/momentum_app/scenes/momentum_app_scene_misc_spoof.c b/applications/main/momentum_app/scenes/momentum_app_scene_misc_spoof.c index 7f7823376..a9aea7efb 100644 --- a/applications/main/momentum_app/scenes/momentum_app_scene_misc_spoof.c +++ b/applications/main/momentum_app/scenes/momentum_app_scene_misc_spoof.c @@ -2,8 +2,24 @@ enum VarItemListIndex { VarItemListIndexFlipperName, // TODO: Split into name, mac, serial + VarItemListIndexShellColor, }; +const char* const shell_color_names[FuriHalVersionColorCount] = { + "Real", + "Black", + "White", + "Transparent", +}; +static void momentum_app_scene_misc_spoof_shell_color_changed(VariableItem* item) { + MomentumApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, shell_color_names[index]); + momentum_settings.spoof_color = index; + app->save_settings = true; + app->require_reboot = true; +} + void momentum_app_scene_misc_spoof_var_item_list_callback(void* context, uint32_t index) { MomentumApp* app = context; view_dispatcher_send_custom_event(app->view_dispatcher, index); @@ -17,6 +33,15 @@ void momentum_app_scene_misc_spoof_on_enter(void* context) { item = variable_item_list_add(var_item_list, "Flipper Name", 0, NULL, app); variable_item_set_current_value_text(item, app->device_name); + item = variable_item_list_add( + var_item_list, + "Shell Color", + FuriHalVersionColorCount, + momentum_app_scene_misc_spoof_shell_color_changed, + app); + variable_item_set_current_value_index(item, momentum_settings.spoof_color); + variable_item_set_current_value_text(item, shell_color_names[momentum_settings.spoof_color]); + variable_item_list_set_enter_callback( var_item_list, momentum_app_scene_misc_spoof_var_item_list_callback, app); diff --git a/lib/momentum/momentum.h b/lib/momentum/momentum.h index b5eeec02f..ea2c86cc4 100644 --- a/lib/momentum/momentum.h +++ b/lib/momentum/momentum.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -89,6 +90,7 @@ typedef struct { VgmColorMode vgm_color_mode; Rgb565Color vgm_color_fg; Rgb565Color vgm_color_bg; + FuriHalVersionColor spoof_color; } MomentumSettings; typedef struct { diff --git a/lib/momentum/settings.c b/lib/momentum/settings.c index 367fd51c9..0294a796f 100644 --- a/lib/momentum/settings.c +++ b/lib/momentum/settings.c @@ -42,6 +42,7 @@ MomentumSettings momentum_settings = { .vgm_color_mode = VgmColorModeDefault, // Default .vgm_color_fg.value = 0x0000, // Default Black .vgm_color_bg.value = 0xFC00, // Default Orange + .spoof_color = FuriHalVersionColorUnknown, // Real }; typedef enum { @@ -112,6 +113,7 @@ static const struct { {setting_enum(vgm_color_mode, VgmColorModeCount)}, {setting_uint(vgm_color_fg, 0x0000, 0xFFFF)}, {setting_uint(vgm_color_bg, 0x0000, 0xFFFF)}, + {setting_enum(spoof_color, FuriHalVersionColorCount)}, }; void momentum_settings_load(void) { diff --git a/targets/f7/furi_hal/furi_hal_version.c b/targets/f7/furi_hal/furi_hal_version.c index 8c650bf0c..04479a7b8 100644 --- a/targets/f7/furi_hal/furi_hal_version.c +++ b/targets/f7/furi_hal/furi_hal_version.c @@ -8,6 +8,8 @@ #include #include +#include + #define TAG "FuriHalVersion" #define FURI_HAL_VERSION_OTP_HEADER_MAGIC 0xBABE @@ -241,7 +243,10 @@ uint8_t furi_hal_version_get_hw_body(void) { } FuriHalVersionColor furi_hal_version_get_hw_color(void) { - return furi_hal_version.board_color; + if(momentum_settings.spoof_color == FuriHalVersionColorUnknown) { + return furi_hal_version.board_color; + } + return momentum_settings.spoof_color; } uint8_t furi_hal_version_get_hw_connect(void) { diff --git a/targets/furi_hal_include/furi_hal_version.h b/targets/furi_hal_include/furi_hal_version.h index 0cdea5073..bbcb77c0d 100644 --- a/targets/furi_hal_include/furi_hal_version.h +++ b/targets/furi_hal_include/furi_hal_version.h @@ -35,6 +35,7 @@ typedef enum { FuriHalVersionColorBlack = 0x01, FuriHalVersionColorWhite = 0x02, FuriHalVersionColorTransparent = 0x03, + FuriHalVersionColorCount, } FuriHalVersionColor; /** Device Regions */ From 07f63f951232e59e570fb31c01415aaf775145c2 Mon Sep 17 00:00:00 2001 From: Zachary Weiss Date: Sun, 7 Apr 2024 16:20:08 -0400 Subject: [PATCH 060/129] Misc cleanup/refactor + passes testing --- .../nfc/plugins/supported_cards/charliecard.c | 816 ++++++++++-------- 1 file changed, 458 insertions(+), 358 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/charliecard.c b/applications/main/nfc/plugins/supported_cards/charliecard.c index f10c6dcdb..7acbfebfe 100644 --- a/applications/main/nfc/plugins/supported_cards/charliecard.c +++ b/applications/main/nfc/plugins/supported_cards/charliecard.c @@ -97,6 +97,7 @@ #define CHARLIE_TIME_DELTA_SECS 60 #define CHARLIE_END_VALID_DELTA_SECS 60 * 8 #define CHARLIE_N_TRIP_HISTORY 10 +#define CHARLIE_N_PASSES 4 enum CharlieActiveSector { CHARLIE_ACTIVE_SECTOR_2, @@ -155,6 +156,12 @@ typedef struct { uint8_t f_flag; } Trip; +typedef struct { + DateTime start_valid; + uint16_t type; + DateTime end_valid; +} Pass; + // IdMapping approach borrowed from Jeremy Cooper's 'clipper.c' typedef struct { uint16_t id; @@ -168,7 +175,7 @@ static const IdMapping charliecard_types[] = { {.id = 366, .name = "SV Adult"}, {.id = 418, .name = "Student"}, {.id = 419, .name = "Senior"}, - {.id = 420, .name = "Tap"}, + {.id = 420, .name = "TAP"}, {.id = 417, .name = "Blind"}, {.id = 426, .name = "Child"}, {.id = 410, .name = "Employee ID Without Passback"}, @@ -184,10 +191,10 @@ static const IdMapping charliecard_types[] = { {.id = 139, .name = "30 Day Senior LinkPass"}, {.id = 148, .name = "30 Day TAP LinkPass"}, {.id = 150, .name = "Monthly Student LinkPass"}, - {.id = 424, .name = "Monthly TAP LinkPass"}, // 0b0110101000 - {.id = 425, .name = "Monthly Senior LinkPass"}, // 0b0110101001 - {.id = 421, .name = "Senior TAP/Permit"}, // 0b0110100101 - {.id = 422, .name = "Senior TAP/Permit 30 Days"}, // 0b0110100110 + {.id = 424, .name = "Monthly TAP LinkPass"}, + {.id = 425, .name = "Monthly Senior LinkPass"}, + {.id = 421, .name = "Senior TAP/Permit"}, + {.id = 422, .name = "Senior TAP/Permit 30 Days"}, // Commuter rail passes {.id = 166, .name = "30 Day Commuter Rail Zone 1A Pass"}, @@ -558,7 +565,7 @@ static const IdMapping charliecard_fare_gate_ids[] = { {.id = 2010, .name = "Wood Island"}, {.id = 6971, .name = "Wood Island"}, // Orient Heights - {.id = 6621, .name = "Orient Heights"}, // marked as needs checking + {.id = 6621, .name = "Orient Heights"}, {.id = 6622, .name = "Orient Heights"}, {.id = 6623, .name = "Orient Heights"}, {.id = 2014, .name = "Orient Heights"}, @@ -590,8 +597,13 @@ static const IdMapping charliecard_fare_gate_ids[] = { }; static const size_t kNumFareGateIds = COUNT_OF(charliecard_fare_gate_ids); +// ********************************************************** +// ********************* MISC HELPERS *********************** +// ********************************************************** + static const uint8_t* pos_to_ptr(const MfClassicData* data, uint8_t sector_num, uint8_t block_num, uint8_t byte_num) { + // returns pointer to specified sector/block/byte of MFClassic card data uint8_t block_offset = mf_classic_get_first_block_num_of_sector(sector_num); return &data->block[block_offset + block_num].data[byte_num]; } @@ -602,16 +614,24 @@ static uint64_t pos_to_num( uint8_t block_num, uint8_t byte_num, uint8_t byte_len) { + // returns numeric values at specified card location, for given byte length. + // assumes big endian. return bit_lib_bytes_to_num_be(pos_to_ptr(data, sector_num, block_num, byte_num), byte_len); } static DateTime dt_delta(DateTime dt, uint64_t delta_secs) { + // returns shifted DateTime, from initial DateTime and time offset in seconds DateTime dt_shifted = {0}; datetime_timestamp_to_datetime(datetime_datetime_to_timestamp(&dt) + delta_secs, &dt_shifted); return dt_shifted; } +static bool dt_ge(DateTime dt1, DateTime dt2) { + // compares two DateTimes + return datetime_datetime_to_timestamp(&dt1) >= datetime_datetime_to_timestamp(&dt2); +} + static bool get_map_item(uint16_t id, const IdMapping* map, size_t sz, const char** out) { // code borrowed from Jeremy Cooper's 'clipper.c'. Used as follows: // const char* s; if(!get_map_item(_,_,_,&s)) {s="Default str";} @@ -626,8 +646,439 @@ static bool get_map_item(uint16_t id, const IdMapping* map, size_t sz, const cha return false; } +uint32_t time_now() { + return furi_hal_rtc_get_timestamp(); +} + +// ********************************************************** +// ********************* DATA PARSING *********************** +// ********************************************************** + +static Money money_parse( + const MfClassicData* data, + uint8_t sector_num, + uint8_t block_num, + uint8_t byte_num) { + // CharlieCards store all money values in two bytes as half-cents + // bitmask removes sign/flag, bitshift converts half-cents to cents, div & mod yield dollars & cents + uint16_t amt = (pos_to_num(data, sector_num, block_num, byte_num, 2) & 0x7FFF) >> 1; + return (Money){amt / 100, amt % 100}; +} + +static DateTime + date_parse(const MfClassicData* data, uint8_t sector_num, uint8_t block_num, uint8_t byte_num) { + // Dates are 3 bytes, in minutes since 2003/1/1 ("CHARLIE_EPOCH") + uint32_t ts_charlie = pos_to_num(data, sector_num, block_num, byte_num, 3); + return dt_delta(CHARLIE_EPOCH, ts_charlie * CHARLIE_TIME_DELTA_SECS); +} + +static uint16_t n_uses(const MfClassicData* data, const enum CharlieActiveSector active_sector) { + /* First two bytes of applicable block (sector 1, block 1 or 2 depending on active_sector) + The *lower* of the two values *minus one* is the true use count, + per DEFCON31 researcher's findings + */ + return pos_to_num(data, 1, 1 + active_sector, 0, 2) - 1; +} + +static enum CharlieActiveSector get_active_sector(const MfClassicData* data) { + /* Card has two transaction sectors (2 & 3) containing balance data, with two + corresponding trip counters in 0x50:0x51 & 0x60:0x61 (sector 1, byte 0:1 of blocks 1 & 2). + + The *lower* count variable corresponds to the active sector + (0x5_ lower -> 2 active, 0x6_ lower -> 3 active) + + Sectors 2 & 3 are (largely) identical, save for trip data. + Card seems to alternate between the two, with active sector storing + the current balance & recent trip/transaction, & the inactive sector storing + the N-1 trip/transaction version of the same data. + + Here I check both the trip count and the stored transaction date, + for my own sanity, to confirm the active sector. + */ + + // active sector based on trip counters + const bool active_trip = n_uses(data, CHARLIE_ACTIVE_SECTOR_2) <= + n_uses(data, CHARLIE_ACTIVE_SECTOR_3); + + // active sector based on transaction date + DateTime ds2 = date_parse(data, 2, 0, 1); + DateTime ds3 = date_parse(data, 3, 0, 1); + const bool active_date = datetime_datetime_to_timestamp(&ds2) >= + datetime_datetime_to_timestamp(&ds3); + + // with all tested cards so far, this has been true + furi_assert(active_trip == active_date); + + return active_trip ? CHARLIE_ACTIVE_SECTOR_2 : CHARLIE_ACTIVE_SECTOR_3; +} + +static uint16_t type_parse(const MfClassicData* data) { + /* Card type data stored in the first 10bits of block 1 of sectors 2 & 3 (Block 9 & Block 13, from card start) + To my knowledge, card type should never change, so we can check either + without caring which is active. For my sanity, I check both, and assert equal. + */ + + // bitshift (2bytes = 16 bits) by 6bits for just first 10bits + const uint16_t type1 = pos_to_num(data, 2, 1, 0, 2) >> 6; + const uint16_t type2 = pos_to_num(data, 3, 1, 0, 2) >> 6; + furi_assert(type1 == type2); + + return type1; +} + +static DateTime end_validity_parse( + const MfClassicData* data, + uint8_t sector_num, + uint8_t block_num, + uint8_t byte_num) { + // End validity field is a bit odd; shares first byte with another variable (the card type field), + // occupying only the last 3 bits (and subsequent two bytes), hence bitmask + // TODO: what are the add'l 3 bits between type & end validity fields? + uint32_t ts_charlie_ev = pos_to_num(data, sector_num, block_num, byte_num, 3) & 0x1FFFFF; + + // additionally, instead of minute deltas, is in 8 minute increments + // relative to CHARLIE_EPOCH (2003/1/1), per DEFCON31 researcher's work + return dt_delta(CHARLIE_EPOCH, ts_charlie_ev * CHARLIE_END_VALID_DELTA_SECS); +} + +static DateTime + main_end_validity_parse(const MfClassicData* data, enum CharlieActiveSector active_sec) { + // primary card type end validity; checked in active sector (probably the same across both 2 & 3) + return end_validity_parse(data, (active_sec == CHARLIE_ACTIVE_SECTOR_2) ? 2 : 3, 1, 1); +} + +static Pass + pass_parse(const MfClassicData* data, uint8_t sector_num, uint8_t block_num, uint8_t byte_num) { + // WIP; testing only. Speculating it may be structured as follows + // Sub-byte field divisions not drawn to scale, see code for exact bit offsets + // + // 0 1 2 3 4 5 6 + // +----.----.----+----.-+--.----.----+ + // | start | type | end | + // +----.----.----+----.-+--.----.----+ + + DateTime start = date_parse(data, sector_num, block_num, byte_num); + uint16_t type = pos_to_num(data, sector_num, block_num, byte_num + 3, 2) >> 6; + DateTime end = end_validity_parse(data, sector_num, block_num, byte_num + 4); + + return (Pass){start, type, end}; +} + +static Trip + trip_parse(const MfClassicData* data, uint8_t sector_num, uint8_t block_num, uint8_t byte_num) { + /* This function parses individual trips. Each trip packs 7 bytes, stored as follows: + + 0 1 2 3 4 5 6 + +----.----.----+----.--+-+----.----+ + | date | loc |f| amt | + +----.----.----+----.--+-+----.----+ + + Where date is in the typical format, loc represents the fare gate tapped, and amt is the fare amount. + Amount appears to contain some flag bits, however, it is unclear what precisely their function is. + + Gate ID ("loc") is only the first 13 bits of 0x3:0x5, the final three bits appear to be flags ("f"). + Least significant flag bit (ie "loc & 0x1") seems to indicate: + — When 0, fare (the amount by which balance is decremented) + — When 1, refill (the amount by which balance is incremented) + + On monthly pass cards, MSB of amt will be set: 0x8000 (negative zero) + Seemingly randomly (irrespective of card type, last trip, etc) 0x0001 will be set on amt in addition to + whatever the regular fare is (a half cent more). I am uncertain what this flag indicates. + */ + const DateTime date = date_parse(data, sector_num, block_num, byte_num); + const uint16_t gate = pos_to_num(data, sector_num, block_num, byte_num + 3, 2) >> 3; + const uint8_t g_flag = pos_to_num(data, sector_num, block_num, byte_num + 3, 2) & 0b111; + const Money fare = money_parse(data, sector_num, block_num, byte_num + 5); + const uint8_t f_flag = pos_to_num(data, sector_num, block_num, byte_num + 5, 2) & 0x8001; + return (Trip){date, gate, g_flag, fare, f_flag}; +} + +static Pass* passes_parse(const MfClassicData* data) { + // WIP. Read in all speculative passes into array + // Sectors 4 & 5 speculated to contain pass data, + // 4 separate fields? active vs inactive sector for 2 passes? + // Sector 4 visualized below; sector 5 layout the same. + // + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + // +----.----.----.----.----.-+--.----+----.----.----.----.----.-+--.----+----.----+ + // 0x100 | pass1? |0 00 | pass2? |0 00 | crc1 | + // +----.----.----.----.----.-+--.----+----.----.----.----.----.-+--.----+----.----+ + // 0x110 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | crc2 | + // +----.----.----.----.----.----.----.----.----.----.----.----.----.----+----.----+ + // 0x120 | 00 00 00 00 00 00 00 05 00 00 00 00 00 00 | crc3 | + // +----.----.----.----.----.----.----.----.----.----.----.----.----.----+----.----+ + + Pass* passes = malloc(sizeof(Pass) * CHARLIE_N_PASSES); + + for(size_t i = 0; i < CHARLIE_N_PASSES; i++) { + passes[i] = pass_parse(data, 4 + (i / 2), 1, (i % 2) * 7); + } + + return passes; +} + +static Trip* trips_parse(const MfClassicData* data) { + // Sectors 6 & 7 store the last 10 trips. Overall layout as follows: + // + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + // +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ + // 0x180 | trip0 | trip1 | crc1 | + // +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ + // ... ... ... ... + // +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ + // 0x1D0 | trip8 | trip9 | crc5 | + // +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ + // 0x1E0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | crc6 | + // +----.----.----.----.----.----.----.----.----.----.----.----.----.----+----.----+ + // + //Trips are not sorted, rather, appear to get overwritten sequentially. (eg, sorted modulo array rotation) + + Trip* trips = malloc(sizeof(Trip) * CHARLIE_N_TRIP_HISTORY); + + // Parse each trip field using some modular math magic to get the offsets: + // move from sector 6 -> 7 after the first 6 trips + // move a block within a given sector every 2 trips, reset every 3 blocks (as sector has changed) + // alternate between a start byte of 0 and 7 with every iteration + for(size_t i = 0; i < CHARLIE_N_TRIP_HISTORY; i++) { + trips[i] = trip_parse(data, 6 + (i / 6), (i / 2) % 3, (i % 2) * 7); + } + + // Iterate through the array to find the maximum (newest) date value + int max_idx = 0; + for(int i = 1; i < CHARLIE_N_TRIP_HISTORY; i++) { + if(dt_ge(trips[i].date, trips[max_idx].date)) { + max_idx = i; + } + } + + // Sort by rotating + for(int r = 0; r < (max_idx + 1); r++) { + // Store the first element + Trip temp = trips[0]; + // Shift elements to the left + for(int i = 0; i < CHARLIE_N_TRIP_HISTORY - 1; i++) { + trips[i] = trips[i + 1]; + } + // Move the first element to the last + trips[CHARLIE_N_TRIP_HISTORY - 1] = temp; + } + + // Reverse order, such that newest is first, oldest last + for(int i = 0; i < CHARLIE_N_TRIP_HISTORY / 2; i++) { + // Swap elements at index i and size - i - 1 + Trip temp = trips[i]; + trips[i] = trips[CHARLIE_N_TRIP_HISTORY - i - 1]; + trips[CHARLIE_N_TRIP_HISTORY - i - 1] = temp; + } + + return trips; +} + +/* +static DateTime expiry(DateTime iss) { + // Per Metrodroid CharlieCard parser (https://github.com/metrodroid/metrodroid/blob/master/src/commonMain/kotlin/au/id/micolous/metrodroid/transit/charlie/CharlieCardTransitData.kt) + // Expiry not explicitly stored in card data; rather, calculated from date of issue + // Cards were first issued in 2006, expired in 5 years, w/ no printed expiry date + // Cards issued after 2011 expire in 10 years + // + // Per DEFCON31 researcher's work (cited above): + // Student cards last one school year and expire at the end of August the following year + // Pre-2011 issued cards expire in 7 years, not 5 as claimed by Metrodroid + // Post-2011 expire in 10 years, less one day + // Redundant function given the existance of the end validity field? + // Any important distinctions between the two? + + + // perhaps additionally clipping to 2030-12-__ in anticipation of upcoming system migration? + // need to get a new card to confirm. + + // TODO add card type logic for student card expiry + DateTime exp; + if(iss.year < 2011) { + // add 7 years; assumes average year of 8766 hrs (to account for leap years) + // may be off by a few hours as a result + exp = dt_delta(iss, 7 * 8766 * 60 * 60); + } else { + // add 10 years, subtract a day. Same assumption as above + exp = dt_delta(iss, ((10 * 8766) - 24) * 60 * 60); + } + + return exp; +}*/ + +static bool expired(DateTime expiry, DateTime last_trip) { + // if a card has sat unused for >2 years, expired (verify this claim?) + // else expired if current date > expiry date + + uint32_t ts_exp = datetime_datetime_to_timestamp(&expiry); + uint32_t ts_last = datetime_datetime_to_timestamp(&last_trip); + uint32_t ts_now = time_now(); + + return (ts_exp <= ts_now) | ((ts_now - ts_last) >= (2 * 365 * 24 * 60 * 60)); +} + +// ********************************************************** +// ****************** STRING FORMATTING ********************* +// ********************************************************** + +void locale_format_dt_cat(FuriString* out, const DateTime* dt) { + // helper to print datetimes + FuriString* s = furi_string_alloc(); + + LocaleDateFormat date_format = locale_get_date_format(); + const char* separator = (date_format == LocaleDateFormatDMY) ? "." : "/"; + locale_format_date(s, dt, date_format, separator); + furi_string_cat(out, s); + locale_format_time(s, dt, locale_get_time_format(), false); + furi_string_cat_printf(out, " "); + furi_string_cat(out, s); + + furi_string_free(s); +} + +void type_format_cat(FuriString* out, uint16_t type) { + const char* s; + if(!get_map_item(type, charliecard_types, kNumTypes, &s)) { + s = ""; + furi_string_cat_printf(out, "Unknown-%u", type); + } + + furi_string_cat_str(out, s); +} + +void pass_format_cat(FuriString* out, Pass pass) { + furi_string_cat_printf(out, "\nPass type: "); + type_format_cat(out, pass.type); + furi_string_cat_printf(out, "\nPass start: "); + locale_format_dt_cat(out, &pass.start_valid); + furi_string_cat_printf(out, "\nPass end: "); + locale_format_dt_cat(out, &pass.end_valid); +} + +void passes_format_cat(FuriString* out, Pass* passes) { + furi_string_cat_printf(out, "\nPasses:"); + for(size_t i = 0; i < CHARLIE_N_PASSES; i++) { + pass_format_cat(out, passes[i]); + furi_string_cat_printf(out, "\n"); + } +} + +void money_format_cat(FuriString* out, Money money) { + furi_string_cat_printf(out, "$%u.%02u", money.dollars, money.cents); +} + +void trip_format_cat(FuriString* out, Trip trip) { + const char* sep = " "; + const char* sta; + + locale_format_dt_cat(out, &trip.date); + furi_string_cat_printf(out, "\n%s", !!(trip.g_flag & 0x1) ? "-" : "+"); + money_format_cat(out, trip.fare); + if(!!(trip.g_flag & 0x1) && (trip.fare.dollars == FARE_BUS.dollars) && + (trip.fare.cents == FARE_BUS.cents)) { + // if not a refill, and the fare amount is equal to bus fare (any better approach? flag bits for modality?) + // format for bus (gate ID on busses = posted bus #) + furi_string_cat_printf(out, "%sBus#%u", sep, trip.gate); + } else if(get_map_item(trip.gate, charliecard_fare_gate_ids, kNumFareGateIds, &sta)) { + // station found in fare gate ID map, append station name + furi_string_cat_str(out, sep); + furi_string_cat_str(out, sta); + } else { + // no found station in fare gate ID map & not a bus, just print ID w/o add'l info + furi_string_cat_printf(out, "%s%u", sep, trip.gate); + } + // print flags for debugging purposes + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + furi_string_cat_printf(out, "%s%u%s%u", sep, trip.g_flag, sep, trip.f_flag); + } +} + +void trips_format_cat(FuriString* out, Trip* trips) { + furi_string_cat_printf(out, "\nTransactions:"); + for(size_t i = 0; i < CHARLIE_N_TRIP_HISTORY; i++) { + furi_string_cat_printf(out, "\n"); + trip_format_cat(out, trips[i]); + furi_string_cat_printf(out, "\n"); + } +} + +// ********************************************************** +// **************** NFC PLUGIN BOILERPLATE ****************** +// ********************************************************** + +static bool charliecard_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + + do { + // Verify card type + if(data->type != MfClassicType1k) break; + + // Verify key + // arbitrary sector in the main data portion + const uint8_t verify_sector = 3; + const MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, verify_sector); + + const uint64_t key_a = + bit_lib_bytes_to_num_be(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); + const uint64_t key_b = + bit_lib_bytes_to_num_be(sec_tr->key_b.data, COUNT_OF(sec_tr->key_b.data)); + if(key_a != charliecard_1k_keys[verify_sector].a) break; + if(key_b != charliecard_1k_keys[verify_sector].b) break; + + // TODO: Verify add'l? + + const enum CharlieActiveSector active_sec_enum = get_active_sector(data); + const uint8_t active_sector = (active_sec_enum == CHARLIE_ACTIVE_SECTOR_2) ? 2 : 3; + + furi_string_cat_printf(parsed_data, "\e#CharlieCard"); + + size_t uid_len = 0; + const uint8_t* uid = mf_classic_get_uid(data, &uid_len); + uint32_t card_number = bit_lib_bytes_to_num_be(uid, 4); + furi_string_cat_printf(parsed_data, "\nSerial: 5-%lu", card_number); + + Money bal = money_parse(data, active_sector, 1, 5); + furi_string_cat_printf(parsed_data, "\nBal: "); + money_format_cat(parsed_data, bal); + + const uint16_t type = type_parse(data); + furi_string_cat_printf(parsed_data, "\nType: "); + type_format_cat(parsed_data, type); + + Pass* passes = passes_parse(data); + passes_format_cat(parsed_data, passes); + free(passes); + + const uint16_t n_trips = n_uses(data, active_sec_enum); + furi_string_cat_printf(parsed_data, "\nTrip Count: %u", n_trips); + + const DateTime iss = date_parse(data, active_sector, 0, 6); + furi_string_cat_printf(parsed_data, "\nIssued: "); + locale_format_dt_cat(parsed_data, &iss); + + const DateTime e_v = main_end_validity_parse(data, active_sec_enum); + furi_string_cat_printf(parsed_data, "\nExpiry: "); + locale_format_dt_cat(parsed_data, &e_v); + + DateTime last = date_parse(data, active_sector, 0, 1); + furi_string_cat_printf(parsed_data, "\nExpired: %s", expired(e_v, last) ? "Yes" : "No"); + + Trip* trips = trips_parse(data); + trips_format_cat(parsed_data, trips); + free(trips); + + parsed = true; + } while(false); + + return parsed; +} + static bool charliecard_verify(Nfc* nfc) { - // does this suffice? Or should I check add'l keys/data/etc? bool verified = false; do { @@ -699,357 +1150,6 @@ static bool charliecard_read(Nfc* nfc, NfcDevice* device) { return is_read; } -uint32_t time_now() { - return furi_hal_rtc_get_timestamp(); -} - -static Money money_parse( - const MfClassicData* data, - uint8_t sector_num, - uint8_t block_num, - uint8_t byte_num) { - // CharlieCards store all money values in two bytes as half-cents - // bitmask removes sign/flag, bitshift converts half-cents to cents, div & mod yield dollars & cents - uint16_t amt = (pos_to_num(data, sector_num, block_num, byte_num, 2) & 0x7FFF) >> 1; - return (Money){amt / 100, amt % 100}; -} - -static DateTime - date_parse(const MfClassicData* data, uint8_t sector_num, uint8_t block_num, uint8_t byte_num) { - // Dates are 3 bytes, in minutes since 2003/1/1 ("CHARLIE_EPOCH") - uint32_t ts_charlie = pos_to_num(data, sector_num, block_num, byte_num, 3); - return dt_delta(CHARLIE_EPOCH, ts_charlie * CHARLIE_TIME_DELTA_SECS); -} - -static DateTime - end_validity_parse(const MfClassicData* data, enum CharlieActiveSector active_sec) { - // End validity field is a bit odd; shares byte 1 with another variable (the card type field), - // occupying only the last 3 bits (and subsequent two bytes), hence bitmask - // TODO: what are the add'l 3 bits between type & end validity fields? - uint32_t ts_charlie_ev = - pos_to_num(data, (active_sec == CHARLIE_ACTIVE_SECTOR_2) ? 2 : 3, 1, 1, 3); - ts_charlie_ev = ts_charlie_ev & 0x1FFFFF; - - // additionally, instead of minute deltas, is in 8 minute increments - // relative to CHARLIE_EPOCH (2003/1/1), per DEFCON31 researcher's work - return dt_delta(CHARLIE_EPOCH, ts_charlie_ev * CHARLIE_END_VALID_DELTA_SECS); -} - -static Trip - trip_parse(const MfClassicData* data, uint8_t sector_num, uint8_t block_num, uint8_t byte_num) { - /* This function parses individual trips. Each trip packs 7 bytes, stored as follows: - - 0 1 2 3 4 5 6 - +----.----.----+----.--+-+----.----+ - | date | loc |f| amt | - +----.----.----+----.--+-+----.----+ - - Where date is in the typical format, loc represents the fare gate tapped, and amt is the fare amount. - Amount appears to contain some flag bits, however, it is unclear what precisely their function is. - - Gate ID ("loc") is only the first 13 bits of 0x3:0x5, the final three bits appear to be flags ("f"). - Least significant flag bit (ie "loc & 0x1") seems to indicate: - — When 0, fare (the amount by which balance is decremented) - — When 1, refill (the amount by which balance is incremented) - - On monthly pass cards, MSB of amt will be set: 0x8000 (negative zero) - Seemingly randomly (irrespective of card type, last trip, etc) 0x0001 will be set on amt in addition to - whatever the regular fare is (a half cent more). I am uncertain what this flag indicates. - */ - const DateTime date = date_parse(data, sector_num, block_num, byte_num); - const uint16_t gate = pos_to_num(data, sector_num, block_num, byte_num + 3, 2) >> 3; - const uint8_t g_flag = pos_to_num(data, sector_num, block_num, byte_num + 3, 2) & 0b111; - const Money fare = money_parse(data, sector_num, block_num, byte_num + 5); - const uint8_t f_flag = pos_to_num(data, sector_num, block_num, byte_num + 5, 2) & 0x8001; - return (Trip){date, gate, g_flag, fare, f_flag}; -} - -static bool date_ge(DateTime dt1, DateTime dt2) { - return datetime_datetime_to_timestamp(&dt1) >= datetime_datetime_to_timestamp(&dt2); -} - -static Trip* trips_parse(const MfClassicData* data) { - /* Sectors 6 & 7 store the last 10 trips. Overall layout as follows: - - 0 1 2 3 4 5 6 7 8 9 A B C D E F - +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ - 0x180 | trip0 | trip1 | crc1 | - +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ - ... ... ... ... - +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ - 0x1D0 | trip8 | trip9 | crc5 | - +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ - 0x1E0 | empty | crc6 | - +----.----.----.----.----.----.----.----.----.----.----.----.----.----+----.----+ - - "empty" is all 0s. Trips are not sorted, rather, appear to get overwritten sequentially. (eg, sorted modulo array rotation) - */ - Trip* trips = malloc(sizeof(Trip) * CHARLIE_N_TRIP_HISTORY); - - // Parse each trip field using some modular math magic to get the offsets: - // move from sector 6 -> 7 after the first 6 trips - // move a block within a given sector every 2 trips, reset every 3 blocks (as sector has changed) - // alternate between a start byte of 0 and 7 with every iteration - for(size_t i = 0; i < CHARLIE_N_TRIP_HISTORY; i++) { - trips[i] = trip_parse(data, 6 + (i / 6), (i / 2) % 3, (i % 2) * 7); - } - - // Iterate through the array to find the maximum (newest) date value - int max_idx = 0; - for(int i = 1; i < CHARLIE_N_TRIP_HISTORY; i++) { - if(date_ge(trips[i].date, trips[max_idx].date)) { - max_idx = i; - } - } - - // Sort by rotating - for(int r = 0; r < (max_idx + 1); r++) { - // Store the first element - Trip temp = trips[0]; - // Shift elements to the left - for(int i = 0; i < CHARLIE_N_TRIP_HISTORY - 1; i++) { - trips[i] = trips[i + 1]; - } - // Move the first element to the last - trips[CHARLIE_N_TRIP_HISTORY - 1] = temp; - } - - // Reverse order, such that newest is first, oldest last - for(int i = 0; i < CHARLIE_N_TRIP_HISTORY / 2; i++) { - // Swap elements at index i and size - i - 1 - Trip temp = trips[i]; - trips[i] = trips[CHARLIE_N_TRIP_HISTORY - i - 1]; - trips[CHARLIE_N_TRIP_HISTORY - i - 1] = temp; - } - - return trips; -} - -static uint16_t n_uses(const MfClassicData* data, const enum CharlieActiveSector active_sector) { - /* First two bytes of applicable block (sector 1, block 1 or 2 depending on active_sector) - The *lower* of the two values *minus one* is the true use count, - per DEFCON31 researcher's findings - */ - return pos_to_num(data, 1, 1 + active_sector, 0, 2) - 1; -} - -static enum CharlieActiveSector get_active_sector(const MfClassicData* data) { - /* Card has two transaction sectors (2 & 3) containing balance data, with two - corresponding trip counters in 0x50:0x51 & 0x60:0x61 (sector 1, byte 0:1 of blocks 1 & 2). - - The *lower* count variable corresponds to the active sector - (0x5_ lower -> 2 active, 0x6_ lower -> 3 active) - - Sectors 2 & 3 are (largely) identical, save for trip data. - Card seems to alternate between the two, with active sector storing - the current balance & recent trip/transaction, & the inactive sector storing - the N-1 trip/transaction version of the same data. - - Here I check both the trip count and the stored transaction date, - for my own sanity, to confirm the active sector. - */ - - // active sector based on trip counters - const bool active_trip = n_uses(data, CHARLIE_ACTIVE_SECTOR_2) <= - n_uses(data, CHARLIE_ACTIVE_SECTOR_3); - - // active sector based on transaction date - DateTime ds2 = date_parse(data, 2, 0, 1); - DateTime ds3 = date_parse(data, 3, 0, 1); - const bool active_date = datetime_datetime_to_timestamp(&ds2) >= - datetime_datetime_to_timestamp(&ds3); - - // with all tested cards so far, this has been true - furi_assert(active_trip == active_date); - - return active_trip ? CHARLIE_ACTIVE_SECTOR_2 : CHARLIE_ACTIVE_SECTOR_3; -} - -static uint16_t type_parse(const MfClassicData* data) { - /* Card type data stored in the first 10bits of block 1 of sectors 2 & 3 (Block 9 & Block 13, from card start) - To my knowledge, card type should never change, so we can check either - without caring which is active. For my sanity, I check both, and assert equal. - */ - - // bitshift (2bytes = 16 bits) by 6bits for just first 10bits - const uint16_t type1 = pos_to_num(data, 2, 1, 0, 2) >> 6; - const uint16_t type2 = pos_to_num(data, 3, 1, 0, 2) >> 6; - furi_assert(type1 == type2); - - return type1; -} - -/* -static DateTime expiry(DateTime iss) { - // Per Metrodroid CharlieCard parser (https://github.com/metrodroid/metrodroid/blob/master/src/commonMain/kotlin/au/id/micolous/metrodroid/transit/charlie/CharlieCardTransitData.kt) - // Expiry not explicitly stored in card data; rather, calculated from date of issue - // Cards were first issued in 2006, expired in 5 years, w/ no printed expiry date - // Cards issued after 2011 expire in 10 years - // - // Per DEFCON31 researcher's work (cited above): - // Student cards last one school year and expire at the end of August the following year - // Pre-2011 issued cards expire in 7 years, not 5 as claimed by Metrodroid - // Post-2011 expire in 10 years, less one day - // Redundant function given the existance of the end validity field? - // Any important distinctions between the two? - - - // perhaps additionally clipping to 2030-12-__ in anticipation of upcoming system migration? - // need to get a new card to confirm. - - // TODO add card type logic for student card expiry - DateTime exp; - if(iss.year < 2011) { - // add 7 years; assumes average year of 8766 hrs (to account for leap years) - // may be off by a few hours as a result - exp = dt_delta(iss, 7 * 8766 * 60 * 60); - } else { - // add 10 years, subtract a day. Same assumption as above - exp = dt_delta(iss, ((10 * 8766) - 24) * 60 * 60); - } - - return exp; -}*/ - -static bool expired(DateTime expiry, DateTime last_trip) { - // if a card has sat unused for >2 years, expired (verify this claim?) - // else expired if current date > expiry date - - uint32_t ts_exp = datetime_datetime_to_timestamp(&expiry); - uint32_t ts_last = datetime_datetime_to_timestamp(&last_trip); - uint32_t ts_now = time_now(); - - return (ts_exp <= ts_now) | ((ts_now - ts_last) >= (2 * 365 * 24 * 60 * 60)); -} - -void locale_format_dt_cat(FuriString* out, const DateTime* dt) { - // helper to print datetimes - FuriString* s = furi_string_alloc(); - - LocaleDateFormat date_format = locale_get_date_format(); - const char* separator = (date_format == LocaleDateFormatDMY) ? "." : "/"; - locale_format_date(s, dt, date_format, separator); - furi_string_cat(out, s); - locale_format_time(s, dt, locale_get_time_format(), false); - furi_string_cat_printf(out, " "); - furi_string_cat(out, s); - - furi_string_free(s); -} - -void type_format_cat(FuriString* out, uint16_t type) { - const char* s; - if(!get_map_item(type, charliecard_types, kNumTypes, &s)) { - s = ""; - furi_string_cat_printf(out, "Unknown-%u", type); - } - - furi_string_cat_str(out, s); -} - -void money_format_cat(FuriString* out, Money money) { - furi_string_cat_printf(out, "$%u.%02u", money.dollars, money.cents); -} - -void trip_format_cat(FuriString* out, Trip trip) { - const char* sep = " "; - const char* sta; - - locale_format_dt_cat(out, &trip.date); - furi_string_cat_printf(out, "\n%s", !!(trip.g_flag & 0x1) ? "-" : "+"); - money_format_cat(out, trip.fare); - if(!!(trip.g_flag & 0x1) && (trip.fare.dollars == FARE_BUS.dollars) && - (trip.fare.cents == FARE_BUS.cents)) { - // if not a refill, and the fare amount is equal to bus fare (any better approach? flag bits for modality?) - // format for bus (gate ID on busses = posted bus #) - furi_string_cat_printf(out, "%sBus#%u", sep, trip.gate); - } else if(get_map_item(trip.gate, charliecard_fare_gate_ids, kNumFareGateIds, &sta)) { - // station found in fare gate ID map, append station name - furi_string_cat_str(out, sep); - furi_string_cat_str(out, sta); - } else { - // no found station in fare gate ID map & not a bus, just print ID w/o add'l info - furi_string_cat_printf(out, "%s%u", sep, trip.gate); - } - // print flags for debugging purposes - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - furi_string_cat_printf(out, "%s%u%s%u", sep, trip.g_flag, sep, trip.f_flag); - } -} - -static bool charliecard_parse(const NfcDevice* device, FuriString* parsed_data) { - furi_assert(device); - - const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); - - bool parsed = false; - - do { - // Verify card type - if(data->type != MfClassicType1k) break; - - // Verify key - // arbitrary sector in the main data portion - const uint8_t verify_sector = 3; - const MfClassicSectorTrailer* sec_tr = - mf_classic_get_sector_trailer_by_sector(data, verify_sector); - - const uint64_t key_a = - bit_lib_bytes_to_num_be(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); - const uint64_t key_b = - bit_lib_bytes_to_num_be(sec_tr->key_b.data, COUNT_OF(sec_tr->key_b.data)); - if(key_a != charliecard_1k_keys[verify_sector].a) break; - if(key_b != charliecard_1k_keys[verify_sector].b) break; - - // TODO: Verify add'l? - - const enum CharlieActiveSector active_sec_enum = get_active_sector(data); - const uint8_t active_sector = (active_sec_enum == CHARLIE_ACTIVE_SECTOR_2) ? 2 : 3; - - furi_string_cat_printf(parsed_data, "\e#CharlieCard"); - - size_t uid_len = 0; - const uint8_t* uid = mf_classic_get_uid(data, &uid_len); - uint32_t card_number = bit_lib_bytes_to_num_be(uid, 4); - furi_string_cat_printf(parsed_data, "\nSerial: 5-%lu", card_number); - - Money bal = money_parse(data, active_sector, 1, 5); - furi_string_cat_printf(parsed_data, "\nBal: "); - money_format_cat(parsed_data, bal); - - const uint16_t type = type_parse(data); - furi_string_cat_printf(parsed_data, "\nType: "); - type_format_cat(parsed_data, type); - - const uint16_t n_trips = n_uses(data, active_sec_enum); - furi_string_cat_printf(parsed_data, "\nTrip Count: %u", n_trips); - - const DateTime iss = date_parse(data, active_sector, 0, 6); - furi_string_cat_printf(parsed_data, "\nIssued: "); - locale_format_dt_cat(parsed_data, &iss); - - const DateTime e_v = end_validity_parse(data, active_sec_enum); - furi_string_cat_printf(parsed_data, "\nExpiry: "); - locale_format_dt_cat(parsed_data, &e_v); - - DateTime last = date_parse(data, active_sector, 0, 1); - furi_string_cat_printf(parsed_data, "\nExpired: %s", expired(e_v, last) ? "Yes" : "No"); - - Trip* trips = trips_parse(data); - furi_string_cat_printf(parsed_data, "\nTransactions:"); - for(size_t i = 0; i < CHARLIE_N_TRIP_HISTORY; i++) { - furi_string_cat_printf(parsed_data, "\n"); - trip_format_cat(parsed_data, trips[i]); - furi_string_cat_printf(parsed_data, "\n"); - } - free(trips); - - parsed = true; - } while(false); - - return parsed; -} - /* Actual implementation of app<>plugin interface */ static const NfcSupportedCardsPlugin charliecard_plugin = { .protocol = NfcProtocolMfClassic, From 3af66c78153161ce8ae61189e19d815813cb9211 Mon Sep 17 00:00:00 2001 From: Zachary Weiss Date: Sun, 7 Apr 2024 20:49:06 -0400 Subject: [PATCH 061/129] ASCII of known layouts, pass testing --- .../nfc/plugins/supported_cards/charliecard.c | 119 +++++++++++++----- 1 file changed, 89 insertions(+), 30 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/charliecard.c b/applications/main/nfc/plugins/supported_cards/charliecard.c index 7acbfebfe..963776047 100644 --- a/applications/main/nfc/plugins/supported_cards/charliecard.c +++ b/applications/main/nfc/plugins/supported_cards/charliecard.c @@ -157,9 +157,10 @@ typedef struct { } Trip; typedef struct { - DateTime start_valid; + bool valid; uint16_t type; - DateTime end_valid; + DateTime start; + DateTime end; } Pass; // IdMapping approach borrowed from Jeremy Cooper's 'clipper.c' @@ -650,6 +651,10 @@ uint32_t time_now() { return furi_hal_rtc_get_timestamp(); } +static bool is_debug() { + return furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug); +} + // ********************************************************** // ********************* DATA PARSING *********************** // ********************************************************** @@ -731,9 +736,8 @@ static DateTime end_validity_parse( uint8_t sector_num, uint8_t block_num, uint8_t byte_num) { - // End validity field is a bit odd; shares first byte with another variable (the card type field), - // occupying only the last 3 bits (and subsequent two bytes), hence bitmask - // TODO: what are the add'l 3 bits between type & end validity fields? + // End validity field is weird; shares first byte with another variable (the card type field), + // occupying the last 5 bits (and subsequent two bytes), hence bitmask uint32_t ts_charlie_ev = pos_to_num(data, sector_num, block_num, byte_num, 3) & 0x1FFFFF; // additionally, instead of minute deltas, is in 8 minute increments @@ -752,16 +756,33 @@ static Pass // WIP; testing only. Speculating it may be structured as follows // Sub-byte field divisions not drawn to scale, see code for exact bit offsets // - // 0 1 2 3 4 5 6 - // +----.----.----+----.-+--.----.----+ - // | start | type | end | - // +----.----.----+----.-+--.----.----+ + // 0 1 2 3 4 5 + // +----.----.----+----.----.----+ + // | type | end | start | + // +----.----.----+----.----.----+ + // + // "Blank" entries are as follows: + // 0 1 2 3 4 5 + // +----.----.----.----.----.--+-. + // | 00 20 00 00 00 0| + // +----.----.----.----.----.--+-. - DateTime start = date_parse(data, sector_num, block_num, byte_num); - uint16_t type = pos_to_num(data, sector_num, block_num, byte_num + 3, 2) >> 6; - DateTime end = end_validity_parse(data, sector_num, block_num, byte_num + 4); + // check for empty, if so, return struct filled w/ 0s + // (incl "valid" field: hence, "valid" is false-y) + if(pos_to_num(data, sector_num, block_num, byte_num, 6) == 0x002000000000) { + return (Pass){0}; + } - return (Pass){start, type, end}; + DateTime start = date_parse(data, sector_num, block_num, byte_num + 1); + uint16_t type = pos_to_num(data, sector_num, block_num, byte_num, 2) >> 6; + + // these values make sense for end + DateTime end = end_validity_parse(data, sector_num, block_num, byte_num + 1); + + // DateTime start = date_parse(data, sector_num, block_num, byte_num); + // uint16_t type = 0; // pos_to_num(data, sector_num, block_num, byte_num + 3, 2) >> 6; + + return (Pass){true, type, start, end}; } static Trip @@ -793,6 +814,39 @@ static Trip return (Trip){date, gate, g_flag, fare, f_flag}; } +// Manufacturer data (Sector 0) +// +// 0 1 2 3 4 5 6 7 8 9 A B C D E F +// +----.----.----.----+----+----.----.----.----+----+----.----.----.----.----+----+ +// 0x000 | UID |LRC | 88 04 00 C8 |unkn| 00 20 00 00 00 |unkn| +// +----.----.----.----+----+----.----.----.----+----+----.----.----.----.----+----+ +// 0x010 | 4E 0F 04 10 04 10 04 10 04 10 04 10 04 10 04 10 | +// +----.----.----.----.----.----.----.----.----.----.----.----.----.----.----.----+ +// 0x020 | ... 00 00 ... | +// +----.----.----.----.----.----.----.----.----.----.----.----.----.----.----.----+ + +// Trip/transaction counters (Sector 1) +// +// 0 1 2 3 4 5 6 7 8 9 A B C D E F +// +----.----.----.----.----.----.----.----.----.----.----.----.----.----.----+----+ +// 0x040 | 04 10 23 45 66 77 ... 00 00 ... | +// +----.----+----+----.----.----.----.----.----.----.----.----.----.----.----+----+ +// 0x050 | uses1 |unkn| ... 00 00 ... | +// +----.----+----+----.----.----.----.----.----.----.----.----.----.----.----.----+ +// 0x060 | uses2 |unkn| ... 00 00 ... | +// +----.----+----+----.----.----.----.----.----.----.----.----.----.----.----.----+ + +// Balance / type / last transaction (Sector 2 & 3) +// +// 0 1 2 3 4 5 6 7 8 9 A B C D E F +// +----+----.----.----+----.----+----.----.----+----.----+----.----+----+----.----+ +// 0x080 | 11 | date last | loc last| date issued | 65 00 | unknown | 00 | crc | +// +----+----.----.----+----+----+----+----+----+----.----+----.----+----+----.----+ +// 0x090 | type |end validity|unkn| balance | 00 | unknown | crc | +// +----.----.----.----+----+----.----+----+----.----.----.----.----.----+----.----+ +// 0x0A0 | 20 ... 00 00 ... 04 | crc | +// +----.----.----.----.----.----.----.----.----.----.----.----.----.----+----.----+ + static Pass* passes_parse(const MfClassicData* data) { // WIP. Read in all speculative passes into array // Sectors 4 & 5 speculated to contain pass data, @@ -801,17 +855,17 @@ static Pass* passes_parse(const MfClassicData* data) { // // 0 1 2 3 4 5 6 7 8 9 A B C D E F // +----.----.----.----.----.-+--.----+----.----.----.----.----.-+--.----+----.----+ - // 0x100 | pass1? |0 00 | pass2? |0 00 | crc1 | + // 0x100 | pass0? |0 00 | pass1? |0 00 | crc | // +----.----.----.----.----.-+--.----+----.----.----.----.----.-+--.----+----.----+ - // 0x110 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | crc2 | + // 0x110 | ... 00 00 ... | crc | // +----.----.----.----.----.----.----.----.----.----.----.----.----.----+----.----+ - // 0x120 | 00 00 00 00 00 00 00 05 00 00 00 00 00 00 | crc3 | + // 0x120 | ... 00 ... 05 | crc | // +----.----.----.----.----.----.----.----.----.----.----.----.----.----+----.----+ Pass* passes = malloc(sizeof(Pass) * CHARLIE_N_PASSES); for(size_t i = 0; i < CHARLIE_N_PASSES; i++) { - passes[i] = pass_parse(data, 4 + (i / 2), 1, (i % 2) * 7); + passes[i] = pass_parse(data, 4 + (i / 2), 0, (i % 2) * 7); } return passes; @@ -822,13 +876,13 @@ static Trip* trips_parse(const MfClassicData* data) { // // 0 1 2 3 4 5 6 7 8 9 A B C D E F // +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ - // 0x180 | trip0 | trip1 | crc1 | + // 0x180 | trip0 | trip1 | crc | // +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ // ... ... ... ... // +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ - // 0x1D0 | trip8 | trip9 | crc5 | + // 0x1D0 | trip8 | trip9 | crc | // +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ - // 0x1E0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | crc6 | + // 0x1E0 | ... 00 00 ... | crc | // +----.----.----.----.----.----.----.----.----.----.----.----.----.----+----.----+ // //Trips are not sorted, rather, appear to get overwritten sequentially. (eg, sorted modulo array rotation) @@ -947,19 +1001,24 @@ void type_format_cat(FuriString* out, uint16_t type) { } void pass_format_cat(FuriString* out, Pass pass) { - furi_string_cat_printf(out, "\nPass type: "); + furi_string_cat_printf(out, "\n-Type: "); type_format_cat(out, pass.type); - furi_string_cat_printf(out, "\nPass start: "); - locale_format_dt_cat(out, &pass.start_valid); - furi_string_cat_printf(out, "\nPass end: "); - locale_format_dt_cat(out, &pass.end_valid); + furi_string_cat_printf(out, "\n-Start: "); + locale_format_dt_cat(out, &pass.start); + furi_string_cat_printf(out, "\n-End: "); + locale_format_dt_cat(out, &pass.end); } void passes_format_cat(FuriString* out, Pass* passes) { - furi_string_cat_printf(out, "\nPasses:"); - for(size_t i = 0; i < CHARLIE_N_PASSES; i++) { - pass_format_cat(out, passes[i]); - furi_string_cat_printf(out, "\n"); + if(is_debug()) { + furi_string_cat_printf(out, "\nPasses (DEBUG & WIP):"); + for(size_t i = 0; i < CHARLIE_N_PASSES; i++) { + if(passes[i].valid) { + furi_string_cat_printf(out, "\nPass %u", i + 1); + pass_format_cat(out, passes[i]); + furi_string_cat_printf(out, "\n"); + } + } } } @@ -988,7 +1047,7 @@ void trip_format_cat(FuriString* out, Trip trip) { furi_string_cat_printf(out, "%s%u", sep, trip.gate); } // print flags for debugging purposes - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + if(is_debug()) { furi_string_cat_printf(out, "%s%u%s%u", sep, trip.g_flag, sep, trip.f_flag); } } From 5e3d3d5dbe1878b9fe6cb7d99d73e9f6a835c991 Mon Sep 17 00:00:00 2001 From: SG Date: Mon, 8 Apr 2024 04:05:44 +0300 Subject: [PATCH 062/129] I like to moving --- furi/core/memmgr_heap.c | 60 ++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/furi/core/memmgr_heap.c b/furi/core/memmgr_heap.c index f45c2da3b..fd0d1df5e 100644 --- a/furi/core/memmgr_heap.c +++ b/furi/core/memmgr_heap.c @@ -3,6 +3,7 @@ #include #include #include +#include extern const void __heap_start__; extern const void __heap_end__; @@ -11,9 +12,6 @@ static tlsf_t tlsf = NULL; static size_t heap_used = 0; static size_t heap_max_used = 0; -// Furi heap extension -#include - // Allocation tracking types DICT_DEF2(MemmgrHeapAllocDict, uint32_t, uint32_t) //-V1048 @@ -69,6 +67,34 @@ void memmgr_heap_disable_thread_trace(FuriThreadId thread_id) { memmgr_unlock(); } +static inline void memmgr_heap_trace_malloc(void* pointer, size_t size) { + FuriThreadId thread_id = furi_thread_get_current_id(); + if(thread_id && memmgr_heap_thread_trace_depth == 0) { + memmgr_heap_thread_trace_depth++; + MemmgrHeapAllocDict_t* alloc_dict = + MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); + if(alloc_dict) { + MemmgrHeapAllocDict_set_at(*alloc_dict, (uint32_t)pointer, (uint32_t)size); + } + memmgr_heap_thread_trace_depth--; + } +} + +static inline void memmgr_heap_trace_free(void* pointer) { + FuriThreadId thread_id = furi_thread_get_current_id(); + if(thread_id && memmgr_heap_thread_trace_depth == 0) { + memmgr_heap_thread_trace_depth++; + MemmgrHeapAllocDict_t* alloc_dict = + MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); + if(alloc_dict) { + // In some cases thread may want to release memory that was not allocated by it + const bool res = MemmgrHeapAllocDict_erase(*alloc_dict, (uint32_t)pointer); + UNUSED(res); + } + memmgr_heap_thread_trace_depth--; + } +} + size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id) { size_t leftovers = MEMMGR_HEAP_UNKNOWN; vTaskSuspendAll(); @@ -143,34 +169,6 @@ void memmgr_heap_walk_blocks(BlockWalker walker, void* context) { memmgr_unlock(); } -static inline void memmgr_heap_trace_malloc(void* pointer, size_t size) { - FuriThreadId thread_id = furi_thread_get_current_id(); - if(thread_id && memmgr_heap_thread_trace_depth == 0) { - memmgr_heap_thread_trace_depth++; - MemmgrHeapAllocDict_t* alloc_dict = - MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); - if(alloc_dict) { - MemmgrHeapAllocDict_set_at(*alloc_dict, (uint32_t)pointer, (uint32_t)size); - } - memmgr_heap_thread_trace_depth--; - } -} - -static inline void memmgr_heap_trace_free(void* pointer) { - FuriThreadId thread_id = furi_thread_get_current_id(); - if(thread_id && memmgr_heap_thread_trace_depth == 0) { - memmgr_heap_thread_trace_depth++; - MemmgrHeapAllocDict_t* alloc_dict = - MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); - if(alloc_dict) { - // In some cases thread may want to release memory that was not allocated by it - const bool res = MemmgrHeapAllocDict_erase(*alloc_dict, (uint32_t)pointer); - UNUSED(res); - } - memmgr_heap_thread_trace_depth--; - } -} - void* pvPortMalloc(size_t xSize) { // memory management in ISR is not allowed if(FURI_IS_IRQ_MODE()) { From e895ba0c89c91bb2a830c35c574e075cf0651715 Mon Sep 17 00:00:00 2001 From: SG Date: Mon, 8 Apr 2024 04:10:00 +0300 Subject: [PATCH 063/129] merge upcoming changes --- furi/core/memmgr_heap.c | 6 +++++- targets/f18/api_symbols.csv | 4 ++-- targets/f7/api_symbols.csv | 4 ++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/furi/core/memmgr_heap.c b/furi/core/memmgr_heap.c index fd0d1df5e..630e26bd9 100644 --- a/furi/core/memmgr_heap.c +++ b/furi/core/memmgr_heap.c @@ -187,7 +187,11 @@ void* pvPortMalloc(size_t xSize) { // allocate block void* data = tlsf_malloc(tlsf, xSize); if(data == NULL) { - furi_crash("out of memory"); + if(xSize == 0) { + furi_crash("malloc(0)"); + } else { + furi_crash("out of memory"); + } } // update heap usage diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 72116ccfd..79ad30648 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.5,, +Version,+,61.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1980,7 +1980,7 @@ Function,+,memmgr_heap_disable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_enable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_get_max_free_block,size_t, Function,+,memmgr_heap_get_thread_memory,size_t,FuriThreadId -Function,+,memmgr_heap_printf_free_blocks,void, +Function,+,memmgr_heap_walk_blocks,void,"BlockWalker, void*" Function,-,memmgr_pool_get_free,size_t, Function,-,memmgr_pool_get_max_block,size_t, Function,+,memmove,void*,"void*, const void*, size_t" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 4ddff921b..506a267ce 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.5,, +Version,+,61.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -2371,7 +2371,7 @@ Function,+,memmgr_heap_disable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_enable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_get_max_free_block,size_t, Function,+,memmgr_heap_get_thread_memory,size_t,FuriThreadId -Function,+,memmgr_heap_printf_free_blocks,void, +Function,+,memmgr_heap_walk_blocks,void,"BlockWalker, void*" Function,-,memmgr_pool_get_free,size_t, Function,-,memmgr_pool_get_max_block,size_t, Function,+,memmove,void*,"void*, const void*, size_t" From 3ea6d46c798ee07e4a34783ece781de33c9cb19e Mon Sep 17 00:00:00 2001 From: Zachary Weiss Date: Sun, 7 Apr 2024 22:10:46 -0400 Subject: [PATCH 064/129] Parsing function readability refactor --- .../nfc/plugins/supported_cards/charliecard.c | 233 +++++++++--------- 1 file changed, 118 insertions(+), 115 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/charliecard.c b/applications/main/nfc/plugins/supported_cards/charliecard.c index 963776047..7313b49ee 100644 --- a/applications/main/nfc/plugins/supported_cards/charliecard.c +++ b/applications/main/nfc/plugins/supported_cards/charliecard.c @@ -23,8 +23,6 @@ * — Reverse engineer passes (sectors 4 & 5?), impl. * — Infer transaction flag meanings * — Infer remaining unknown bytes in the balance sectors (2 & 3) - * – ASCII art &/or unified read function for the balance sectors, - * to improve readability / interpretability by others? * — Improve string output formatting, esp. of transaction log * — Mapping of buses to garages, and subsequently, route subsets via * http://roster.transithistory.org/ data @@ -99,11 +97,6 @@ #define CHARLIE_N_TRIP_HISTORY 10 #define CHARLIE_N_PASSES 4 -enum CharlieActiveSector { - CHARLIE_ACTIVE_SECTOR_2, - CHARLIE_ACTIVE_SECTOR_3, -}; - typedef struct { uint64_t a; uint64_t b; @@ -163,6 +156,18 @@ typedef struct { DateTime end; } Pass; +typedef struct { + uint16_t n_uses; + uint8_t active_balance_sector; +} CounterSector; + +typedef struct { + Money balance; + uint16_t type; + DateTime issued; + DateTime end_validity; +} BalanceSector; + // IdMapping approach borrowed from Jeremy Cooper's 'clipper.c' typedef struct { uint16_t id; @@ -656,7 +661,7 @@ static bool is_debug() { } // ********************************************************** -// ********************* DATA PARSING *********************** +// ******************** FIELD PARSING *********************** // ********************************************************** static Money money_parse( @@ -677,46 +682,6 @@ static DateTime return dt_delta(CHARLIE_EPOCH, ts_charlie * CHARLIE_TIME_DELTA_SECS); } -static uint16_t n_uses(const MfClassicData* data, const enum CharlieActiveSector active_sector) { - /* First two bytes of applicable block (sector 1, block 1 or 2 depending on active_sector) - The *lower* of the two values *minus one* is the true use count, - per DEFCON31 researcher's findings - */ - return pos_to_num(data, 1, 1 + active_sector, 0, 2) - 1; -} - -static enum CharlieActiveSector get_active_sector(const MfClassicData* data) { - /* Card has two transaction sectors (2 & 3) containing balance data, with two - corresponding trip counters in 0x50:0x51 & 0x60:0x61 (sector 1, byte 0:1 of blocks 1 & 2). - - The *lower* count variable corresponds to the active sector - (0x5_ lower -> 2 active, 0x6_ lower -> 3 active) - - Sectors 2 & 3 are (largely) identical, save for trip data. - Card seems to alternate between the two, with active sector storing - the current balance & recent trip/transaction, & the inactive sector storing - the N-1 trip/transaction version of the same data. - - Here I check both the trip count and the stored transaction date, - for my own sanity, to confirm the active sector. - */ - - // active sector based on trip counters - const bool active_trip = n_uses(data, CHARLIE_ACTIVE_SECTOR_2) <= - n_uses(data, CHARLIE_ACTIVE_SECTOR_3); - - // active sector based on transaction date - DateTime ds2 = date_parse(data, 2, 0, 1); - DateTime ds3 = date_parse(data, 3, 0, 1); - const bool active_date = datetime_datetime_to_timestamp(&ds2) >= - datetime_datetime_to_timestamp(&ds3); - - // with all tested cards so far, this has been true - furi_assert(active_trip == active_date); - - return active_trip ? CHARLIE_ACTIVE_SECTOR_2 : CHARLIE_ACTIVE_SECTOR_3; -} - static uint16_t type_parse(const MfClassicData* data) { /* Card type data stored in the first 10bits of block 1 of sectors 2 & 3 (Block 9 & Block 13, from card start) To my knowledge, card type should never change, so we can check either @@ -745,12 +710,6 @@ static DateTime end_validity_parse( return dt_delta(CHARLIE_EPOCH, ts_charlie_ev * CHARLIE_END_VALID_DELTA_SECS); } -static DateTime - main_end_validity_parse(const MfClassicData* data, enum CharlieActiveSector active_sec) { - // primary card type end validity; checked in active sector (probably the same across both 2 & 3) - return end_validity_parse(data, (active_sec == CHARLIE_ACTIVE_SECTOR_2) ? 2 : 3, 1, 1); -} - static Pass pass_parse(const MfClassicData* data, uint8_t sector_num, uint8_t block_num, uint8_t byte_num) { // WIP; testing only. Speculating it may be structured as follows @@ -814,53 +773,100 @@ static Trip return (Trip){date, gate, g_flag, fare, f_flag}; } -// Manufacturer data (Sector 0) -// -// 0 1 2 3 4 5 6 7 8 9 A B C D E F -// +----.----.----.----+----+----.----.----.----+----+----.----.----.----.----+----+ -// 0x000 | UID |LRC | 88 04 00 C8 |unkn| 00 20 00 00 00 |unkn| -// +----.----.----.----+----+----.----.----.----+----+----.----.----.----.----+----+ -// 0x010 | 4E 0F 04 10 04 10 04 10 04 10 04 10 04 10 04 10 | -// +----.----.----.----.----.----.----.----.----.----.----.----.----.----.----.----+ -// 0x020 | ... 00 00 ... | -// +----.----.----.----.----.----.----.----.----.----.----.----.----.----.----.----+ +// ********************************************************** +// ******************* SECTOR PARSING *********************** +// ********************************************************** -// Trip/transaction counters (Sector 1) -// -// 0 1 2 3 4 5 6 7 8 9 A B C D E F -// +----.----.----.----.----.----.----.----.----.----.----.----.----.----.----+----+ -// 0x040 | 04 10 23 45 66 77 ... 00 00 ... | -// +----.----+----+----.----.----.----.----.----.----.----.----.----.----.----+----+ -// 0x050 | uses1 |unkn| ... 00 00 ... | -// +----.----+----+----.----.----.----.----.----.----.----.----.----.----.----.----+ -// 0x060 | uses2 |unkn| ... 00 00 ... | -// +----.----+----+----.----.----.----.----.----.----.----.----.----.----.----.----+ +static uint32_t mfg_sector_parse(const MfClassicData* data) { + // Manufacturer data (Sector 0) + // + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + // +----.----.----.----+----+----.----.----.----+----+----.----.----.----.----+----+ + // 0x000 | UID |LRC | 88 04 00 C8 |unkn| 00 20 00 00 00 |unkn| + // +----.----.----.----+----+----.----.----.----+----+----.----.----.----.----+----+ + // 0x010 | 4E 0F 04 10 04 10 04 10 04 10 04 10 04 10 04 10 | + // +----.----.----.----.----.----.----.----.----.----.----.----.----.----.----.----+ + // 0x020 | ... 00 00 ... | + // +----.----.----.----.----.----.----.----.----.----.----.----.----.----.----.----+ -// Balance / type / last transaction (Sector 2 & 3) -// -// 0 1 2 3 4 5 6 7 8 9 A B C D E F -// +----+----.----.----+----.----+----.----.----+----.----+----.----+----+----.----+ -// 0x080 | 11 | date last | loc last| date issued | 65 00 | unknown | 00 | crc | -// +----+----.----.----+----+----+----+----+----+----.----+----.----+----+----.----+ -// 0x090 | type |end validity|unkn| balance | 00 | unknown | crc | -// +----.----.----.----+----+----.----+----+----.----.----.----.----.----+----.----+ -// 0x0A0 | 20 ... 00 00 ... 04 | crc | -// +----.----.----.----.----.----.----.----.----.----.----.----.----.----+----.----+ + size_t uid_len = 0; + const uint8_t* uid = mf_classic_get_uid(data, &uid_len); + const uint32_t card_number = bit_lib_bytes_to_num_be(uid, 4); + + return card_number; +} + +static CounterSector counter_sector_parse(const MfClassicData* data) { + // Trip/transaction counters (Sector 1) + // + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + // +----.----.----.----.----.----.----.----.----.----.----.----.----.----.----+----+ + // 0x040 | 04 10 23 45 66 77 ... 00 00 ... | + // +----.----+----+----.----.----.----.----.----.----.----.----.----.----.----+----+ + // 0x050 | uses1 |unkn| ... 00 00 ... | + // +----.----+----+----.----.----.----.----.----.----.----.----.----.----.----.----+ + // 0x060 | uses2 |unkn| ... 00 00 ... | + // +----.----+----+----.----.----.----.----.----.----.----.----.----.----.----.----+ + + // Card has two transaction sectors (2 & 3) containing balance data, with two + // corresponding trip counters in 0x50:0x51 & 0x60:0x61 (sector 1, byte 0:1 of blocks 1 & 2). + + // The *lower* of the two values *minus one* is the true use count, + // and corresponds to the active balance sector, + // (0x50 counter lower -> sector 2 active, 0x60 counter lower -> 3 active) + // per DEFCON31 researcher's findings + + const uint16_t n_uses1 = pos_to_num(data, 1, 1, 0, 2); + const uint16_t n_uses2 = pos_to_num(data, 1, 2, 0, 2); + + const bool is_sec2_active = n_uses1 <= n_uses2; + const uint8_t active_sector = is_sec2_active ? 2 : 3; + const uint16_t n_uses = (is_sec2_active ? n_uses1 : n_uses2) - 1; + + return (CounterSector){n_uses, active_sector}; +} + +static BalanceSector balance_sector_parse(const MfClassicData* data, uint8_t active_sector) { + // Balance / type / last transaction (Sector 2 or 3) + // + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + // +----+----.----.----+----.----+----.----.----+----.----+----.----+----+----.----+ + // 0x080 | 11 | date last | loc last| date issued | 65 00 | unknown | 00 | crc | 0x0C0 + // +----+----.----.----+----+----+----+----+----+----.----+----.----+----+----.----+ + // 0x090 | type |end validity|unkn| balance | 00 | unknown | crc | 0x0D0 + // +----.----.----.----+----+----.----+----+----.----.----.----.----.----+----.----+ + // 0x0A0 | 20 ... 00 00 ... 04 | crc | 0x0E0 + // +----.----.----.----.----.----.----.----.----.----.----.----.----.----+----.----+ + // + // "Active" balance sector alternates between 2 and 3 + // Last trip/transaction info in balance sector (date last & loc last) + // also included in transaction log, hence don't bother to read here + // + // Inactive balance sector represent the transaction N-1 balance data + // (where active sector represents data from transaction N). + + const DateTime issued = date_parse(data, active_sector, 0, 6); + const DateTime end_validity = end_validity_parse(data, active_sector, 1, 1); + const uint16_t type = type_parse(data); + const Money bal = money_parse(data, active_sector, 1, 5); + + return (BalanceSector){bal, type, issued, end_validity}; +} static Pass* passes_parse(const MfClassicData* data) { - // WIP. Read in all speculative passes into array - // Sectors 4 & 5 speculated to contain pass data, - // 4 separate fields? active vs inactive sector for 2 passes? - // Sector 4 visualized below; sector 5 layout the same. + // Passes, speculative (Sectors 4 &/or 5) // // 0 1 2 3 4 5 6 7 8 9 A B C D E F // +----.----.----.----.----.-+--.----+----.----.----.----.----.-+--.----+----.----+ - // 0x100 | pass0? |0 00 | pass1? |0 00 | crc | + // 0x100 | pass0? |0 00 | pass1? |0 00 | crc | 0x140 // +----.----.----.----.----.-+--.----+----.----.----.----.----.-+--.----+----.----+ - // 0x110 | ... 00 00 ... | crc | + // 0x110 | ... 00 00 ... | crc | 0x150 // +----.----.----.----.----.----.----.----.----.----.----.----.----.----+----.----+ - // 0x120 | ... 00 ... 05 | crc | + // 0x120 | ... 00 ... 05 | crc | 0x160 // +----.----.----.----.----.----.----.----.----.----.----.----.----.----+----.----+ + // + // WIP. Read in all speculative passes into array + // 4 separate fields? active vs inactive sector for 2 passes? Pass* passes = malloc(sizeof(Pass) * CHARLIE_N_PASSES); @@ -872,7 +878,7 @@ static Pass* passes_parse(const MfClassicData* data) { } static Trip* trips_parse(const MfClassicData* data) { - // Sectors 6 & 7 store the last 10 trips. Overall layout as follows: + // Transaction history (Sectors 6–7) // // 0 1 2 3 4 5 6 7 8 9 A B C D E F // +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ @@ -885,7 +891,8 @@ static Trip* trips_parse(const MfClassicData* data) { // 0x1E0 | ... 00 00 ... | crc | // +----.----.----.----.----.----.----.----.----.----.----.----.----.----+----.----+ // - //Trips are not sorted, rather, appear to get overwritten sequentially. (eg, sorted modulo array rotation) + // Transactions are not sorted, rather, appear to get overwritten + // sequentially. (eg, sorted modulo array rotation) Trip* trips = malloc(sizeof(Trip) * CHARLIE_N_TRIP_HISTORY); @@ -958,7 +965,7 @@ static DateTime expiry(DateTime iss) { } return exp; -}*/ +} static bool expired(DateTime expiry, DateTime last_trip) { // if a card has sat unused for >2 years, expired (verify this claim?) @@ -970,6 +977,7 @@ static bool expired(DateTime expiry, DateTime last_trip) { return (ts_exp <= ts_now) | ((ts_now - ts_last) >= (2 * 365 * 24 * 60 * 60)); } +*/ // ********************************************************** // ****************** STRING FORMATTING ********************* @@ -1011,7 +1019,7 @@ void pass_format_cat(FuriString* out, Pass pass) { void passes_format_cat(FuriString* out, Pass* passes) { if(is_debug()) { - furi_string_cat_printf(out, "\nPasses (DEBUG & WIP):"); + furi_string_cat_printf(out, "\nPasses (DEBUG / WIP):"); for(size_t i = 0; i < CHARLIE_N_PASSES; i++) { if(passes[i].valid) { furi_string_cat_printf(out, "\nPass %u", i + 1); @@ -1091,43 +1099,38 @@ static bool charliecard_parse(const NfcDevice* device, FuriString* parsed_data) // TODO: Verify add'l? - const enum CharlieActiveSector active_sec_enum = get_active_sector(data); - const uint8_t active_sector = (active_sec_enum == CHARLIE_ACTIVE_SECTOR_2) ? 2 : 3; + // parse card data + const uint32_t card_number = mfg_sector_parse(data); + const CounterSector counter_sector = counter_sector_parse(data); + const BalanceSector balance_sector = + balance_sector_parse(data, counter_sector.active_balance_sector); + Pass* passes = passes_parse(data); + Trip* trips = trips_parse(data); + // print/append card data furi_string_cat_printf(parsed_data, "\e#CharlieCard"); - - size_t uid_len = 0; - const uint8_t* uid = mf_classic_get_uid(data, &uid_len); - uint32_t card_number = bit_lib_bytes_to_num_be(uid, 4); furi_string_cat_printf(parsed_data, "\nSerial: 5-%lu", card_number); - Money bal = money_parse(data, active_sector, 1, 5); furi_string_cat_printf(parsed_data, "\nBal: "); - money_format_cat(parsed_data, bal); + money_format_cat(parsed_data, balance_sector.balance); - const uint16_t type = type_parse(data); furi_string_cat_printf(parsed_data, "\nType: "); - type_format_cat(parsed_data, type); + type_format_cat(parsed_data, balance_sector.type); - Pass* passes = passes_parse(data); passes_format_cat(parsed_data, passes); free(passes); - const uint16_t n_trips = n_uses(data, active_sec_enum); - furi_string_cat_printf(parsed_data, "\nTrip Count: %u", n_trips); + furi_string_cat_printf(parsed_data, "\nTrip Count: %u", counter_sector.n_uses); - const DateTime iss = date_parse(data, active_sector, 0, 6); furi_string_cat_printf(parsed_data, "\nIssued: "); - locale_format_dt_cat(parsed_data, &iss); + locale_format_dt_cat(parsed_data, &balance_sector.issued); - const DateTime e_v = main_end_validity_parse(data, active_sec_enum); furi_string_cat_printf(parsed_data, "\nExpiry: "); - locale_format_dt_cat(parsed_data, &e_v); + locale_format_dt_cat(parsed_data, &balance_sector.end_validity); - DateTime last = date_parse(data, active_sector, 0, 1); - furi_string_cat_printf(parsed_data, "\nExpired: %s", expired(e_v, last) ? "Yes" : "No"); + // const DateTime last = date_parse(data, active_sector, 0, 1); + // furi_string_cat_printf(parsed_data, "\nExpired: %s", expired(e_v, last) ? "Yes" : "No"); - Trip* trips = trips_parse(data); trips_format_cat(parsed_data, trips); free(trips); From 6776bdae1d6b0b14e9f0a6fbd8bf702f6bf86724 Mon Sep 17 00:00:00 2001 From: Aleksandr Kutuzov Date: Mon, 8 Apr 2024 18:41:40 +0900 Subject: [PATCH 065/129] Ble: set max connection interal same as min, kinda speedups everything --- lib/ble_profile/extra_profiles/hid_profile.c | 4 ++-- targets/f7/ble_glue/extra_beacon.c | 3 ++- targets/f7/ble_glue/profiles/serial_profile.c | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/ble_profile/extra_profiles/hid_profile.c b/lib/ble_profile/extra_profiles/hid_profile.c index aaa66d960..7231fdb19 100644 --- a/lib/ble_profile/extra_profiles/hid_profile.c +++ b/lib/ble_profile/extra_profiles/hid_profile.c @@ -380,8 +380,8 @@ static GapConfig template_config = { .pairing_method = GapPairingPinCodeVerifyYesNo, .conn_param = { - .conn_int_min = 0x18, // 30 ms - .conn_int_max = 0x24, // 45 ms + .conn_int_min = 0x18, // AN5289: 4.7, we need at least 25ms + advertisement, which is 30 ms + .conn_int_max = 0x18, // 30 ms .slave_latency = 0, .supervisor_timeout = 0, }, diff --git a/targets/f7/ble_glue/extra_beacon.c b/targets/f7/ble_glue/extra_beacon.c index 2ef3e056f..60338cb76 100644 --- a/targets/f7/ble_glue/extra_beacon.c +++ b/targets/f7/ble_glue/extra_beacon.c @@ -9,7 +9,8 @@ #define GAP_MS_TO_SCAN_INTERVAL(x) ((uint16_t)((x) / 0.625)) // Also used as an indicator of whether the beacon had ever been configured -#define GAP_MIN_ADV_INTERVAL_MS (20) +// AN5289: 4.7, we need at least 25ms + advertisement, which is 30 ms +#define GAP_MIN_ADV_INTERVAL_MS (30u) typedef struct { GapExtraBeaconConfig last_config; diff --git a/targets/f7/ble_glue/profiles/serial_profile.c b/targets/f7/ble_glue/profiles/serial_profile.c index a3949abfc..165b81330 100644 --- a/targets/f7/ble_glue/profiles/serial_profile.c +++ b/targets/f7/ble_glue/profiles/serial_profile.c @@ -46,8 +46,8 @@ static GapConfig serial_template_config = { .bonding_mode = true, .pairing_method = GapPairingPinCodeShow, .conn_param = { - .conn_int_min = 0x18, // 30 ms - .conn_int_max = 0x24, // 45 ms + .conn_int_min = 0x18, // AN5289: 4.7, we need at least 25ms + advertisement, which is 30 ms + .conn_int_max = 0x18, // 30 ms .slave_latency = 0, .supervisor_timeout = 0, }}; From 463dc8c727e91cde3027c58b238bb286506cef89 Mon Sep 17 00:00:00 2001 From: SG Date: Mon, 8 Apr 2024 20:27:58 +0300 Subject: [PATCH 066/129] memmgr: alloc aligned, realloc --- furi/core/memmgr.c | 39 ++++---------- furi/core/memmgr_heap.c | 116 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 123 insertions(+), 32 deletions(-) diff --git a/furi/core/memmgr.c b/furi/core/memmgr.c index 768adc05d..0fe79dff0 100644 --- a/furi/core/memmgr.c +++ b/furi/core/memmgr.c @@ -4,6 +4,8 @@ #include extern void* pvPortMalloc(size_t xSize); +extern void* pvPortAllocAligned(size_t xSize, size_t xAlignment); +extern void* pvPortRealloc(void* pv, size_t xSize); extern void vPortFree(void* pv); extern size_t xPortGetFreeHeapSize(void); extern size_t xPortGetTotalHeapSize(void); @@ -18,18 +20,7 @@ void free(void* ptr) { } void* realloc(void* ptr, size_t size) { - if(size == 0) { - vPortFree(ptr); - return NULL; - } - - void* p = pvPortMalloc(size); - if(ptr != NULL) { - memcpy(p, ptr, size); - vPortFree(ptr); - } - - return p; + return pvPortRealloc(ptr, size); } void* calloc(size_t count, size_t size) { @@ -47,6 +38,14 @@ char* strdup(const char* s) { return y; } +void* aligned_malloc(size_t size, size_t alignment) { + return pvPortAllocAligned(size, alignment); +} + +void aligned_free(void* p) { + vPortFree(p); +} + size_t memmgr_get_free_heap(void) { return xPortGetFreeHeapSize(); } @@ -92,20 +91,4 @@ size_t memmgr_pool_get_free(void) { size_t memmgr_pool_get_max_block(void) { return furi_hal_memory_max_pool_block(); -} - -void* aligned_malloc(size_t size, size_t alignment) { - void* p1; // original block - void** p2; // aligned block - int offset = alignment - 1 + sizeof(void*); - if((p1 = (void*)malloc(size + offset)) == NULL) { - return NULL; - } - p2 = (void**)(((size_t)(p1) + offset) & ~(alignment - 1)); - p2[-1] = p1; - return p2; -} - -void aligned_free(void* p) { - free(((void**)p)[-1]); } \ No newline at end of file diff --git a/furi/core/memmgr_heap.c b/furi/core/memmgr_heap.c index 630e26bd9..d6ca3f255 100644 --- a/furi/core/memmgr_heap.c +++ b/furi/core/memmgr_heap.c @@ -204,11 +204,11 @@ void* pvPortMalloc(size_t xSize) { // clear block content memset(data, 0, xSize); + memmgr_unlock(); + // trace allocation memmgr_heap_trace_malloc(data, xSize); - memmgr_unlock(); - return data; } @@ -233,13 +233,121 @@ void vPortFree(void* pv) { // free tlsf_free(tlsf, pv); + memmgr_unlock(); + // trace free memmgr_heap_trace_free(pv); - - memmgr_unlock(); } } +extern void* pvPortAllocAligned(size_t xSize, size_t xAlignment) { + // memory management in ISR is not allowed + if(FURI_IS_IRQ_MODE()) { + furi_crash("memmgt in ISR"); + } + + memmgr_lock(); + + // initialize tlsf, if not initialized + if(tlsf == NULL) { + size_t pool_size = (size_t)&__heap_end__ - (size_t)&__heap_start__; + tlsf = tlsf_create_with_pool((void*)&__heap_start__, pool_size, pool_size); + memmgr_heap_init(); + } + + // allocate block + void* data = tlsf_memalign(tlsf, xAlignment, xSize); + if(data == NULL) { + if(xSize == 0) { + furi_crash("malloc_aligned(0)"); + } else { + furi_crash("out of memory"); + } + } + + // update heap usage + heap_used += tlsf_block_size(data); + heap_used += tlsf_alloc_overhead(); + if(heap_used > heap_max_used) { + heap_max_used = heap_used; + } + + memmgr_unlock(); + + // clear block content + memset(data, 0, xSize); + + // trace allocation + memmgr_heap_trace_malloc(data, xSize); + + return data; +} + +extern void* pvPortRealloc(void* pv, size_t xSize) { + // size 0 is considered as free + if(xSize == 0) { + vPortFree(pv); + return NULL; + } + + // realloc(NULL, size) is equivalent to malloc(size) + if(pv == NULL) { + return pvPortMalloc(xSize); + } + + // realloc things // + + // memory management in ISR is not allowed + if(FURI_IS_IRQ_MODE()) { + furi_crash("memmgt in ISR"); + } + + memmgr_lock(); + + // initialize tlsf, if not initialized + if(tlsf == NULL) { + size_t pool_size = (size_t)&__heap_end__ - (size_t)&__heap_start__; + tlsf = tlsf_create_with_pool((void*)&__heap_start__, pool_size, pool_size); + memmgr_heap_init(); + } + + // trace old block as free + size_t old_size = 0; + if(pv != NULL) { + old_size = tlsf_block_size(pv); + memmgr_heap_trace_free(pv); + } + + // reallocate block + void* data = tlsf_realloc(tlsf, pv, xSize); + if(data == NULL) { + if(xSize == 0) { + furi_crash("realloc(0)"); + } else { + furi_crash("out of memory"); + } + } + + // update heap usage + heap_used -= old_size; + heap_used += tlsf_block_size(data); + if(heap_used > heap_max_used) { + heap_max_used = heap_used; + } + + // clear remain block content, if the new size is bigger + if(xSize > old_size) { + memset((uint8_t*)data + old_size, 0, xSize - old_size); + } + + memmgr_unlock(); + + // trace allocation + memmgr_heap_trace_malloc(data, xSize); + + return data; +} + size_t xPortGetFreeHeapSize(void) { return memmgr_get_heap_size() - heap_used - tlsf_size(tlsf); } From 00f1263f7d530dcda8dfd849c33cfe817d8d5844 Mon Sep 17 00:00:00 2001 From: Kuronons <110337784+Kuronons@users.noreply.github.com> Date: Tue, 9 Apr 2024 02:22:55 +0200 Subject: [PATCH 067/129] MNTM Pack - Adding Mafia (erf...) yappy asset --- .../Icons/Dolphin/DolphinMafia_119x62.png | Bin 0 -> 2058 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/packs/Momentum/Icons/Dolphin/DolphinMafia_119x62.png diff --git a/assets/packs/Momentum/Icons/Dolphin/DolphinMafia_119x62.png b/assets/packs/Momentum/Icons/Dolphin/DolphinMafia_119x62.png new file mode 100644 index 0000000000000000000000000000000000000000..ccaf12307acb1f39d67d09e7ea8e1dd951342138 GIT binary patch literal 2058 zcmbVN2~ZPP7>?Dk7H_DnmX2;it*C5vHye(u28#p=Vh{lZv^Z{dA0#EoF4@HdRHj0Q zf>1A_1y2-pys%n9acJuWIG|RUaZo|TDp(a9L1i3E!M+4YosO+eci!&1zW4p-`!^>d z{ELw;AGt^*l98by>PXmI;Ojr^9r)co!Yvhc@9`mVfsqLD^5ufJ*$$&cx!MC9|&~%kTf%h zN(+x>(vlb@E1l(!%(POFzz9Sdu^J5~fwHQk4qgh5?aP=HaX`c*l{COE5Q)`9Ac342 zAU-l2WpJECe3df7N8v5^^+M#h9LEUQyivlJQutA%9O-|gP@0)dpd!`5{j%UpCDn-{ zPhptFVv$)$8D~z!2&Gbq;c`qaM@c7ypz@>^t%Z<471WaM#ylxNe&>(IB|T6+29b)G8ixbBQS{q#1c*{^pZy7guj;7 zXmp|lF=!dosNc5NqJ`*G9$=9`;8M&C2wDaudJHJW>o}1Ublkt#;oNpw!&8A~K#QC? zn&S)ufr=PlLF957fq2H~O)O^-rrM?S9{_4v1S%V?-Ol!mDR2l`_@|hEjN%eaApn1LZ6fN zY<6T76`~hl=u-RdM-)iz9~tzBBatYYvFEx<%GiSpSZV*V{(t+akJh3Ckn}%--v<-8 z1kpm9K|mt(=^K*^gUVy}TiIs^_D>TXd#~K!0XQrR_Mx*L!HKh?0TV1^W>~Lwm)?w* zNZu(6RR=^{8y;BMtpjctxZpAhc~DcvO(k95bKJG8o@_mj5qZ-1l zCF_o6%-2bMif?Wv+t<4hL2(<%;?{!f3J)Q>Zgik6>r6rRp3EX!;c3^1s>lajBhd5e zi*u36+BqRt8pl5ET1R?XqB`QU$Ca(WZ!6wkpxocmI(R{D!;G=Jul-t7(_FqdXC(2* zZyCWcRlP+u!m6tZm)64pL*>th^mM2XcE?@K?u<&ja{2O)r3ZuR)|ysaPbw^_pNu{% z3BItjyzwB&7fS9QiM(*V@dZY2-_^e3fM?mv(#ySz?gf0i_siu`Q<}_AH;n)xsRzj8 zP4(R%{b8k&I5Y9av;*al)n$L86K?Y+FBzzw-yYfN!l&Ezw2$gOSzrsO(}9ochoVeI@&dMu&-;+xujIe zqe}N8;!0NjH$xXqT$mfa+puNDbj#z8Y5rObdMoecium}WtF?&>)?|G@bVQC@>y45b z@1kQT^B{{;`z`A)qG?(>s zUDb#64i9ZUKiBAfnBQOBRg_z!l6$p~Ve4cGs-H7*3%~n)R#bDH|C)WtJGQNCerEq$ NhX#eK56@nj@fS5W8v_6U literal 0 HcmV?d00001 From e3c3ba440c27867f44a9feda258435cd4407152a Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Tue, 9 Apr 2024 03:09:04 +0100 Subject: [PATCH 068/129] Remove old workflows --nobuild --- .github/workflows/webhook.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/webhook.yml b/.github/workflows/webhook.yml index 7409e201e..003442a1f 100644 --- a/.github/workflows/webhook.yml +++ b/.github/workflows/webhook.yml @@ -7,8 +7,6 @@ on: - "Build" - "Lint" - "Release" - - "SonarCloud" - - "Submodules" types: - "completed" issues: From 3a4bff82f40b6f2856ef5ebedb5463513405b3cd Mon Sep 17 00:00:00 2001 From: SG Date: Tue, 9 Apr 2024 05:12:15 +0300 Subject: [PATCH 069/129] Furi: distinct name for auxiliary memory pool --- applications/services/cli/cli_commands.c | 4 ++-- furi/core/memmgr.c | 4 ++-- furi/core/memmgr.h | 10 +++++----- furi/core/thread.c | 4 ++-- targets/f18/api_symbols.csv | 6 +++--- targets/f7/api_symbols.csv | 6 +++--- targets/f7/fatfs/sector_cache.c | 2 +- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index 9105e40e8..56d05785f 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -425,8 +425,8 @@ void cli_command_free(Cli* cli, FuriString* args, void* context) { printf("Minimum heap size: %zu\r\n", memmgr_get_minimum_free_heap()); printf("Maximum heap block: %zu\r\n", memmgr_heap_get_max_free_block()); - printf("Pool free: %zu\r\n", memmgr_pool_get_free()); - printf("Maximum pool block: %zu\r\n", memmgr_pool_get_max_block()); + printf("Aux pool total free: %zu\r\n", memmgr_aux_pool_get_free()); + printf("Aux pool max free block: %zu\r\n", memmgr_pool_get_max_block()); } typedef struct { diff --git a/furi/core/memmgr.c b/furi/core/memmgr.c index 0fe79dff0..5785bff6e 100644 --- a/furi/core/memmgr.c +++ b/furi/core/memmgr.c @@ -78,14 +78,14 @@ void* __wrap__realloc_r(struct _reent* r, void* ptr, size_t size) { return realloc(ptr, size); } -void* memmgr_alloc_from_pool(size_t size) { +void* memmgr_aux_pool_alloc(size_t size) { void* p = furi_hal_memory_alloc(size); if(p == NULL) p = malloc(size); return p; } -size_t memmgr_pool_get_free(void) { +size_t memmgr_aux_pool_get_free(void) { return furi_hal_memory_get_free(); } diff --git a/furi/core/memmgr.h b/furi/core/memmgr.h index bc0c35faa..7a3f7b8f3 100644 --- a/furi/core/memmgr.h +++ b/furi/core/memmgr.h @@ -51,22 +51,22 @@ void* aligned_malloc(size_t size, size_t alignment); void aligned_free(void* p); /** - * @brief Allocate memory from separate memory pool. That memory can't be freed. + * @brief Allocate memory from the auxiliary memory pool. That memory can't be freed. * * @param size * @return void* */ -void* memmgr_alloc_from_pool(size_t size); +void* memmgr_aux_pool_alloc(size_t size); /** - * @brief Get free memory pool size + * @brief Get the auxiliary poll free memory size * * @return size_t */ -size_t memmgr_pool_get_free(void); +size_t memmgr_aux_pool_get_free(void); /** - * @brief Get max free block size from memory pool + * @brief Get max free block size from the auxiliary memory pool * * @return size_t */ diff --git a/furi/core/thread.c b/furi/core/thread.c index f9f73b4f7..c9bf79d32 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -276,8 +276,8 @@ void furi_thread_start(FuriThread* thread) { stack, thread, priority, - memmgr_alloc_from_pool(sizeof(StackType_t) * stack), - memmgr_alloc_from_pool(sizeof(StaticTask_t))); + memmgr_aux_pool_alloc(sizeof(StackType_t) * stack), + memmgr_aux_pool_alloc(sizeof(StaticTask_t))); } else { BaseType_t ret = xTaskCreate( furi_thread_body, thread->name, stack, thread, priority, &thread->task_handle); diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 79ad30648..d1beda897 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,61.0,, +Version,+,61.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1972,7 +1972,8 @@ Function,+,memchr,void*,"const void*, int, size_t" Function,+,memcmp,int,"const void*, const void*, size_t" Function,+,memcpy,void*,"void*, const void*, size_t" Function,-,memmem,void*,"const void*, size_t, const void*, size_t" -Function,-,memmgr_alloc_from_pool,void*,size_t +Function,-,memmgr_aux_pool_alloc,void*,size_t +Function,-,memmgr_aux_pool_get_free,size_t, Function,+,memmgr_get_free_heap,size_t, Function,+,memmgr_get_minimum_free_heap,size_t, Function,+,memmgr_get_total_heap,size_t, @@ -1981,7 +1982,6 @@ Function,+,memmgr_heap_enable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_get_max_free_block,size_t, Function,+,memmgr_heap_get_thread_memory,size_t,FuriThreadId Function,+,memmgr_heap_walk_blocks,void,"BlockWalker, void*" -Function,-,memmgr_pool_get_free,size_t, Function,-,memmgr_pool_get_max_block,size_t, Function,+,memmove,void*,"void*, const void*, size_t" Function,-,mempcpy,void*,"void*, const void*, size_t" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 506a267ce..291e57b27 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,61.0,, +Version,+,61.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -2363,7 +2363,8 @@ Function,+,memchr,void*,"const void*, int, size_t" Function,+,memcmp,int,"const void*, const void*, size_t" Function,+,memcpy,void*,"void*, const void*, size_t" Function,-,memmem,void*,"const void*, size_t, const void*, size_t" -Function,-,memmgr_alloc_from_pool,void*,size_t +Function,-,memmgr_aux_pool_alloc,void*,size_t +Function,-,memmgr_aux_pool_get_free,size_t, Function,+,memmgr_get_free_heap,size_t, Function,+,memmgr_get_minimum_free_heap,size_t, Function,+,memmgr_get_total_heap,size_t, @@ -2372,7 +2373,6 @@ Function,+,memmgr_heap_enable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_get_max_free_block,size_t, Function,+,memmgr_heap_get_thread_memory,size_t,FuriThreadId Function,+,memmgr_heap_walk_blocks,void,"BlockWalker, void*" -Function,-,memmgr_pool_get_free,size_t, Function,-,memmgr_pool_get_max_block,size_t, Function,+,memmove,void*,"void*, const void*, size_t" Function,-,mempcpy,void*,"void*, const void*, size_t" diff --git a/targets/f7/fatfs/sector_cache.c b/targets/f7/fatfs/sector_cache.c index 319dd2173..df86cb7f1 100644 --- a/targets/f7/fatfs/sector_cache.c +++ b/targets/f7/fatfs/sector_cache.c @@ -19,7 +19,7 @@ static SectorCache* cache = NULL; void sector_cache_init(void) { if(cache == NULL) { - cache = memmgr_alloc_from_pool(sizeof(SectorCache)); + cache = memmgr_aux_pool_alloc(sizeof(SectorCache)); } if(cache != NULL) { From e1f7f86c94c02fe4b2ab163c56ef431769eb6630 Mon Sep 17 00:00:00 2001 From: SG Date: Tue, 9 Apr 2024 05:12:56 +0300 Subject: [PATCH 070/129] Furi: put idle and timer thread to mem2 --- furi/flipper.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/furi/flipper.c b/furi/flipper.c index c7ba3b4fb..6c7b9831a 100644 --- a/furi/flipper.c +++ b/furi/flipper.c @@ -51,12 +51,17 @@ void flipper_init(void) { FURI_LOG_I(TAG, "Startup complete"); } +PLACE_IN_SECTION("MB_MEM2") static StaticTask_t idle_task_tcb; +PLACE_IN_SECTION("MB_MEM2") static StackType_t idle_task_stack[configIDLE_TASK_STACK_DEPTH]; +PLACE_IN_SECTION("MB_MEM2") static StaticTask_t timer_task_tcb; +PLACE_IN_SECTION("MB_MEM2") static StackType_t timer_task_stack[configTIMER_TASK_STACK_DEPTH]; + void vApplicationGetIdleTaskMemory( StaticTask_t** tcb_ptr, StackType_t** stack_ptr, uint32_t* stack_size) { - *tcb_ptr = memmgr_alloc_from_pool(sizeof(StaticTask_t)); - *stack_ptr = memmgr_alloc_from_pool(sizeof(StackType_t) * configIDLE_TASK_STACK_DEPTH); + *tcb_ptr = &idle_task_tcb; + *stack_ptr = idle_task_stack; *stack_size = configIDLE_TASK_STACK_DEPTH; } @@ -64,7 +69,7 @@ void vApplicationGetTimerTaskMemory( StaticTask_t** tcb_ptr, StackType_t** stack_ptr, uint32_t* stack_size) { - *tcb_ptr = memmgr_alloc_from_pool(sizeof(StaticTask_t)); - *stack_ptr = memmgr_alloc_from_pool(sizeof(StackType_t) * configTIMER_TASK_STACK_DEPTH); + *tcb_ptr = &timer_task_tcb; + *stack_ptr = timer_task_stack; *stack_size = configTIMER_TASK_STACK_DEPTH; } \ No newline at end of file From 50e852a25fc8b81e23be1c8ad308751fe29e6a16 Mon Sep 17 00:00:00 2001 From: SG Date: Tue, 9 Apr 2024 05:13:27 +0300 Subject: [PATCH 071/129] Furi: fix smal things in allocator --- furi/core/memmgr_heap.c | 87 ++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 53 deletions(-) diff --git a/furi/core/memmgr_heap.c b/furi/core/memmgr_heap.c index d6ca3f255..5afeb8f67 100644 --- a/furi/core/memmgr_heap.c +++ b/furi/core/memmgr_heap.c @@ -38,11 +38,17 @@ static inline size_t memmgr_get_heap_size(void) { return (size_t)&__heap_end__ - (size_t)&__heap_start__; } -// Initialize tracing storage on start -void memmgr_heap_init(void) { +// Initialize tracing storage +static void memmgr_heap_init(void) { MemmgrHeapThreadDict_init(memmgr_heap_thread_dict); } +__attribute__((constructor)) static void memmgr_init(void) { + size_t pool_size = (size_t)&__heap_end__ - (size_t)&__heap_start__; + tlsf = tlsf_create_with_pool((void*)&__heap_start__, pool_size, pool_size); + memmgr_heap_init(); +} + void memmgr_heap_enable_thread_trace(FuriThreadId thread_id) { memmgr_lock(); { @@ -97,7 +103,7 @@ static inline void memmgr_heap_trace_free(void* pointer) { size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id) { size_t leftovers = MEMMGR_HEAP_UNKNOWN; - vTaskSuspendAll(); + memmgr_lock(); { memmgr_heap_thread_trace_depth++; MemmgrHeapAllocDict_t* alloc_dict = @@ -112,7 +118,6 @@ size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id) { if(data->key != 0) { block_header_t* block = block_from_ptr((uint8_t*)data->key); if(!block_is_free(block)) { - // with tlsf we know the size of the block, so we don't need to store it on the dict leftovers += data->value; } } @@ -120,7 +125,7 @@ size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id) { } memmgr_heap_thread_trace_depth--; } - (void)xTaskResumeAll(); + memmgr_unlock(); return leftovers; } @@ -177,13 +182,6 @@ void* pvPortMalloc(size_t xSize) { memmgr_lock(); - // initialize tlsf, if not initialized - if(tlsf == NULL) { - size_t pool_size = (size_t)&__heap_end__ - (size_t)&__heap_start__; - tlsf = tlsf_create_with_pool((void*)&__heap_start__, pool_size, pool_size); - memmgr_heap_init(); - } - // allocate block void* data = tlsf_malloc(tlsf, xSize); if(data == NULL) { @@ -201,13 +199,13 @@ void* pvPortMalloc(size_t xSize) { heap_max_used = heap_used; } - // clear block content - memset(data, 0, xSize); + // trace allocation + memmgr_heap_trace_malloc(data, xSize); memmgr_unlock(); - // trace allocation - memmgr_heap_trace_malloc(data, xSize); + // clear block content + memset(data, 0, xSize); return data; } @@ -222,8 +220,10 @@ void vPortFree(void* pv) { if(pv != NULL) { memmgr_lock(); - // clear block content + // get block size size_t block_size = tlsf_block_size(pv); + + // clear block content memset(pv, 0, block_size); // update heap usage @@ -233,10 +233,10 @@ void vPortFree(void* pv) { // free tlsf_free(tlsf, pv); - memmgr_unlock(); - // trace free memmgr_heap_trace_free(pv); + + memmgr_unlock(); } } @@ -248,13 +248,6 @@ extern void* pvPortAllocAligned(size_t xSize, size_t xAlignment) { memmgr_lock(); - // initialize tlsf, if not initialized - if(tlsf == NULL) { - size_t pool_size = (size_t)&__heap_end__ - (size_t)&__heap_start__; - tlsf = tlsf_create_with_pool((void*)&__heap_start__, pool_size, pool_size); - memmgr_heap_init(); - } - // allocate block void* data = tlsf_memalign(tlsf, xAlignment, xSize); if(data == NULL) { @@ -272,19 +265,19 @@ extern void* pvPortAllocAligned(size_t xSize, size_t xAlignment) { heap_max_used = heap_used; } + // trace allocation + memmgr_heap_trace_malloc(data, xSize); + memmgr_unlock(); // clear block content memset(data, 0, xSize); - // trace allocation - memmgr_heap_trace_malloc(data, xSize); - return data; } extern void* pvPortRealloc(void* pv, size_t xSize) { - // size 0 is considered as free + // realloc(ptr, 0) is equivalent to free(ptr) if(xSize == 0) { vPortFree(pv); return NULL; @@ -295,7 +288,7 @@ extern void* pvPortRealloc(void* pv, size_t xSize) { return pvPortMalloc(xSize); } - // realloc things // + /* realloc things */ // memory management in ISR is not allowed if(FURI_IS_IRQ_MODE()) { @@ -304,28 +297,16 @@ extern void* pvPortRealloc(void* pv, size_t xSize) { memmgr_lock(); - // initialize tlsf, if not initialized - if(tlsf == NULL) { - size_t pool_size = (size_t)&__heap_end__ - (size_t)&__heap_start__; - tlsf = tlsf_create_with_pool((void*)&__heap_start__, pool_size, pool_size); - memmgr_heap_init(); - } - // trace old block as free - size_t old_size = 0; - if(pv != NULL) { - old_size = tlsf_block_size(pv); - memmgr_heap_trace_free(pv); - } + size_t old_size = tlsf_block_size(pv); + + // trace free + memmgr_heap_trace_free(pv); // reallocate block void* data = tlsf_realloc(tlsf, pv, xSize); if(data == NULL) { - if(xSize == 0) { - furi_crash("realloc(0)"); - } else { - furi_crash("out of memory"); - } + furi_crash("out of memory"); } // update heap usage @@ -335,16 +316,16 @@ extern void* pvPortRealloc(void* pv, size_t xSize) { heap_max_used = heap_used; } + // trace allocation + memmgr_heap_trace_malloc(data, xSize); + + memmgr_unlock(); + // clear remain block content, if the new size is bigger if(xSize > old_size) { memset((uint8_t*)data + old_size, 0, xSize - old_size); } - memmgr_unlock(); - - // trace allocation - memmgr_heap_trace_malloc(data, xSize); - return data; } From 33cfd6340a109a4f4892bba5491547a2eeb6f005 Mon Sep 17 00:00:00 2001 From: Zachary Weiss Date: Mon, 8 Apr 2024 22:14:46 -0400 Subject: [PATCH 072/129] More cleanup, pass testing --- .../nfc/plugins/supported_cards/charliecard.c | 177 ++++++++++-------- 1 file changed, 99 insertions(+), 78 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/charliecard.c b/applications/main/nfc/plugins/supported_cards/charliecard.c index 7313b49ee..81b9bb897 100644 --- a/applications/main/nfc/plugins/supported_cards/charliecard.c +++ b/applications/main/nfc/plugins/supported_cards/charliecard.c @@ -146,14 +146,14 @@ typedef struct { uint16_t gate; uint8_t g_flag; Money fare; - uint8_t f_flag; + uint16_t f_flag; } Trip; typedef struct { bool valid; - uint16_t type; - DateTime start; - DateTime end; + uint16_t pre; + uint16_t post; + DateTime date; } Pass; typedef struct { @@ -682,20 +682,6 @@ static DateTime return dt_delta(CHARLIE_EPOCH, ts_charlie * CHARLIE_TIME_DELTA_SECS); } -static uint16_t type_parse(const MfClassicData* data) { - /* Card type data stored in the first 10bits of block 1 of sectors 2 & 3 (Block 9 & Block 13, from card start) - To my knowledge, card type should never change, so we can check either - without caring which is active. For my sanity, I check both, and assert equal. - */ - - // bitshift (2bytes = 16 bits) by 6bits for just first 10bits - const uint16_t type1 = pos_to_num(data, 2, 1, 0, 2) >> 6; - const uint16_t type2 = pos_to_num(data, 3, 1, 0, 2) >> 6; - furi_assert(type1 == type2); - - return type1; -} - static DateTime end_validity_parse( const MfClassicData* data, uint8_t sector_num, @@ -716,36 +702,45 @@ static Pass // Sub-byte field divisions not drawn to scale, see code for exact bit offsets // // 0 1 2 3 4 5 - // +----.----.----+----.----.----+ - // | type | end | start | - // +----.----.----+----.----.----+ + // +----.----.----.----+----.----+ + // | uk1 | date | uk2 | + // +----.----.----.----+----.----+ // // "Blank" entries are as follows: // 0 1 2 3 4 5 - // +----.----.----.----.----.--+-. - // | 00 20 00 00 00 0| - // +----.----.----.----.----.--+-. - + // +----.----.----.----.----.----+ + // | 00 20 00 00 00 00 | + // +----.----.----.----.----.----+ + // + // if not blank, uk1 MSB seems to always be set to 1... + // the sole bit set to 1 on the blank entry seems to divide + // the uk1 and date fields, and is always set to 1 regardless + // same is true of type & end-validity split found in balance sector + // + // // check for empty, if so, return struct filled w/ 0s // (incl "valid" field: hence, "valid" is false-y) if(pos_to_num(data, sector_num, block_num, byte_num, 6) == 0x002000000000) { return (Pass){0}; } - DateTime start = date_parse(data, sector_num, block_num, byte_num + 1); - uint16_t type = pos_to_num(data, sector_num, block_num, byte_num, 2) >> 6; + // const DateTime start = date_parse(data, sector_num, block_num, byte_num + 1); - // these values make sense for end - DateTime end = end_validity_parse(data, sector_num, block_num, byte_num + 1); + const uint16_t pre = pos_to_num(data, sector_num, block_num, byte_num, 2) >> 6; + const uint16_t post = pos_to_num(data, sector_num, block_num, byte_num + 4, 2); + + // these values make sense for a date, but implied position of type + // before end validity, as seen in balance sector, doesn't seem + // to produce sensible values + const DateTime date = end_validity_parse(data, sector_num, block_num, byte_num + 1); // DateTime start = date_parse(data, sector_num, block_num, byte_num); // uint16_t type = 0; // pos_to_num(data, sector_num, block_num, byte_num + 3, 2) >> 6; - return (Pass){true, type, start, end}; + return (Pass){true, pre, post, date}; } -static Trip - trip_parse(const MfClassicData* data, uint8_t sector_num, uint8_t block_num, uint8_t byte_num) { +static Trip trip_parse(const MfClassicData* data, uint8_t sector, uint8_t block, uint8_t byte) { /* This function parses individual trips. Each trip packs 7 bytes, stored as follows: 0 1 2 3 4 5 6 @@ -757,19 +752,23 @@ static Trip Amount appears to contain some flag bits, however, it is unclear what precisely their function is. Gate ID ("loc") is only the first 13 bits of 0x3:0x5, the final three bits appear to be flags ("f"). - Least significant flag bit (ie "loc & 0x1") seems to indicate: - — When 0, fare (the amount by which balance is decremented) - — When 1, refill (the amount by which balance is incremented) + Least significant flag bit seems to indicate: + — When f & 1 == 0, fare (the amount by which balance is decremented) + — When f & 1 == 1, refill (the amount by which balance is incremented) + MSB (sign bit) of amt seems to serve the same role, just inverted, ie + — When amt & 0x8000 == 0x8000, fare + — When amt & 0x8000 == 0, refill - On monthly pass cards, MSB of amt will be set: 0x8000 (negative zero) - Seemingly randomly (irrespective of card type, last trip, etc) 0x0001 will be set on amt in addition to - whatever the regular fare is (a half cent more). I am uncertain what this flag indicates. + Remaining unknown bits: + — f & 0b100 + — f & 0b010 + — amt & 1; does not seem to correspond with card type, last trip, first trip, refill v. fare, etc */ - const DateTime date = date_parse(data, sector_num, block_num, byte_num); - const uint16_t gate = pos_to_num(data, sector_num, block_num, byte_num + 3, 2) >> 3; - const uint8_t g_flag = pos_to_num(data, sector_num, block_num, byte_num + 3, 2) & 0b111; - const Money fare = money_parse(data, sector_num, block_num, byte_num + 5); - const uint8_t f_flag = pos_to_num(data, sector_num, block_num, byte_num + 5, 2) & 0x8001; + const DateTime date = date_parse(data, sector, block, byte); + const uint16_t gate = pos_to_num(data, sector, block, byte + 3, 2) >> 3; + const uint8_t g_flag = pos_to_num(data, sector, block, byte + 3, 2) & 0b111; + const Money fare = money_parse(data, sector, block, byte + 5); + const uint16_t f_flag = pos_to_num(data, sector, block, byte + 5, 2) & 0x8001; return (Trip){date, gate, g_flag, fare, f_flag}; } @@ -782,12 +781,15 @@ static uint32_t mfg_sector_parse(const MfClassicData* data) { // // 0 1 2 3 4 5 6 7 8 9 A B C D E F // +----.----.----.----+----+----.----.----.----+----+----.----.----.----.----+----+ - // 0x000 | UID |LRC | 88 04 00 C8 |unkn| 00 20 00 00 00 |unkn| + // 0x000 | UID | rc | 88 04 00 C8 | uk | 00 20 00 00 00 | uk | // +----.----.----.----+----+----.----.----.----+----+----.----.----.----.----+----+ // 0x010 | 4E 0F 04 10 04 10 04 10 04 10 04 10 04 10 04 10 | // +----.----.----.----.----.----.----.----.----.----.----.----.----.----.----.----+ // 0x020 | ... 00 00 ... | // +----.----.----.----.----.----.----.----.----.----.----.----.----.----.----.----+ + // + // rc := "redundancy check" (lrc / bcc) + // uk := "unknown" size_t uid_len = 0; const uint8_t* uid = mf_classic_get_uid(data, &uid_len); @@ -800,13 +802,16 @@ static CounterSector counter_sector_parse(const MfClassicData* data) { // Trip/transaction counters (Sector 1) // // 0 1 2 3 4 5 6 7 8 9 A B C D E F - // +----.----.----.----.----.----.----.----.----.----.----.----.----.----.----+----+ + // +----.----.----.----.----.----.----.----.----.----.----.----.----.----.----.----+ // 0x040 | 04 10 23 45 66 77 ... 00 00 ... | - // +----.----+----+----.----.----.----.----.----.----.----.----.----.----.----+----+ - // 0x050 | uses1 |unkn| ... 00 00 ... | // +----.----+----+----.----.----.----.----.----.----.----.----.----.----.----.----+ - // 0x060 | uses2 |unkn| ... 00 00 ... | + // 0x050 | uses1 | uk | ... 00 00 ... | // +----.----+----+----.----.----.----.----.----.----.----.----.----.----.----.----+ + // 0x060 | uses2 | uk | ... 00 00 ... | + // +----.----+----+----.----.----.----.----.----.----.----.----.----.----.----.----+ + // + // uk := "unknown"; if nonzero, seems to only occupy the first 4 bits (ie, mask w/ 0xF0), + // with the remaining 4 zero // Card has two transaction sectors (2 & 3) containing balance data, with two // corresponding trip counters in 0x50:0x51 & 0x60:0x61 (sector 1, byte 0:1 of blocks 1 & 2). @@ -827,27 +832,30 @@ static CounterSector counter_sector_parse(const MfClassicData* data) { } static BalanceSector balance_sector_parse(const MfClassicData* data, uint8_t active_sector) { - // Balance / type / last transaction (Sector 2 or 3) + // Balance & misc card info (Sector 2 or 3) // // 0 1 2 3 4 5 6 7 8 9 A B C D E F // +----+----.----.----+----.----+----.----.----+----.----+----.----+----+----.----+ // 0x080 | 11 | date last | loc last| date issued | 65 00 | unknown | 00 | crc | 0x0C0 // +----+----.----.----+----+----+----+----+----+----.----+----.----+----+----.----+ - // 0x090 | type |end validity|unkn| balance | 00 | unknown | crc | 0x0D0 + // 0x090 | type |end validity| uk | balance | 00 | unknown | crc | 0x0D0 // +----.----.----.----+----+----.----+----+----.----.----.----.----.----+----.----+ // 0x0A0 | 20 ... 00 00 ... 04 | crc | 0x0E0 // +----.----.----.----.----.----.----.----.----.----.----.----.----.----+----.----+ // // "Active" balance sector alternates between 2 and 3 - // Last trip/transaction info in balance sector (date last & loc last) - // also included in transaction log, hence don't bother to read here + // Last trip/transaction info in balance sector ("date last" & "loc last") + // is also included in transaction log, hence don't bother to read here // - // Inactive balance sector represent the transaction N-1 balance data + // Inactive balance sector represent the transaction N-1 version // (where active sector represents data from transaction N). const DateTime issued = date_parse(data, active_sector, 0, 6); const DateTime end_validity = end_validity_parse(data, active_sector, 1, 1); - const uint16_t type = type_parse(data); + // Card type data stored in the first 10bits of block 1 + // (0x90 or 0xD0 depending on active sector) + // bitshift (2bytes = 16 bits) by 6bits for just first 10bits + const uint16_t type = pos_to_num(data, active_sector, 1, 0, 2) >> 6; const Money bal = money_parse(data, active_sector, 1, 5); return (BalanceSector){bal, type, issued, end_validity}; @@ -857,9 +865,9 @@ static Pass* passes_parse(const MfClassicData* data) { // Passes, speculative (Sectors 4 &/or 5) // // 0 1 2 3 4 5 6 7 8 9 A B C D E F - // +----.----.----.----.----.-+--.----+----.----.----.----.----.-+--.----+----.----+ - // 0x100 | pass0? |0 00 | pass1? |0 00 | crc | 0x140 - // +----.----.----.----.----.-+--.----+----.----.----.----.----.-+--.----+----.----+ + // +----.----.----.----.----.----+----+----.----.----.----.----.----+----+----.----+ + // 0x100 | pass0? | 00 | pass1? | 00 | crc | 0x140 + // +----.----.----.----.----.----+----+----.----.----.----.----.----+----+----.----+ // 0x110 | ... 00 00 ... | crc | 0x150 // +----.----.----.----.----.----.----.----.----.----.----.----.----.----+----.----+ // 0x120 | ... 00 ... 05 | crc | 0x160 @@ -867,6 +875,7 @@ static Pass* passes_parse(const MfClassicData* data) { // // WIP. Read in all speculative passes into array // 4 separate fields? active vs inactive sector for 2 passes? + // something else entirely? Pass* passes = malloc(sizeof(Pass) * CHARLIE_N_PASSES); @@ -1009,23 +1018,35 @@ void type_format_cat(FuriString* out, uint16_t type) { } void pass_format_cat(FuriString* out, Pass pass) { - furi_string_cat_printf(out, "\n-Type: "); - type_format_cat(out, pass.type); - furi_string_cat_printf(out, "\n-Start: "); - locale_format_dt_cat(out, &pass.start); - furi_string_cat_printf(out, "\n-End: "); - locale_format_dt_cat(out, &pass.end); + furi_string_cat_printf(out, "\n-Pre: %b", pass.pre); + // type_format_cat(out, pass.type); + furi_string_cat_printf(out, "\n-Post: %b", pass.post); + // locale_format_dt_cat(out, &pass.start); + furi_string_cat_printf(out, "\n-Date: "); + locale_format_dt_cat(out, &pass.date); } void passes_format_cat(FuriString* out, Pass* passes) { - if(is_debug()) { - furi_string_cat_printf(out, "\nPasses (DEBUG / WIP):"); - for(size_t i = 0; i < CHARLIE_N_PASSES; i++) { - if(passes[i].valid) { - furi_string_cat_printf(out, "\nPass %u", i + 1); - pass_format_cat(out, passes[i]); - furi_string_cat_printf(out, "\n"); - } + // only print passes if DEBUG on + if(!is_debug()) { + return; + } + + // only print if there is at least 1 valid pass to print + bool any_valid = false; + for(size_t i = 0; i < CHARLIE_N_PASSES; i++) { + any_valid |= passes[i].valid; + } + if(!any_valid) { + return; + } + + furi_string_cat_printf(out, "\nPasses (DEBUG / WIP):"); + for(size_t i = 0; i < CHARLIE_N_PASSES; i++) { + if(passes[i].valid) { + furi_string_cat_printf(out, "\nPass %u", i + 1); + pass_format_cat(out, passes[i]); + furi_string_cat_printf(out, "\n"); } } } @@ -1044,19 +1065,19 @@ void trip_format_cat(FuriString* out, Trip trip) { if(!!(trip.g_flag & 0x1) && (trip.fare.dollars == FARE_BUS.dollars) && (trip.fare.cents == FARE_BUS.cents)) { // if not a refill, and the fare amount is equal to bus fare (any better approach? flag bits for modality?) - // format for bus (gate ID on busses = posted bus #) - furi_string_cat_printf(out, "%sBus#%u", sep, trip.gate); + // format for bus — supposedly some correlation between gate ID & bus #, haven't investigated + furi_string_cat_printf(out, "%s#%u", sep, trip.gate); } else if(get_map_item(trip.gate, charliecard_fare_gate_ids, kNumFareGateIds, &sta)) { // station found in fare gate ID map, append station name furi_string_cat_str(out, sep); furi_string_cat_str(out, sta); } else { // no found station in fare gate ID map & not a bus, just print ID w/o add'l info - furi_string_cat_printf(out, "%s%u", sep, trip.gate); + furi_string_cat_printf(out, "%s#%u", sep, trip.gate); } // print flags for debugging purposes if(is_debug()) { - furi_string_cat_printf(out, "%s%u%s%u", sep, trip.g_flag, sep, trip.f_flag); + furi_string_cat_printf(out, "%s%x%s%x", sep, trip.g_flag, sep, trip.f_flag); } } @@ -1117,9 +1138,6 @@ static bool charliecard_parse(const NfcDevice* device, FuriString* parsed_data) furi_string_cat_printf(parsed_data, "\nType: "); type_format_cat(parsed_data, balance_sector.type); - passes_format_cat(parsed_data, passes); - free(passes); - furi_string_cat_printf(parsed_data, "\nTrip Count: %u", counter_sector.n_uses); furi_string_cat_printf(parsed_data, "\nIssued: "); @@ -1131,6 +1149,9 @@ static bool charliecard_parse(const NfcDevice* device, FuriString* parsed_data) // const DateTime last = date_parse(data, active_sector, 0, 1); // furi_string_cat_printf(parsed_data, "\nExpired: %s", expired(e_v, last) ? "Yes" : "No"); + passes_format_cat(parsed_data, passes); + free(passes); + trips_format_cat(parsed_data, trips); free(trips); From fe85c512f25d40872c7c8fcbf53130157a991bcd Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Tue, 9 Apr 2024 03:36:19 +0100 Subject: [PATCH 073/129] Archive: Fix .txt file type for /any paths --- applications/main/archive/helpers/archive_files.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/applications/main/archive/helpers/archive_files.c b/applications/main/archive/helpers/archive_files.c index 10b89c3b5..3406ceea9 100644 --- a/applications/main/archive/helpers/archive_files.c +++ b/applications/main/archive/helpers/archive_files.c @@ -36,7 +36,13 @@ void archive_set_file_type(ArchiveFile_t* file, const char* path, bool is_folder txt_path = archive_get_default_path(ArchiveTabBadKb); break; } - if(txt_path != NULL && furi_string_start_with_str(file->path, txt_path)) { + if(txt_path != NULL) { + size_t len = strlen(txt_path); + if(furi_string_size(file->path) < len) continue; + // Compare but ignore /ext or /any, continue if different (memcmp() != 0) + if(memcmp(furi_string_get_cstr(file->path) + 4, txt_path + 4, len - 4)) { + continue; + } file->type = i; return; } From 08a428039d4f7b8e0efbb2e429e7493a298152d3 Mon Sep 17 00:00:00 2001 From: Kuronons <110337784+Kuronons@users.noreply.github.com> Date: Tue, 9 Apr 2024 05:37:56 +0200 Subject: [PATCH 074/129] MNTM Pack - Adding "done" Yappy (PI) asset :P --- .../Momentum/Icons/Dolphin/DolphinDone_80x58.png | Bin 0 -> 2602 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/packs/Momentum/Icons/Dolphin/DolphinDone_80x58.png diff --git a/assets/packs/Momentum/Icons/Dolphin/DolphinDone_80x58.png b/assets/packs/Momentum/Icons/Dolphin/DolphinDone_80x58.png new file mode 100644 index 0000000000000000000000000000000000000000..81a0e29ba55b02e1281dff350820dcfeb1199727 GIT binary patch literal 2602 zcmb_e32+lt7*4AcIVvz6v?9o|AY$oe@6GPgfNj%222yKVprSI}?7k!`NjB_mNK18S zai*yCEDE(092Fh4)Zq}Ycu-Lpb&3~IundZhsN6@z0qs~{(lnrQ4P$0E`*!#H|Mz|W z`@g(AR#Z4Oec*_J7Kd%CuPe zuT)FR%rf^3QPzT1NzpvOS`iFGw8fG$r6MfJ^MHwXfKLtC(G%}(MG;l8qqA8z;ST2m zzdCK94oVglmdXp~$%2ATnT+IAh%iAAm=aPE41^4^!j8uCif|lzjH5^#V$QRpxv>e6 zGItS@ujv5ctOO<#1dZ4PE6Fh|Wy?k=f+BDd9xO)MM1~h>3Q2rWIGe6`#bSpuF&DhE zqkhv2i#Q&QMy*lWs_8zQ6a)b$D4e1&h`@}>kSSGQA!BS`1_v-?T@9P67D8efC65*{ z?I>j0DiaP9*8qmWE=!Q z5QIzvV#(wLu(WP>5`IxG!C(??m<0=H_U4_n)0@s`eI^ewSzAU*= zkpY*^Ncba9@+C$Ab#kv46-|az&xcApK~px_h6xfwVZ6+EF%L(`81E%X;3Zg&p~xIO zW(}C4*L83ZD_Kmk)5&uzOVBo+;7PKeK;UdtUcSTWWEd(pj}tB(DN`iLv5Jg&BvQaA zzz7&D6OB zVlMoB`TxYkTeG*r@P9X7{M`k63|z?gBe-$JhRGch-X!;25Q25m;g0&JGyQ9eC6#tL za!dW+9cy&yA0vYcB0bY5H5W#1`Rxo_eUE3^0}UM;yi)65-`3xJwx6|o!3_n{(-E{ZuRMuI$xyUf-yBK?bTCry095zmfDv#P6@Xb?yvYlyd<61hVZ%X+<@F8P%Z$JESTVvgp)Va5o(~gwQd!9IFmXEACr{8|B z_8B+F*@Y~bIehn*mbyi3+Ol6C9FW}_VZwL4-(EXzLv~)j)1dRCjGFo8nMVJu>2i5{ z?YxZ{Z2P$SQ;!ce3un|FxWdp=}LozG$8^>(^yl?Vbk@t#-Ght}2uUPNlnf z(EsG#KX%RPeyHo6>oPZ2)m86s?aex{ZUZ@}wyj}2@zvZxYrwGdy{{fSHKS?Slkb&< z+IK#BFl$x&F87VmU$oV0U*0zQmFgeKv_M)*>yACnPY*dbIMg7+SUz8S+H% z`poXq&x-4ZQ`Wm?=b$)_=SLzwKOQi&|7$H*@BQBWYW9 ze)IebXKu30KD2Yvhu-bw%E;mP;g@ei%8S}+?>RB5d6(_0!k!y{8h0A~e804(aosIT z!u?;{`08?f(A_D^@6Ycl8Fsq%lWuNy Date: Tue, 9 Apr 2024 16:01:55 +0900 Subject: [PATCH 075/129] Update radio stack to v1.19.0 (#3545) * Update radio stack to v1.19.0 * Ble: set max connection interal same as min, kinda speedups everything --- fbt_options.py | 2 +- lib/ble_profile/extra_profiles/hid_profile.c | 4 ++-- lib/stm32wb_copro | 2 +- targets/f7/ble_glue/ble_app.c | 4 ++-- targets/f7/ble_glue/extra_beacon.c | 3 ++- targets/f7/ble_glue/profiles/serial_profile.c | 4 ++-- 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/fbt_options.py b/fbt_options.py index 9e4d821a4..c4b841058 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -22,7 +22,7 @@ DIST_SUFFIX = "local" COPRO_OB_DATA = "scripts/ob.data" # Must match lib/stm32wb_copro version -COPRO_CUBE_VERSION = "1.17.3" +COPRO_CUBE_VERSION = "1.19.0" COPRO_CUBE_DIR = "lib/stm32wb_copro" diff --git a/lib/ble_profile/extra_profiles/hid_profile.c b/lib/ble_profile/extra_profiles/hid_profile.c index aaa66d960..7231fdb19 100644 --- a/lib/ble_profile/extra_profiles/hid_profile.c +++ b/lib/ble_profile/extra_profiles/hid_profile.c @@ -380,8 +380,8 @@ static GapConfig template_config = { .pairing_method = GapPairingPinCodeVerifyYesNo, .conn_param = { - .conn_int_min = 0x18, // 30 ms - .conn_int_max = 0x24, // 45 ms + .conn_int_min = 0x18, // AN5289: 4.7, we need at least 25ms + advertisement, which is 30 ms + .conn_int_max = 0x18, // 30 ms .slave_latency = 0, .supervisor_timeout = 0, }, diff --git a/lib/stm32wb_copro b/lib/stm32wb_copro index d8a6f1feb..64a060d91 160000 --- a/lib/stm32wb_copro +++ b/lib/stm32wb_copro @@ -1 +1 @@ -Subproject commit d8a6f1feb0ebb6798c44162c6ae5ea743f90f3df +Subproject commit 64a060d91f5cbf25d765cf23231876add006bcf4 diff --git a/targets/f7/ble_glue/ble_app.c b/targets/f7/ble_glue/ble_app.c index 1f392529d..fd4e64c09 100644 --- a/targets/f7/ble_glue/ble_app.c +++ b/targets/f7/ble_glue/ble_app.c @@ -54,8 +54,8 @@ static const SHCI_C2_Ble_Init_Cmd_Packet_t ble_init_cmd_packet = { .PrWriteListSize = CFG_BLE_PREPARE_WRITE_LIST_SIZE, .MblockCount = CFG_BLE_MBLOCK_COUNT, .AttMtu = CFG_BLE_MAX_ATT_MTU, - .SlaveSca = CFG_BLE_SLAVE_SCA, - .MasterSca = CFG_BLE_MASTER_SCA, + .PeripheralSca = CFG_BLE_SLAVE_SCA, + .CentralSca = CFG_BLE_MASTER_SCA, .LsSource = CFG_BLE_LSE_SOURCE, .MaxConnEventLength = CFG_BLE_MAX_CONN_EVENT_LENGTH, .HsStartupTime = CFG_BLE_HSE_STARTUP_TIME, diff --git a/targets/f7/ble_glue/extra_beacon.c b/targets/f7/ble_glue/extra_beacon.c index 2ef3e056f..60338cb76 100644 --- a/targets/f7/ble_glue/extra_beacon.c +++ b/targets/f7/ble_glue/extra_beacon.c @@ -9,7 +9,8 @@ #define GAP_MS_TO_SCAN_INTERVAL(x) ((uint16_t)((x) / 0.625)) // Also used as an indicator of whether the beacon had ever been configured -#define GAP_MIN_ADV_INTERVAL_MS (20) +// AN5289: 4.7, we need at least 25ms + advertisement, which is 30 ms +#define GAP_MIN_ADV_INTERVAL_MS (30u) typedef struct { GapExtraBeaconConfig last_config; diff --git a/targets/f7/ble_glue/profiles/serial_profile.c b/targets/f7/ble_glue/profiles/serial_profile.c index a3949abfc..165b81330 100644 --- a/targets/f7/ble_glue/profiles/serial_profile.c +++ b/targets/f7/ble_glue/profiles/serial_profile.c @@ -46,8 +46,8 @@ static GapConfig serial_template_config = { .bonding_mode = true, .pairing_method = GapPairingPinCodeShow, .conn_param = { - .conn_int_min = 0x18, // 30 ms - .conn_int_max = 0x24, // 45 ms + .conn_int_min = 0x18, // AN5289: 4.7, we need at least 25ms + advertisement, which is 30 ms + .conn_int_max = 0x18, // 30 ms .slave_latency = 0, .supervisor_timeout = 0, }}; From 58da27fa912422d0a903c24f25549f15f6c20a3c Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Tue, 9 Apr 2024 12:06:37 +0300 Subject: [PATCH 076/129] JS Documentation (#3535) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial JS documentation * Spelling fix Co-authored-by: あく --- documentation/doxygen/index.dox | 1 + documentation/doxygen/js.dox | 18 ++++ documentation/js/js_badusb.md | 144 ++++++++++++++++++++++++++++ documentation/js/js_builtin.md | 56 +++++++++++ documentation/js/js_data_types.md | 13 +++ documentation/js/js_dialog.md | 49 ++++++++++ documentation/js/js_notification.md | 36 +++++++ documentation/js/js_serial.md | 107 +++++++++++++++++++++ 8 files changed, 424 insertions(+) create mode 100644 documentation/doxygen/js.dox create mode 100644 documentation/js/js_badusb.md create mode 100644 documentation/js/js_builtin.md create mode 100644 documentation/js/js_data_types.md create mode 100644 documentation/js/js_dialog.md create mode 100644 documentation/js/js_notification.md create mode 100644 documentation/js/js_serial.md diff --git a/documentation/doxygen/index.dox b/documentation/doxygen/index.dox index 6fb4d5a71..78055caad 100644 --- a/documentation/doxygen/index.dox +++ b/documentation/doxygen/index.dox @@ -15,6 +15,7 @@ The documentation is divided into several sections, with all of them accessible - @ref dev_tools - Hardware and software tools for all kinds of programming - @ref expansion - Additional modules to expand Flipper's consciousness - @ref misc - Various useful pieces of information +- @ref js - JS-based scripting engine documentation Aside from the manually-written documentation files, there's also a few automatically-generated ones at the bottom of the sidebar: diff --git a/documentation/doxygen/js.dox b/documentation/doxygen/js.dox new file mode 100644 index 000000000..1c81a0835 --- /dev/null +++ b/documentation/doxygen/js.dox @@ -0,0 +1,18 @@ +/** +@page js JavaScript + +This page contains some information on the Flipper Zero scripting engine, which is based on a modified mJS library + +- [Brief mJS description](https://github.com/cesanta/mjs/blob/master/README.md) +- @subpage js_data_types +- @subpage js_builtin + +JavaScript Modules +JS modules use the Flipper app plugin system. Each module is compiled into a .fal library file and is located on a microSD card. Here is a list of implemented modules: + +- @subpage js_badusb - BadUSB module +- @subpage js_serial - Serial module +- @subpage js_dialog - Dialog module +- @subpage js_notification - Notifications module + +*/ diff --git a/documentation/js/js_badusb.md b/documentation/js/js_badusb.md new file mode 100644 index 000000000..28372e56a --- /dev/null +++ b/documentation/js/js_badusb.md @@ -0,0 +1,144 @@ +# js_badusb {#js_badusb} + +# BadUSB module +```js +let badusb = require("badusb"); +``` +# Methods +## setup +Start USB HID with optional parameters. Should be called before all other methods. + +### Parameters +Configuration object (optional): +- vid, pid (number): VID and PID values, both are mandatory +- mfr_name (string): Manufacturer name (32 ASCII characters max), optional +- prod_name (string): Product name (32 ASCII characters max), optional + +### Examples: +```js +// Start USB HID with default parameters +badusb.setup(); +// Start USB HID with custom vid:pid = AAAA:BBBB, manufacturer and product strings not defined +badusb.setup({ vid: 0xAAAA, pid: 0xBBBB }); +// Start USB HID with custom vid:pid = AAAA:BBBB, manufacturer string = "Flipper Devices", product string = "Flipper Zero" +badusb.setup({ vid: 0xAAAA, pid: 0xBBBB, mfr_name: "Flipper Devices", prod_name: "Flipper Zero" }); +``` + +## isConnected +Returns USB connection state. + +### Example: +```js +if (badusb.isConnected()) { + // Do something +} else { + // Show an error +} +``` + +## press +Press and release a key. + +### Parameters +Key or modifier name, key code. + +See a list of key names below. + +### Examples: +```js +badusb.press("a"); // Press "a" key +badusb.press("A"); // SHIFT + "a" +badusb.press("CTRL", "a"); // CTRL + "a" +badusb.press("CTRL", "SHIFT", "ESC"); // CTRL + SHIFT + ESC combo +badusb.press(98); // Press key with HID code (dec) 98 (Numpad 0 / Insert) +badusb.press(0x47); // Press key with HID code (hex) 0x47 (Scroll lock) +``` + +## hold +Hold a key. Up to 5 keys (excluding modifiers) can be held simultaneously. + +### Parameters +Same as `press` + +### Examples: +```js +badusb.hold("a"); // Press and hold "a" key +badusb.hold("CTRL", "v"); // Press and hold CTRL + "v" combo +``` + +## release +Release a previously hold key. + +### Parameters +Same as `press` + +Release all keys if called without parameters + +### Examples: +```js +badusb.release(); // Release all keys +badusb.release("a"); // Release "a" key +``` + +## print +Print a string. + +### Parameters +- A string to print +- (optional) delay between key presses + +### Examples: +```js +badusb.print("Hello, world!"); // print "Hello, world!" +badusb.print("Hello, world!", 100); // Add 100ms delay between key presses +``` + +## println +Same as `print` but ended with "ENTER" press. + +### Parameters +- A string to print +- (optional) delay between key presses + +### Examples: +```js +badusb.println("Hello, world!"); // print "Hello, world!" and press "ENTER" +``` + +# Key names list + +## Modifier keys + +| Name | +| ------------- | +| CTRL | +| SHIFT | +| ALT | +| GUI | + +## Special keys + +| Name | Notes | +| ------------------ | ---------------- | +| DOWN | Down arrow | +| LEFT | Left arrow | +| RIGHT | Right arrow | +| UP | Up arrow | +| ENTER | | +| DELETE | | +| BACKSPACE | | +| END | | +| HOME | | +| ESC | | +| INSERT | | +| PAGEUP | | +| PAGEDOWN | | +| CAPSLOCK | | +| NUMLOCK | | +| SCROLLLOCK | | +| PRINTSCREEN | | +| PAUSE | Pause/Break key | +| SPACE | | +| TAB | | +| MENU | Context menu key | +| Fx | F1-F24 keys | diff --git a/documentation/js/js_builtin.md b/documentation/js/js_builtin.md new file mode 100644 index 000000000..3d113807b --- /dev/null +++ b/documentation/js/js_builtin.md @@ -0,0 +1,56 @@ +# Built-in methods {#js_builtin} + +## require +Load a module plugin. + +### Parameters +- Module name + +### Examples: +```js +let serial = require("serial"); // Load "serial" module +``` + +## delay +### Parameters +- Delay value in ms + +### Examples: +```js +delay(500); // Delay for 500ms +``` +## print +Print a message on a screen console. + +### Parameters +The following argument types are supported: +- String +- Number +- Bool +- undefined + +### Examples: +```js +print("string1", "string2", 123); +``` + +## console.log +## console.warn +## console.error +## console.debug +Same as `print`, but output to serial console only, with corresponding log level. + +## to_string +Convert a number to string. + +### Examples: +```js +to_string(123) +``` +## to_hex_string +Convert a number to string(hex format). + +### Examples: +```js +to_hex_string(0xFF) +``` diff --git a/documentation/js/js_data_types.md b/documentation/js/js_data_types.md new file mode 100644 index 000000000..de1c896dc --- /dev/null +++ b/documentation/js/js_data_types.md @@ -0,0 +1,13 @@ +# Data types {#js_data_types} + +Here is a list of common data types used by mJS. +- string - sequence of single byte characters, no UTF8 support +- number +- boolean +- foreign - C function or data pointer +- undefined +- null +- object - a data structure with named fields +- array - special type of object, all items have indexes and equal types +- ArrayBuffer - raw data buffer +- DataView - provides interface for accessing ArrayBuffer contents diff --git a/documentation/js/js_dialog.md b/documentation/js/js_dialog.md new file mode 100644 index 000000000..5804b075e --- /dev/null +++ b/documentation/js/js_dialog.md @@ -0,0 +1,49 @@ +# js_dialog {#js_dialog} + +# Dialog module +```js +let dialog = require("dialog"); +``` +# Methods + +## message +Show a simple message dialog with header, text and "OK" button. + +### Parameters +- Dialog header text +- Dialog text + +### Retuns +true if central button was pressed, false if the dialog was closed by back key press + +### Examples: +```js +dialog.message("Dialog demo", "Press OK to start"); +``` + +## custom +More complex dialog with configurable buttons + +### Parameters +Configuration object with the following fileds: +- header: Dialog header text +- text: Dialog text +- button_left: (optional) left button name +- button_right: (optional) right button name +- button_center: (optional) central button name + +### Retuns +Name of pressed button or empty string if the dialog was closed by back key press + +### Examples: +```js +let dialog_params = ({ + header: "Dialog header", + text: "Dialog text", + button_left: "Left", + button_right: "Right", + button_center: "OK" +}); + +dialog.custom(dialog_params); +``` diff --git a/documentation/js/js_notification.md b/documentation/js/js_notification.md new file mode 100644 index 000000000..100da4414 --- /dev/null +++ b/documentation/js/js_notification.md @@ -0,0 +1,36 @@ +# js_notification {#js_notification} + +# Notification module +```js +let notify = require("notification"); +``` +# Methods + +## success +"Success" flipper notification message + +### Examples: +```js +notify.success(); +``` + +## error +"Error" flipper notification message + +### Examples: +```js +notify.error(); +``` + +## blink +Blink notification LED + +### Parameters +- Blink color (blue/red/green/yellow/cyan/magenta) +- Blink type (short/long) + +### Examples: +```js +notify.blink("red", "short"); // Short blink of red LED +notify.blink("green", "short"); // Long blink of green LED +``` \ No newline at end of file diff --git a/documentation/js/js_serial.md b/documentation/js/js_serial.md new file mode 100644 index 000000000..cd9993a18 --- /dev/null +++ b/documentation/js/js_serial.md @@ -0,0 +1,107 @@ +# js_serial {#js_serial} + +# Serial module +```js +let serial = require("serial"); +``` +# Methods + +## setup +Configure serial port. Should be called before all other methods. + +### Parameters +- Serial port name (usart, lpuart) +- Baudrate + +### Examples: +```js +// Configure LPUART port with baudrate = 115200 +serial.setup("lpuart", 115200); +``` + +## write +Write data to serial port + +### Parameters +One or more arguments of the following types: +- A string +- Single number, each number is interpreted as a byte +- Array of numbers, each number is interpreted as a byte +- ArrayBuffer or DataView + +### Examples: +```js +serial.write(0x0a); // Write a single byte 0x0A +serial.write("Hello, world!"); // Write a string +serial.write("Hello, world!", [0x0d, 0x0a]); // Write a string followed by two bytes +``` + +## read +Read a fixed number of characters from serial port. + +### Parameters +- Number of bytes to read +- (optional) Timeout value in ms + +### Returns +A sting of received characters or undefined if nothing was received before timeout. + +### Examples: +```js +serial.read(1); // Read a single byte, without timeout +serial.read(10, 5000); // Read 10 bytes, with 5s timeout +``` + +## readln +Read from serial port untill line break character + +### Parameters +(optional) Timeout value in ms + +### Returns +A sting of received characters or undefined if nothing was received before timeout. + +### Examples: +```js +serial.readln(); // Read without timeout +serial.readln(5000); // Read with 5s timeout +``` + +## readBytes +Read from serial port untill line break character + +### Parameters +- Number of bytes to read +- (optional) Timeout value in ms + +### Returns +ArrayBuffer with received data or undefined if nothing was received before timeout. + +### Examples: +```js +serial.readBytes(4); // Read 4 bytes, without timeout + +// Read one byte from receive buffer with zero timeout, returns UNDEFINED if Rx bufer is empty +serial.readBytes(1, 0); +``` + +## expect +Search for a string pattern in received data stream + +### Parameters +- Single argument or array of the following types: + - A string + - Array of numbers, each number is interpreted as a byte +- (optional) Timeout value in ms + +### Returns +Index of matched pattern in input patterns list, undefined if nothing was found. + +### Examples: +```js +// Wait for root shell prompt with 1s timeout, returns 0 if it was received before timeout, undefined if not +serial.expect("# ", 1000); + +// Infinitely wait for one of two strings, should return 0 if the first string got matched, 1 if the second one +serial.expect([": not found", "Usage: "]); +``` \ No newline at end of file From 1669765252dd913bf50ec478b37d0f5bd69f6d72 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Tue, 9 Apr 2024 11:00:20 +0100 Subject: [PATCH 077/129] Fix BLE Spam #85 --- applications/external | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/external b/applications/external index 07710ce2b..b069a9c43 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit 07710ce2b0ed38387d62e65d381309eb34270fc2 +Subproject commit b069a9c43cd13b62249982f9a1d8113a1ab6e858 From 5bca6d2e842625c234a114d0a8db2fc7a9c178aa Mon Sep 17 00:00:00 2001 From: SG Date: Tue, 9 Apr 2024 15:23:46 +0300 Subject: [PATCH 078/129] Furi: remove aligned_free. Use free instead. --- furi/core/memmgr.c | 4 ---- furi/core/memmgr.h | 11 ++--------- lib/flipper_application/elf/elf_file.c | 6 +++--- targets/f18/api_symbols.csv | 3 +-- targets/f7/api_symbols.csv | 3 +-- 5 files changed, 7 insertions(+), 20 deletions(-) diff --git a/furi/core/memmgr.c b/furi/core/memmgr.c index 5785bff6e..bb1040343 100644 --- a/furi/core/memmgr.c +++ b/furi/core/memmgr.c @@ -42,10 +42,6 @@ void* aligned_malloc(size_t size, size_t alignment) { return pvPortAllocAligned(size, alignment); } -void aligned_free(void* p) { - vPortFree(p); -} - size_t memmgr_get_free_heap(void) { return xPortGetFreeHeapSize(); } diff --git a/furi/core/memmgr.h b/furi/core/memmgr.h index 7a3f7b8f3..d38633118 100644 --- a/furi/core/memmgr.h +++ b/furi/core/memmgr.h @@ -36,20 +36,13 @@ size_t memmgr_get_total_heap(void); size_t memmgr_get_minimum_free_heap(void); /** - * An aligned version of malloc, used when you need to get the aligned space on the heap - * Freeing the received address is performed ONLY through the aligned_free function + * An aligned version of malloc, used when you need to get the aligned space on the heap. * @param size * @param alignment * @return void* */ void* aligned_malloc(size_t size, size_t alignment); -/** - * Freed space obtained through the aligned_malloc function - * @param p pointer to result of aligned_malloc - */ -void aligned_free(void* p); - /** * @brief Allocate memory from the auxiliary memory pool. That memory can't be freed. * @@ -59,7 +52,7 @@ void aligned_free(void* p); void* memmgr_aux_pool_alloc(size_t size); /** - * @brief Get the auxiliary poll free memory size + * @brief Get the auxiliary pool free memory size * * @return size_t */ diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 654316866..4d36f3460 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -713,7 +713,7 @@ static bool elf_relocate_fast(ELFFile* elf, ELFSection* s) { } } - aligned_free(s->fast_rel->data); + free(s->fast_rel->data); free(s->fast_rel); s->fast_rel = NULL; @@ -780,10 +780,10 @@ void elf_file_free(ELFFile* elf) { ELFSectionDict_next(it)) { const ELFSectionDict_itref_t* itref = ELFSectionDict_cref(it); if(itref->value.data) { - aligned_free(itref->value.data); + free(itref->value.data); } if(itref->value.fast_rel) { - aligned_free(itref->value.fast_rel->data); + free(itref->value.fast_rel->data); free(itref->value.fast_rel); } free((void*)itref->key); diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index d1beda897..ea4d997ac 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,61.1,, +Version,+,62.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -515,7 +515,6 @@ Function,-,acoshf,float,float Function,-,acoshl,long double,long double Function,-,acosl,long double,long double Function,-,aligned_alloc,void*,"size_t, size_t" -Function,+,aligned_free,void,void* Function,+,aligned_malloc,void*,"size_t, size_t" Function,-,arc4random,__uint32_t, Function,-,arc4random_buf,void,"void*, size_t" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 291e57b27..cf1eb642d 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,61.1,, +Version,+,62.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -584,7 +584,6 @@ Function,-,acoshf,float,float Function,-,acoshl,long double,long double Function,-,acosl,long double,long double Function,-,aligned_alloc,void*,"size_t, size_t" -Function,+,aligned_free,void,void* Function,+,aligned_malloc,void*,"size_t, size_t" Function,-,arc4random,__uint32_t, Function,-,arc4random_buf,void,"void*, size_t" From e3ca293eee588c155eac7b389ab7f7e2768d43e0 Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+Willy-JL@users.noreply.github.com> Date: Tue, 9 Apr 2024 14:42:03 +0100 Subject: [PATCH 079/129] Desktop/Loader: Unload animations before loading FAPs (#3573) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Desktop: Unload animations before FAP is loaded * Loader: Add API to start detached (returns instantly, queues event) * Desktop: Fix early animation unload deadlocks * Loader: remove redundant event * Bump api symbols Co-authored-by: あく Co-authored-by: SG --- applications/services/desktop/desktop.c | 6 ++- .../desktop/scenes/desktop_scene_main.c | 17 +++--- applications/services/loader/loader.c | 53 ++++++++++++++----- applications/services/loader/loader.h | 17 ++++-- applications/services/loader/loader_i.h | 1 + targets/f18/api_symbols.csv | 3 +- targets/f7/api_symbols.csv | 3 +- 7 files changed, 69 insertions(+), 31 deletions(-) diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 49aa04e35..c5a334a45 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -32,10 +32,12 @@ static void desktop_loader_callback(const void* message, void* context) { Desktop* desktop = context; const LoaderEvent* event = message; - if(event->type == LoaderEventTypeApplicationStarted) { + if(event->type == LoaderEventTypeApplicationBeforeLoad) { view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalBeforeAppStarted); furi_check(furi_semaphore_acquire(desktop->animation_semaphore, 3000) == FuriStatusOk); - } else if(event->type == LoaderEventTypeApplicationStopped) { + } else if( + event->type == LoaderEventTypeApplicationLoadFailed || + event->type == LoaderEventTypeApplicationStopped) { view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalAfterAppFinished); } } diff --git a/applications/services/desktop/scenes/desktop_scene_main.c b/applications/services/desktop/scenes/desktop_scene_main.c index a659ff4e3..41b1c3e75 100644 --- a/applications/services/desktop/scenes/desktop_scene_main.c +++ b/applications/services/desktop/scenes/desktop_scene_main.c @@ -62,23 +62,18 @@ static void #endif static void desktop_scene_main_open_app_or_profile(Desktop* desktop, FavoriteApp* application) { - bool load_ok = false; if(strlen(application->name_or_path) > 0) { - if(loader_start(desktop->loader, application->name_or_path, NULL, NULL) == - LoaderStatusOk) { - load_ok = true; - } - } - if(!load_ok) { - loader_start(desktop->loader, "Passport", NULL, NULL); + loader_start_detached_with_gui_error(desktop->loader, application->name_or_path, NULL); + } else { + loader_start_detached_with_gui_error(desktop->loader, "Passport", NULL); } } static void desktop_scene_main_start_favorite(Desktop* desktop, FavoriteApp* application) { if(strlen(application->name_or_path) > 0) { - loader_start_with_gui_error(desktop->loader, application->name_or_path, NULL); + loader_start_detached_with_gui_error(desktop->loader, application->name_or_path, NULL); } else { - loader_start(desktop->loader, LOADER_APPLICATIONS_NAME, NULL, NULL); + loader_start_detached_with_gui_error(desktop->loader, LOADER_APPLICATIONS_NAME, NULL); } } @@ -141,7 +136,7 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { break; case DesktopMainEventOpenPowerOff: { - loader_start(desktop->loader, "Power", "off", NULL); + loader_start_detached_with_gui_error(desktop->loader, "Power", "off"); consumed = true; break; } diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 5daf99d1d..1527807cc 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -47,13 +47,8 @@ LoaderStatus return result.value; } -LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const char* args) { - furi_check(loader); - furi_check(name); - - FuriString* error_message = furi_string_alloc(); - LoaderStatus status = loader_start(loader, name, args, error_message); - +static void + loader_show_gui_error(LoaderStatus status, const char* name, FuriString* error_message) { if(status == LoaderStatusErrorUnknownApp && loader_find_external_application_by_name(name) != NULL) { // Special case for external apps @@ -86,11 +81,31 @@ LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const dialog_message_free(message); furi_record_close(RECORD_DIALOGS); } +} +LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const char* args) { + furi_check(loader); + furi_check(name); + + FuriString* error_message = furi_string_alloc(); + LoaderStatus status = loader_start(loader, name, args, error_message); + loader_show_gui_error(status, name, error_message); furi_string_free(error_message); return status; } +void loader_start_detached_with_gui_error(Loader* loader, const char* name, const char* args) { + furi_check(loader); + furi_check(name); + + LoaderMessage message = { + .type = LoaderMessageTypeStartByNameDetachedWithGuiError, + .start.name = name ? strdup(name) : NULL, + .start.args = args ? strdup(args) : NULL, + }; + furi_message_queue_put(loader->queue, &message, FuriWaitForever); +} + bool loader_lock(Loader* loader) { furi_check(loader); @@ -166,11 +181,7 @@ static void loader_thread_state_callback(FuriThreadState thread_state, void* con Loader* loader = context; - if(thread_state == FuriThreadStateRunning) { - LoaderEvent event; - event.type = LoaderEventTypeApplicationStarted; - furi_pubsub_publish(loader->pubsub, &event); - } else if(thread_state == FuriThreadStateStopped) { + if(thread_state == FuriThreadStateStopped) { LoaderMessage message; message.type = LoaderMessageTypeAppClosed; furi_message_queue_put(loader->queue, &message, FuriWaitForever); @@ -255,6 +266,9 @@ static void loader_start_internal_app( const FlipperInternalApplication* app, const char* args) { FURI_LOG_I(TAG, "Starting %s", app->name); + LoaderEvent event; + event.type = LoaderEventTypeApplicationBeforeLoad; + furi_pubsub_publish(loader->pubsub, &event); // store args furi_assert(loader->app.args == NULL); @@ -309,6 +323,9 @@ static LoaderStatus loader_start_external_app( const char* args, FuriString* error_message) { LoaderStatus status = loader_make_success_status(error_message); + LoaderEvent event; + event.type = LoaderEventTypeApplicationBeforeLoad; + furi_pubsub_publish(loader->pubsub, &event); do { loader->app.fap = flipper_application_alloc(storage, firmware_api_interface); @@ -356,6 +373,8 @@ static LoaderStatus loader_start_external_app( if(status != LoaderStatusOk) { flipper_application_free(loader->app.fap); loader->app.fap = NULL; + event.type = LoaderEventTypeApplicationLoadFailed; + furi_pubsub_publish(loader->pubsub, &event); } return status; @@ -528,6 +547,16 @@ int32_t loader_srv(void* p) { loader, message.start.name, message.start.args, message.start.error_message); api_lock_unlock(message.api_lock); break; + case LoaderMessageTypeStartByNameDetachedWithGuiError: { + FuriString* error_message = furi_string_alloc(); + LoaderStatus status = loader_do_start_by_name( + loader, message.start.name, message.start.args, error_message); + loader_show_gui_error(status, message.start.name, error_message); + if(message.start.name) free((void*)message.start.name); + if(message.start.args) free((void*)message.start.args); + furi_string_free(error_message); + break; + } case LoaderMessageTypeShowMenu: loader_do_menu_show(loader); break; diff --git a/applications/services/loader/loader.h b/applications/services/loader/loader.h index cca65628f..ae914117d 100644 --- a/applications/services/loader/loader.h +++ b/applications/services/loader/loader.h @@ -18,7 +18,8 @@ typedef enum { } LoaderStatus; typedef enum { - LoaderEventTypeApplicationStarted, + LoaderEventTypeApplicationBeforeLoad, + LoaderEventTypeApplicationLoadFailed, LoaderEventTypeApplicationStopped } LoaderEventType; @@ -32,7 +33,7 @@ typedef struct { * @param[in] name application name or id * @param[in] args application arguments * @param[out] error_message detailed error message, can be NULL - * @return LoaderStatus + * @return LoaderStatus */ LoaderStatus loader_start(Loader* instance, const char* name, const char* args, FuriString* error_message); @@ -42,11 +43,19 @@ LoaderStatus * @param[in] instance loader instance * @param[in] name application name or id * @param[in] args application arguments - * @return LoaderStatus + * @return LoaderStatus */ LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const char* args); -/** +/** + * @brief Start application detached with GUI error message + * @param[in] instance loader instance + * @param[in] name application name or id + * @param[in] args application arguments + */ +void loader_start_detached_with_gui_error(Loader* loader, const char* name, const char* args); + +/** * @brief Lock application start * @param[in] instance loader instance * @return true on success diff --git a/applications/services/loader/loader_i.h b/applications/services/loader/loader_i.h index 688b8fb66..daa5484d3 100644 --- a/applications/services/loader/loader_i.h +++ b/applications/services/loader/loader_i.h @@ -30,6 +30,7 @@ typedef enum { LoaderMessageTypeLock, LoaderMessageTypeUnlock, LoaderMessageTypeIsLocked, + LoaderMessageTypeStartByNameDetachedWithGuiError, } LoaderMessageType; typedef struct { diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 72116ccfd..7944bc58f 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.5,, +Version,+,60.6,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1748,6 +1748,7 @@ Function,+,loader_is_locked,_Bool,Loader* Function,+,loader_lock,_Bool,Loader* Function,+,loader_show_menu,void,Loader* Function,+,loader_start,LoaderStatus,"Loader*, const char*, const char*, FuriString*" +Function,+,loader_start_detached_with_gui_error,void,"Loader*, const char*, const char*" Function,+,loader_start_with_gui_error,LoaderStatus,"Loader*, const char*, const char*" Function,+,loader_unlock,void,Loader* Function,+,loading_alloc,Loading*, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 4ddff921b..8fd9f4c9b 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.5,, +Version,+,60.6,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -2139,6 +2139,7 @@ Function,+,loader_is_locked,_Bool,Loader* Function,+,loader_lock,_Bool,Loader* Function,+,loader_show_menu,void,Loader* Function,+,loader_start,LoaderStatus,"Loader*, const char*, const char*, FuriString*" +Function,+,loader_start_detached_with_gui_error,void,"Loader*, const char*, const char*" Function,+,loader_start_with_gui_error,LoaderStatus,"Loader*, const char*, const char*" Function,+,loader_unlock,void,Loader* Function,+,loading_alloc,Loading*, From 0d3a717762764ecb394b2e87797fb006e9d07929 Mon Sep 17 00:00:00 2001 From: SG Date: Tue, 9 Apr 2024 17:08:12 +0300 Subject: [PATCH 080/129] aligned_malloc -> aligned_alloc --- furi/core/memmgr.c | 2 +- furi/core/memmgr.h | 8 -------- lib/flipper_application/elf/elf_file.c | 2 +- targets/f18/api_symbols.csv | 3 +-- targets/f7/api_symbols.csv | 3 +-- 5 files changed, 4 insertions(+), 14 deletions(-) diff --git a/furi/core/memmgr.c b/furi/core/memmgr.c index bb1040343..6c8937850 100644 --- a/furi/core/memmgr.c +++ b/furi/core/memmgr.c @@ -38,7 +38,7 @@ char* strdup(const char* s) { return y; } -void* aligned_malloc(size_t size, size_t alignment) { +void* aligned_alloc(size_t size, size_t alignment) { return pvPortAllocAligned(size, alignment); } diff --git a/furi/core/memmgr.h b/furi/core/memmgr.h index d38633118..796a1f537 100644 --- a/furi/core/memmgr.h +++ b/furi/core/memmgr.h @@ -35,14 +35,6 @@ size_t memmgr_get_total_heap(void); */ size_t memmgr_get_minimum_free_heap(void); -/** - * An aligned version of malloc, used when you need to get the aligned space on the heap. - * @param size - * @param alignment - * @return void* - */ -void* aligned_malloc(size_t size, size_t alignment); - /** * @brief Allocate memory from the auxiliary memory pool. That memory can't be freed. * diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 4d36f3460..80d86b637 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -461,7 +461,7 @@ static bool elf_load_section_data(ELFFile* elf, ELFSection* section, Elf32_Shdr* return true; } - section->data = aligned_malloc(section_header->sh_size, section_header->sh_addralign); + section->data = aligned_alloc(section_header->sh_size, section_header->sh_addralign); section->size = section_header->sh_size; if(section_header->sh_type == SHT_NOBITS) { diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index ea4d997ac..195a36702 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -514,8 +514,7 @@ Function,-,acosh,double,double Function,-,acoshf,float,float Function,-,acoshl,long double,long double Function,-,acosl,long double,long double -Function,-,aligned_alloc,void*,"size_t, size_t" -Function,+,aligned_malloc,void*,"size_t, size_t" +Function,+,aligned_alloc,void*,"size_t, size_t" Function,-,arc4random,__uint32_t, Function,-,arc4random_buf,void,"void*, size_t" Function,-,arc4random_uniform,__uint32_t,__uint32_t diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index cf1eb642d..d381e350f 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -583,8 +583,7 @@ Function,-,acosh,double,double Function,-,acoshf,float,float Function,-,acoshl,long double,long double Function,-,acosl,long double,long double -Function,-,aligned_alloc,void*,"size_t, size_t" -Function,+,aligned_malloc,void*,"size_t, size_t" +Function,+,aligned_alloc,void*,"size_t, size_t" Function,-,arc4random,__uint32_t, Function,-,arc4random_buf,void,"void*, size_t" Function,-,arc4random_uniform,__uint32_t,__uint32_t From f1e6de796410e3aa9ac462d536d52b838b71ec41 Mon Sep 17 00:00:00 2001 From: SG Date: Tue, 9 Apr 2024 17:10:59 +0300 Subject: [PATCH 081/129] aligned_alloc, parameters order --- furi/core/memmgr.c | 2 +- lib/flipper_application/elf/elf_file.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/furi/core/memmgr.c b/furi/core/memmgr.c index 6c8937850..768d44890 100644 --- a/furi/core/memmgr.c +++ b/furi/core/memmgr.c @@ -38,7 +38,7 @@ char* strdup(const char* s) { return y; } -void* aligned_alloc(size_t size, size_t alignment) { +void* aligned_alloc(size_t alignment, size_t size) { return pvPortAllocAligned(size, alignment); } diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 80d86b637..e117e546a 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -461,7 +461,7 @@ static bool elf_load_section_data(ELFFile* elf, ELFSection* section, Elf32_Shdr* return true; } - section->data = aligned_alloc(section_header->sh_size, section_header->sh_addralign); + section->data = aligned_alloc(section_header->sh_addralign, section_header->sh_size); section->size = section_header->sh_size; if(section_header->sh_type == SHT_NOBITS) { From db14ca964bd4456f8f86e79a4b1a28469890952e Mon Sep 17 00:00:00 2001 From: Victor Nikitchuk Date: Tue, 9 Apr 2024 18:05:30 +0300 Subject: [PATCH 082/129] Status output !TX/RX on the GDO2 CC1101 pin (#3571) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Status output !TX/RX on the GDO2 CC1101 pin\ * Fix PVS warnings Co-authored-by: あく --- .../drivers/subghz/cc1101_ext/cc1101_ext.c | 20 +++++++++++++++++++ applications/services/loader/loader.c | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index 8be60088a..429a4b3a4 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -177,6 +177,14 @@ static bool subghz_device_cc1101_ext_check_init(void) { furi_hal_gpio_init( subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + // Reset GDO2 (!TX/RX) to floating state + cc1101_status = cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHighImpedance); + if(cc1101_status.CHIP_RDYn != 0) { + //timeout or error + break; + } + // Go to sleep cc1101_status = cc1101_shutdown(subghz_device_cc1101_ext->spi_bus_handle); if(cc1101_status.CHIP_RDYn != 0) { @@ -385,6 +393,9 @@ void subghz_device_cc1101_ext_reset(void) { // Warning: push pull cc1101 clock output on GD0 cc1101_write_reg( subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); + // Reset GDO2 (!TX/RX) to floating state + cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHighImpedance); furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); } @@ -394,6 +405,9 @@ void subghz_device_cc1101_ext_idle(void) { //waiting for the chip to switch to IDLE mode furi_check(cc1101_wait_status_state( subghz_device_cc1101_ext->spi_bus_handle, CC1101StateIDLE, 10000)); + // Reset GDO2 (!TX/RX) to floating state + cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHighImpedance); furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); } @@ -403,6 +417,10 @@ void subghz_device_cc1101_ext_rx(void) { //waiting for the chip to switch to Rx mode furi_check( cc1101_wait_status_state(subghz_device_cc1101_ext->spi_bus_handle, CC1101StateRX, 10000)); + // Go GDO2 (!TX/RX) to high (RX state) + cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHW | CC1101_IOCFG_INV); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); } @@ -413,6 +431,8 @@ bool subghz_device_cc1101_ext_tx(void) { //waiting for the chip to switch to Tx mode furi_check( cc1101_wait_status_state(subghz_device_cc1101_ext->spi_bus_handle, CC1101StateTX, 10000)); + // Go GDO2 (!TX/RX) to low (TX state) + cc1101_write_reg(subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHW); furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); return true; } diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 1527807cc..cedc2e83e 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -100,7 +100,7 @@ void loader_start_detached_with_gui_error(Loader* loader, const char* name, cons LoaderMessage message = { .type = LoaderMessageTypeStartByNameDetachedWithGuiError, - .start.name = name ? strdup(name) : NULL, + .start.name = strdup(name), .start.args = args ? strdup(args) : NULL, }; furi_message_queue_put(loader->queue, &message, FuriWaitForever); From 01c55c7b7eee48ab7823b35df996ef57488f3cd6 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Tue, 9 Apr 2024 22:09:46 +0100 Subject: [PATCH 083/129] Fix build --- applications/services/power/power_service/power.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/applications/services/power/power_service/power.c b/applications/services/power/power_service/power.c index 7badb7103..39724910c 100644 --- a/applications/services/power/power_service/power.c +++ b/applications/services/power/power_service/power.c @@ -300,9 +300,11 @@ static void power_loader_callback(const void* message, void* context) { Power* power = context; const LoaderEvent* event = message; - if(event->type == LoaderEventTypeApplicationStarted) { + if(event->type == LoaderEventTypeApplicationBeforeLoad) { power_auto_shutdown_inhibit(power); - } else if(event->type == LoaderEventTypeApplicationStopped) { + } else if( + event->type == LoaderEventTypeApplicationLoadFailed || + event->type == LoaderEventTypeApplicationStopped) { power_auto_shutdown_arm(power); } } From c1446116099790220053a36e5eea765569c93541 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 10 Apr 2024 01:44:41 +0300 Subject: [PATCH 084/129] update changelog --- CHANGELOG.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0066ec7e9..70b8bdf9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,9 +10,16 @@ * JS & HAL: Various fixes and FURI_HAL_RANDOM_MAX define added (by @Willy-JL) * JS: BadUSB layout support (by @Willy-JL) * JS: Module `widget` and path globals (by @jamisonderek) -* Apps: NFC Magic - **Gen2 writing support** (by @Astrrra) +* Apps: NFC Magic - **Gen2 writing support, Gen4 NTAG password and PACK fixes** (by @Astrrra) * Apps: MFKey - fixed crashes (by @noproto) * Apps: **Check out Apps updates by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* OFW: Desktop/Loader: Unload animations before loading FAPs +* OFW: JS Documentation +* OFW: Update radio stack to v1.19.0 +* OFW: Move crypto1 to helpers, add it to the public API +* OFW: Explain RNG differences, add FURI_HAL_RANDOM_MAX +* OFW: Furi: Add "out of memory" and "malloc(0)" crash messages +* OFW: IR: Fix crash on duty_cycle=1 * OFW: Desktop: ensure that animation is unloaded before app start (fixes some out of memory crashes) * OFW: Hide unlock with reader for MFU-C * OFW: fbt: fixed missing FBT_FAP_DEBUG_ELF_ROOT to dist env From 225d2d30df51a94f9e93bb117bcc9bc68b5957a7 Mon Sep 17 00:00:00 2001 From: Kuronons <110337784+Kuronons@users.noreply.github.com> Date: Wed, 10 Apr 2024 01:40:17 +0200 Subject: [PATCH 085/129] MNTM Pack - Adding IR reading yappy asset --- .../Dolphin/DolphinReadingSuccess_59x63.png | Bin 0 -> 2880 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/packs/Momentum/Icons/Dolphin/DolphinReadingSuccess_59x63.png diff --git a/assets/packs/Momentum/Icons/Dolphin/DolphinReadingSuccess_59x63.png b/assets/packs/Momentum/Icons/Dolphin/DolphinReadingSuccess_59x63.png new file mode 100644 index 0000000000000000000000000000000000000000..b700ba94da72901250f3c77a62155286b77ef47c GIT binary patch literal 2880 zcmbVO3v3j}8NSj4#XKcQR0KsV2f_rH+nIgu#b|Ns3n|39;JY0bUk$4njL@p#5pS4C>+^&WaGykitSzjwO)D!r7YtCrgy&)CWC z@jcJ3-BUcCJKj&!H9C#)#eQrheVSqE#FtB^Dca+ySeQ#|xP>^3PMQ*_fcNjO{@%+Z zjDWX6jKg?3Oqvr_>oR27x|%w?t_7=xcVQ({k@Hi6Bylt*muyYh{#?MD=k?Qj_ciNf z@(`yb;0?MCna221CTwL0Bl#e}5b}(y`j8}uoVr89CnbnQT zC^aft58rq$umq_-n+~KsTrDB3Y51-+Q8{wZg376aVu}FsDiw|e5$K8{0Ag@NR4}i} zJg#8fLZK8xHTdmZ8bOfo2*?o9lO~un0Z;*ofD8#KNJ4}wm8+CwmdvHgs;tWxh^mPI z(F6nN0xtta7d6$CAkBbLDwiT5gmh#AQzs%&WB~%5U;+e5G9*EkOaxJ>T$%!TPR24& zH9GSYO!K8n5Cg?Th?r261g>N*EJ97xMF@z@DYSjMTm}_tDx9Q9#DKcaml_vVblxyT z8DI%M0f~tab zRE>lsC`S+;Q9lP+<*C* zJF|0}$_DRhm=t^Mc)0iOi(hwty654(*XC*$r|q46VL|yu_MxriYaXzAk9N+iTU2p= z|GAgz=d6FaKhXS2e`?Qup`UB`x_jcB`P(2ppZ&x3N zGq$`*`mV2d=|IbZnB8&e@p228gw=vJZcwyyV9>QO#?0v!RcUG&)&!5dT;ukmHTdxC>93wY`-{%5X-5YZzp#9QuYB~oe;DmKxbsX$yA`YL z;`^p-Z^+(`zMuR&cg49c{MnVZ9Z#=5(b@F;o~kF+-~D(JKUm3ce&P6;>1DkWf3j$6 za8|$U+#{MjAI(qW8}-b&vKIZ7P2COuTt4;EgNHVKb#3rd{L+pkA0K%A$ODhql^-sC j=T9qpo*QhRvB5Lh^Ml7O9DlUO-Q!kAYa)9?t2X}|U}xX< literal 0 HcmV?d00001 From 2150f18496a3d8f683ba4b6a54dd213a0009e69d Mon Sep 17 00:00:00 2001 From: Zachary Weiss Date: Tue, 9 Apr 2024 20:19:27 -0400 Subject: [PATCH 086/129] Struct&var refactor for clarity --- .../nfc/plugins/supported_cards/charliecard.c | 116 +++++++++--------- 1 file changed, 61 insertions(+), 55 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/charliecard.c b/applications/main/nfc/plugins/supported_cards/charliecard.c index 81b9bb897..136cfeb27 100644 --- a/applications/main/nfc/plugins/supported_cards/charliecard.c +++ b/applications/main/nfc/plugins/supported_cards/charliecard.c @@ -94,7 +94,7 @@ // timestep is one minute #define CHARLIE_TIME_DELTA_SECS 60 #define CHARLIE_END_VALID_DELTA_SECS 60 * 8 -#define CHARLIE_N_TRIP_HISTORY 10 +#define CHARLIE_N_TRANSACTION_HISTORY 10 #define CHARLIE_N_PASSES 4 typedef struct { @@ -147,7 +147,7 @@ typedef struct { uint8_t g_flag; Money fare; uint16_t f_flag; -} Trip; +} Transaction; typedef struct { bool valid; @@ -206,7 +206,7 @@ static const IdMapping charliecard_types[] = { {.id = 166, .name = "30 Day Commuter Rail Zone 1A Pass"}, {.id = 167, .name = "30 Day Commuter Rail Zone 1 Pass"}, {.id = 168, .name = "30 Day Commuter Rail Zone 2 Pass"}, - {.id = 169, .name = "30 Day Commuter Rail Zone 3 Pass"}, + {.id = 169, .name = "30 Day ComZmuter Rail Zone 3 Pass"}, {.id = 170, .name = "30 Day Commuter Rail Zone 4 Pass"}, {.id = 171, .name = "30 Day Commuter Rail Zone 5 Pass"}, {.id = 172, .name = "30 Day Commuter Rail Zone 6 Pass"}, @@ -717,7 +717,11 @@ static Pass // the uk1 and date fields, and is always set to 1 regardless // same is true of type & end-validity split found in balance sector // - // + // likely fields incl + // — type #, + // — a secondary date field (eg start/end, end validity or normal format) + // — ID of FVM from which the pass was loaded + // check for empty, if so, return struct filled w/ 0s // (incl "valid" field: hence, "valid" is false-y) if(pos_to_num(data, sector_num, block_num, byte_num, 6) == 0x002000000000) { @@ -727,7 +731,7 @@ static Pass // const DateTime start = date_parse(data, sector_num, block_num, byte_num + 1); const uint16_t pre = pos_to_num(data, sector_num, block_num, byte_num, 2) >> 6; - const uint16_t post = pos_to_num(data, sector_num, block_num, byte_num + 4, 2); + const uint16_t post = (pos_to_num(data, sector_num, block_num, byte_num + 4, 2) >> 2) & 0x3ff; // these values make sense for a date, but implied position of type // before end validity, as seen in balance sector, doesn't seem @@ -740,8 +744,9 @@ static Pass return (Pass){true, pre, post, date}; } -static Trip trip_parse(const MfClassicData* data, uint8_t sector, uint8_t block, uint8_t byte) { - /* This function parses individual trips. Each trip packs 7 bytes, stored as follows: +static Transaction + transaction_parse(const MfClassicData* data, uint8_t sector, uint8_t block, uint8_t byte) { + /* This function parses individual transactions. Each transaction packs 7 bytes, stored as follows: 0 1 2 3 4 5 6 +----.----.----+----.--+-+----.----+ @@ -753,23 +758,23 @@ static Trip trip_parse(const MfClassicData* data, uint8_t sector, uint8_t block, Gate ID ("loc") is only the first 13 bits of 0x3:0x5, the final three bits appear to be flags ("f"). Least significant flag bit seems to indicate: - — When f & 1 == 0, fare (the amount by which balance is decremented) - — When f & 1 == 1, refill (the amount by which balance is incremented) + — When f & 1 == 1, fare (the amount by which balance is decremented) + — When f & 1 == 0, refill (the amount by which balance is incremented) MSB (sign bit) of amt seems to serve the same role, just inverted, ie - — When amt & 0x8000 == 0x8000, fare - — When amt & 0x8000 == 0, refill + — When amt & 0x8000 == 0, fare + — When amt & 0x8000 == 0x8000, refill Remaining unknown bits: — f & 0b100 — f & 0b010 - — amt & 1; does not seem to correspond with card type, last trip, first trip, refill v. fare, etc + — amt & 1; does not seem to correspond with card type, last transaction, first transaction, refill v. fare, etc */ const DateTime date = date_parse(data, sector, block, byte); const uint16_t gate = pos_to_num(data, sector, block, byte + 3, 2) >> 3; const uint8_t g_flag = pos_to_num(data, sector, block, byte + 3, 2) & 0b111; const Money fare = money_parse(data, sector, block, byte + 5); const uint16_t f_flag = pos_to_num(data, sector, block, byte + 5, 2) & 0x8001; - return (Trip){date, gate, g_flag, fare, f_flag}; + return (Transaction){date, gate, g_flag, fare, f_flag}; } // ********************************************************** @@ -810,10 +815,10 @@ static CounterSector counter_sector_parse(const MfClassicData* data) { // 0x060 | uses2 | uk | ... 00 00 ... | // +----.----+----+----.----.----.----.----.----.----.----.----.----.----.----.----+ // - // uk := "unknown"; if nonzero, seems to only occupy the first 4 bits (ie, mask w/ 0xF0), + // uk := "unknown"; if nonzero, seems to only occupy the first 4 bits (ie, uk & 0xF0 == uk), // with the remaining 4 zero - // Card has two transaction sectors (2 & 3) containing balance data, with two + // Card has two sectors (2 & 3) containing balance data, with two // corresponding trip counters in 0x50:0x51 & 0x60:0x61 (sector 1, byte 0:1 of blocks 1 & 2). // The *lower* of the two values *minus one* is the true use count, @@ -866,7 +871,7 @@ static Pass* passes_parse(const MfClassicData* data) { // // 0 1 2 3 4 5 6 7 8 9 A B C D E F // +----.----.----.----.----.----+----+----.----.----.----.----.----+----+----.----+ - // 0x100 | pass0? | 00 | pass1? | 00 | crc | 0x140 + // 0x100 | pass0/2? | 00 | pass1/3? | 00 | crc | 0x140 // +----.----.----.----.----.----+----+----.----.----.----.----.----+----+----.----+ // 0x110 | ... 00 00 ... | crc | 0x150 // +----.----.----.----.----.----.----.----.----.----.----.----.----.----+----.----+ @@ -886,16 +891,16 @@ static Pass* passes_parse(const MfClassicData* data) { return passes; } -static Trip* trips_parse(const MfClassicData* data) { +static Transaction* transactions_parse(const MfClassicData* data) { // Transaction history (Sectors 6–7) // // 0 1 2 3 4 5 6 7 8 9 A B C D E F // +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ - // 0x180 | trip0 | trip1 | crc | + // 0x180 | transaction0 | transaction1 | crc | // +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ // ... ... ... ... // +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ - // 0x1D0 | trip8 | trip9 | crc | + // 0x1D0 | transaction8 | transaction9 | crc | // +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ // 0x1E0 | ... 00 00 ... | crc | // +----.----.----.----.----.----.----.----.----.----.----.----.----.----+----.----+ @@ -903,20 +908,20 @@ static Trip* trips_parse(const MfClassicData* data) { // Transactions are not sorted, rather, appear to get overwritten // sequentially. (eg, sorted modulo array rotation) - Trip* trips = malloc(sizeof(Trip) * CHARLIE_N_TRIP_HISTORY); + Transaction* transactions = malloc(sizeof(Transaction) * CHARLIE_N_TRANSACTION_HISTORY); - // Parse each trip field using some modular math magic to get the offsets: - // move from sector 6 -> 7 after the first 6 trips - // move a block within a given sector every 2 trips, reset every 3 blocks (as sector has changed) + // Parse each transaction field using some modular math magic to get the offsets: + // move from sector 6 -> 7 after the first 6 transactions + // move a block within a given sector every 2 transactions, reset every 3 blocks (as sector has changed) // alternate between a start byte of 0 and 7 with every iteration - for(size_t i = 0; i < CHARLIE_N_TRIP_HISTORY; i++) { - trips[i] = trip_parse(data, 6 + (i / 6), (i / 2) % 3, (i % 2) * 7); + for(size_t i = 0; i < CHARLIE_N_TRANSACTION_HISTORY; i++) { + transactions[i] = transaction_parse(data, 6 + (i / 6), (i / 2) % 3, (i % 2) * 7); } // Iterate through the array to find the maximum (newest) date value int max_idx = 0; - for(int i = 1; i < CHARLIE_N_TRIP_HISTORY; i++) { - if(dt_ge(trips[i].date, trips[max_idx].date)) { + for(int i = 1; i < CHARLIE_N_TRANSACTION_HISTORY; i++) { + if(dt_ge(transactions[i].date, transactions[max_idx].date)) { max_idx = i; } } @@ -924,24 +929,24 @@ static Trip* trips_parse(const MfClassicData* data) { // Sort by rotating for(int r = 0; r < (max_idx + 1); r++) { // Store the first element - Trip temp = trips[0]; + Transaction temp = transactions[0]; // Shift elements to the left - for(int i = 0; i < CHARLIE_N_TRIP_HISTORY - 1; i++) { - trips[i] = trips[i + 1]; + for(int i = 0; i < CHARLIE_N_TRANSACTION_HISTORY - 1; i++) { + transactions[i] = transactions[i + 1]; } // Move the first element to the last - trips[CHARLIE_N_TRIP_HISTORY - 1] = temp; + transactions[CHARLIE_N_TRANSACTION_HISTORY - 1] = temp; } // Reverse order, such that newest is first, oldest last - for(int i = 0; i < CHARLIE_N_TRIP_HISTORY / 2; i++) { + for(int i = 0; i < CHARLIE_N_TRANSACTION_HISTORY / 2; i++) { // Swap elements at index i and size - i - 1 - Trip temp = trips[i]; - trips[i] = trips[CHARLIE_N_TRIP_HISTORY - i - 1]; - trips[CHARLIE_N_TRIP_HISTORY - i - 1] = temp; + Transaction temp = transactions[i]; + transactions[i] = transactions[CHARLIE_N_TRANSACTION_HISTORY - i - 1]; + transactions[CHARLIE_N_TRANSACTION_HISTORY - i - 1] = temp; } - return trips; + return transactions; } /* @@ -976,12 +981,12 @@ static DateTime expiry(DateTime iss) { return exp; } -static bool expired(DateTime expiry, DateTime last_trip) { +static bool expired(DateTime expiry, DateTime last_transaction) { // if a card has sat unused for >2 years, expired (verify this claim?) // else expired if current date > expiry date uint32_t ts_exp = datetime_datetime_to_timestamp(&expiry); - uint32_t ts_last = datetime_datetime_to_timestamp(&last_trip); + uint32_t ts_last = datetime_datetime_to_timestamp(&last_transaction); uint32_t ts_now = time_now(); return (ts_exp <= ts_now) | ((ts_now - ts_last) >= (2 * 365 * 24 * 60 * 60)); @@ -1020,7 +1025,8 @@ void type_format_cat(FuriString* out, uint16_t type) { void pass_format_cat(FuriString* out, Pass pass) { furi_string_cat_printf(out, "\n-Pre: %b", pass.pre); // type_format_cat(out, pass.type); - furi_string_cat_printf(out, "\n-Post: %b", pass.post); + furi_string_cat_printf(out, "\n-Post: "); + type_format_cat(out, pass.post); // locale_format_dt_cat(out, &pass.start); furi_string_cat_printf(out, "\n-Date: "); locale_format_dt_cat(out, &pass.date); @@ -1055,37 +1061,37 @@ void money_format_cat(FuriString* out, Money money) { furi_string_cat_printf(out, "$%u.%02u", money.dollars, money.cents); } -void trip_format_cat(FuriString* out, Trip trip) { +void transaction_format_cat(FuriString* out, Transaction transaction) { const char* sep = " "; const char* sta; - locale_format_dt_cat(out, &trip.date); - furi_string_cat_printf(out, "\n%s", !!(trip.g_flag & 0x1) ? "-" : "+"); - money_format_cat(out, trip.fare); - if(!!(trip.g_flag & 0x1) && (trip.fare.dollars == FARE_BUS.dollars) && - (trip.fare.cents == FARE_BUS.cents)) { + locale_format_dt_cat(out, &transaction.date); + furi_string_cat_printf(out, "\n%s", !!(transaction.g_flag & 0x1) ? "-" : "+"); + money_format_cat(out, transaction.fare); + if(!!(transaction.g_flag & 0x1) && (transaction.fare.dollars == FARE_BUS.dollars) && + (transaction.fare.cents == FARE_BUS.cents)) { // if not a refill, and the fare amount is equal to bus fare (any better approach? flag bits for modality?) // format for bus — supposedly some correlation between gate ID & bus #, haven't investigated - furi_string_cat_printf(out, "%s#%u", sep, trip.gate); - } else if(get_map_item(trip.gate, charliecard_fare_gate_ids, kNumFareGateIds, &sta)) { + furi_string_cat_printf(out, "%s#%u", sep, transaction.gate); + } else if(get_map_item(transaction.gate, charliecard_fare_gate_ids, kNumFareGateIds, &sta)) { // station found in fare gate ID map, append station name furi_string_cat_str(out, sep); furi_string_cat_str(out, sta); } else { // no found station in fare gate ID map & not a bus, just print ID w/o add'l info - furi_string_cat_printf(out, "%s#%u", sep, trip.gate); + furi_string_cat_printf(out, "%s#%u", sep, transaction.gate); } // print flags for debugging purposes if(is_debug()) { - furi_string_cat_printf(out, "%s%x%s%x", sep, trip.g_flag, sep, trip.f_flag); + furi_string_cat_printf(out, "%s%x%s%x", sep, transaction.g_flag, sep, transaction.f_flag); } } -void trips_format_cat(FuriString* out, Trip* trips) { +void transactions_format_cat(FuriString* out, Transaction* transactions) { furi_string_cat_printf(out, "\nTransactions:"); - for(size_t i = 0; i < CHARLIE_N_TRIP_HISTORY; i++) { + for(size_t i = 0; i < CHARLIE_N_TRANSACTION_HISTORY; i++) { furi_string_cat_printf(out, "\n"); - trip_format_cat(out, trips[i]); + transaction_format_cat(out, transactions[i]); furi_string_cat_printf(out, "\n"); } } @@ -1126,7 +1132,7 @@ static bool charliecard_parse(const NfcDevice* device, FuriString* parsed_data) const BalanceSector balance_sector = balance_sector_parse(data, counter_sector.active_balance_sector); Pass* passes = passes_parse(data); - Trip* trips = trips_parse(data); + Transaction* transactions = transactions_parse(data); // print/append card data furi_string_cat_printf(parsed_data, "\e#CharlieCard"); @@ -1152,8 +1158,8 @@ static bool charliecard_parse(const NfcDevice* device, FuriString* parsed_data) passes_format_cat(parsed_data, passes); free(passes); - trips_format_cat(parsed_data, trips); - free(trips); + transactions_format_cat(parsed_data, transactions); + free(transactions); parsed = true; } while(false); From 905c33a08b028ded4a246d4acb8ff21ba9a2a34f Mon Sep 17 00:00:00 2001 From: Zachary Weiss Date: Tue, 9 Apr 2024 21:12:46 -0400 Subject: [PATCH 087/129] Only print end validity when present Same might be worth doing for type & balance, keeping those as is for now; expiry/EV specifically given the switching logic as it has most potential for misinterpretation of null/0 value --- .../nfc/plugins/supported_cards/charliecard.c | 76 +++++++++++-------- 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/charliecard.c b/applications/main/nfc/plugins/supported_cards/charliecard.c index 136cfeb27..3582eca63 100644 --- a/applications/main/nfc/plugins/supported_cards/charliecard.c +++ b/applications/main/nfc/plugins/supported_cards/charliecard.c @@ -638,6 +638,11 @@ static bool dt_ge(DateTime dt1, DateTime dt2) { return datetime_datetime_to_timestamp(&dt1) >= datetime_datetime_to_timestamp(&dt2); } +static bool dt_eq(DateTime dt1, DateTime dt2) { + // compares two DateTimes + return datetime_datetime_to_timestamp(&dt1) == datetime_datetime_to_timestamp(&dt2); +} + static bool get_map_item(uint16_t id, const IdMapping* map, size_t sz, const char** out) { // code borrowed from Jeremy Cooper's 'clipper.c'. Used as follows: // const char* s; if(!get_map_item(_,_,_,&s)) {s="Default str";} @@ -712,7 +717,7 @@ static Pass // | 00 20 00 00 00 00 | // +----.----.----.----.----.----+ // - // if not blank, uk1 MSB seems to always be set to 1... + // even when not blank, uk1 LSB seems to always be set to 1... // the sole bit set to 1 on the blank entry seems to divide // the uk1 and date fields, and is always set to 1 regardless // same is true of type & end-validity split found in balance sector @@ -746,29 +751,32 @@ static Pass static Transaction transaction_parse(const MfClassicData* data, uint8_t sector, uint8_t block, uint8_t byte) { - /* This function parses individual transactions. Each transaction packs 7 bytes, stored as follows: + // This function parses individual transactions. Each transaction packs 7 bytes, stored as follows: + // + // 0 1 2 3 4 5 6 + // +----.----.----+----.--+-+----.----+ + // | date | loc |f| amt | + // +----.----.----+----.--+-+----.----+ + // + // Where date is in the typical format, loc represents the fare gate tapped, and amt is the fare amount. + // Amount appears to contain some flag bits, however, it is unclear what precisely their function is. + // + // Gate ID ("loc") is only the first 13 bits of 0x3:0x5, the final three bits appear to be flags ("f"). + // Least significant flag bit seems to indicate: + // — When f & 1 == 1, fare (the amount by which balance is decremented) + // — When f & 1 == 0, refill (the amount by which balance is incremented) + // MSB (sign bit) of amt seems to serve the same role, just inverted, ie + // — When amt & 0x8000 == 0, fare + // — When amt & 0x8000 == 0x8000, refill + // Only contradiction between the two observed is on cards w/ passes; + // MSB of amt seems to be set for every transaction when (remaining bits of) amt is 0 on a card w/ a pass + // Hence, using f's LSB as method for inferring fare v. refill + // + // Remaining unknown bits: + // — f & 0b100; seems to be set on fares where the card has a pass, and amt is 0 + // — f & 0b010 + // — amt & 1; does not seem to correspond with card type, last transaction, first transaction, refill v. fare, etc - 0 1 2 3 4 5 6 - +----.----.----+----.--+-+----.----+ - | date | loc |f| amt | - +----.----.----+----.--+-+----.----+ - - Where date is in the typical format, loc represents the fare gate tapped, and amt is the fare amount. - Amount appears to contain some flag bits, however, it is unclear what precisely their function is. - - Gate ID ("loc") is only the first 13 bits of 0x3:0x5, the final three bits appear to be flags ("f"). - Least significant flag bit seems to indicate: - — When f & 1 == 1, fare (the amount by which balance is decremented) - — When f & 1 == 0, refill (the amount by which balance is incremented) - MSB (sign bit) of amt seems to serve the same role, just inverted, ie - — When amt & 0x8000 == 0, fare - — When amt & 0x8000 == 0x8000, refill - - Remaining unknown bits: - — f & 0b100 - — f & 0b010 - — amt & 1; does not seem to correspond with card type, last transaction, first transaction, refill v. fare, etc - */ const DateTime date = date_parse(data, sector, block, byte); const uint16_t gate = pos_to_num(data, sector, block, byte + 3, 2) >> 3; const uint8_t g_flag = pos_to_num(data, sector, block, byte + 3, 2) & 0b111; @@ -1124,8 +1132,6 @@ static bool charliecard_parse(const NfcDevice* device, FuriString* parsed_data) if(key_a != charliecard_1k_keys[verify_sector].a) break; if(key_b != charliecard_1k_keys[verify_sector].b) break; - // TODO: Verify add'l? - // parse card data const uint32_t card_number = mfg_sector_parse(data); const CounterSector counter_sector = counter_sector_parse(data); @@ -1138,6 +1144,10 @@ static bool charliecard_parse(const NfcDevice* device, FuriString* parsed_data) furi_string_cat_printf(parsed_data, "\e#CharlieCard"); furi_string_cat_printf(parsed_data, "\nSerial: 5-%lu", card_number); + // Type and balance 0 on some (Perq) cards + // (ie no "main" type / balance / end validity, + // essentially only pass & trip info) + // skip/change formatting for that case? furi_string_cat_printf(parsed_data, "\nBal: "); money_format_cat(parsed_data, balance_sector.balance); @@ -1149,18 +1159,24 @@ static bool charliecard_parse(const NfcDevice* device, FuriString* parsed_data) furi_string_cat_printf(parsed_data, "\nIssued: "); locale_format_dt_cat(parsed_data, &balance_sector.issued); - furi_string_cat_printf(parsed_data, "\nExpiry: "); - locale_format_dt_cat(parsed_data, &balance_sector.end_validity); + if(!dt_eq(balance_sector.end_validity, CHARLIE_EPOCH) & + dt_ge(balance_sector.end_validity, balance_sector.issued)) { + // sometimes (seen on Perq cards) end validity field is all 0 + // When this is the case, calc'd end validity is equal to CHARLIE_EPOCH). + // Only print if not 0, & end validity after issuance date + furi_string_cat_printf(parsed_data, "\nExpiry: "); + locale_format_dt_cat(parsed_data, &balance_sector.end_validity); + } // const DateTime last = date_parse(data, active_sector, 0, 1); // furi_string_cat_printf(parsed_data, "\nExpired: %s", expired(e_v, last) ? "Yes" : "No"); - passes_format_cat(parsed_data, passes); - free(passes); - transactions_format_cat(parsed_data, transactions); free(transactions); + passes_format_cat(parsed_data, passes); + free(passes); + parsed = true; } while(false); From 66f1558b070d51eda674b4751a29516b4ab97bdb Mon Sep 17 00:00:00 2001 From: Zachary Weiss Date: Tue, 9 Apr 2024 21:14:57 -0400 Subject: [PATCH 088/129] Typo --- applications/main/nfc/plugins/supported_cards/charliecard.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/nfc/plugins/supported_cards/charliecard.c b/applications/main/nfc/plugins/supported_cards/charliecard.c index 3582eca63..9880ca3ad 100644 --- a/applications/main/nfc/plugins/supported_cards/charliecard.c +++ b/applications/main/nfc/plugins/supported_cards/charliecard.c @@ -206,7 +206,7 @@ static const IdMapping charliecard_types[] = { {.id = 166, .name = "30 Day Commuter Rail Zone 1A Pass"}, {.id = 167, .name = "30 Day Commuter Rail Zone 1 Pass"}, {.id = 168, .name = "30 Day Commuter Rail Zone 2 Pass"}, - {.id = 169, .name = "30 Day ComZmuter Rail Zone 3 Pass"}, + {.id = 169, .name = "30 Day Commuter Rail Zone 3 Pass"}, {.id = 170, .name = "30 Day Commuter Rail Zone 4 Pass"}, {.id = 171, .name = "30 Day Commuter Rail Zone 5 Pass"}, {.id = 172, .name = "30 Day Commuter Rail Zone 6 Pass"}, From de265b282a83460b79a6bedcd6de71b40a01676a Mon Sep 17 00:00:00 2001 From: Chonk_m <143907552+Davim09@users.noreply.github.com> Date: Wed, 10 Apr 2024 02:16:34 -0300 Subject: [PATCH 089/129] Add Dolphin 3d printing and wardriving animation (#86) * Add files via upload * Add files via upload * Update manifest.txt * Update manifest.txt * Rebalance levels * Naming consistency * Consistent cycle time --------- Co-authored-by: Willy-JL <49810075+Willy-JL@users.noreply.github.com> --- .../L1_3d_printing_128x64/Frame_0.png | Bin 0 -> 1449 bytes .../L1_3d_printing_128x64/frame_1.png | Bin 0 -> 1470 bytes .../L1_3d_printing_128x64/frame_10.png | Bin 0 -> 1266 bytes .../L1_3d_printing_128x64/frame_11.png | Bin 0 -> 1275 bytes .../L1_3d_printing_128x64/frame_12.png | Bin 0 -> 1302 bytes .../L1_3d_printing_128x64/frame_13.png | Bin 0 -> 1266 bytes .../L1_3d_printing_128x64/frame_14.png | Bin 0 -> 1266 bytes .../L1_3d_printing_128x64/frame_15.png | Bin 0 -> 1284 bytes .../L1_3d_printing_128x64/frame_16.png | Bin 0 -> 1284 bytes .../L1_3d_printing_128x64/frame_17.png | Bin 0 -> 1257 bytes .../L1_3d_printing_128x64/frame_18.png | Bin 0 -> 1281 bytes .../L1_3d_printing_128x64/frame_19.png | Bin 0 -> 1299 bytes .../L1_3d_printing_128x64/frame_2.png | Bin 0 -> 1269 bytes .../L1_3d_printing_128x64/frame_20.png | Bin 0 -> 1287 bytes .../L1_3d_printing_128x64/frame_21.png | Bin 0 -> 1287 bytes .../L1_3d_printing_128x64/frame_22.png | Bin 0 -> 1278 bytes .../L1_3d_printing_128x64/frame_23.png | Bin 0 -> 1314 bytes .../L1_3d_printing_128x64/frame_24.png | Bin 0 -> 1287 bytes .../L1_3d_printing_128x64/frame_25.png | Bin 0 -> 1281 bytes .../L1_3d_printing_128x64/frame_26.png | Bin 0 -> 1299 bytes .../L1_3d_printing_128x64/frame_27.png | Bin 0 -> 1293 bytes .../L1_3d_printing_128x64/frame_28.png | Bin 0 -> 1302 bytes .../L1_3d_printing_128x64/frame_29.png | Bin 0 -> 1302 bytes .../L1_3d_printing_128x64/frame_3.png | Bin 0 -> 1233 bytes .../L1_3d_printing_128x64/frame_30.png | Bin 0 -> 1293 bytes .../L1_3d_printing_128x64/frame_31.png | Bin 0 -> 1266 bytes .../L1_3d_printing_128x64/frame_32.png | Bin 0 -> 1314 bytes .../L1_3d_printing_128x64/frame_33.png | Bin 0 -> 1299 bytes .../L1_3d_printing_128x64/frame_34.png | Bin 0 -> 1293 bytes .../L1_3d_printing_128x64/frame_35.png | Bin 0 -> 1287 bytes .../L1_3d_printing_128x64/frame_36.png | Bin 0 -> 1302 bytes .../L1_3d_printing_128x64/frame_37.png | Bin 0 -> 1284 bytes .../L1_3d_printing_128x64/frame_38.png | Bin 0 -> 1308 bytes .../L1_3d_printing_128x64/frame_39.png | Bin 0 -> 1299 bytes .../L1_3d_printing_128x64/frame_4.png | Bin 0 -> 1251 bytes .../L1_3d_printing_128x64/frame_40.png | Bin 0 -> 1293 bytes .../L1_3d_printing_128x64/frame_41.png | Bin 0 -> 1269 bytes .../L1_3d_printing_128x64/frame_42.png | Bin 0 -> 1314 bytes .../L1_3d_printing_128x64/frame_43.png | Bin 0 -> 1332 bytes .../L1_3d_printing_128x64/frame_44.png | Bin 0 -> 1305 bytes .../L1_3d_printing_128x64/frame_45.png | Bin 0 -> 1287 bytes .../L1_3d_printing_128x64/frame_5.png | Bin 0 -> 1269 bytes .../L1_3d_printing_128x64/frame_6.png | Bin 0 -> 1287 bytes .../L1_3d_printing_128x64/frame_7.png | Bin 0 -> 1284 bytes .../L1_3d_printing_128x64/frame_8.png | Bin 0 -> 1284 bytes .../L1_3d_printing_128x64/frame_9.png | Bin 0 -> 1266 bytes .../external/L1_3d_printing_128x64/meta.txt | 14 +++++++++++ .../external/L1_Wardriving_128x64/frame_0.png | Bin 0 -> 1536 bytes .../external/L1_Wardriving_128x64/frame_1.png | Bin 0 -> 1569 bytes .../external/L1_Wardriving_128x64/frame_2.png | Bin 0 -> 1575 bytes .../external/L1_Wardriving_128x64/frame_3.png | Bin 0 -> 1587 bytes .../external/L1_Wardriving_128x64/frame_4.png | Bin 0 -> 1560 bytes .../external/L1_Wardriving_128x64/frame_5.png | Bin 0 -> 1590 bytes .../external/L1_Wardriving_128x64/frame_6.png | Bin 0 -> 1578 bytes .../external/L1_Wardriving_128x64/frame_7.png | Bin 0 -> 1539 bytes .../external/L1_Wardriving_128x64/meta.txt | 23 ++++++++++++++++++ assets/dolphin/external/manifest.txt | 14 +++++++++++ 57 files changed, 51 insertions(+) create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/Frame_0.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_1.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_10.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_11.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_12.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_13.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_14.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_15.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_16.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_17.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_18.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_19.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_2.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_20.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_21.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_22.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_23.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_24.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_25.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_26.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_27.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_28.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_29.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_3.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_30.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_31.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_32.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_33.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_34.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_35.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_36.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_37.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_38.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_39.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_4.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_40.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_41.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_42.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_43.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_44.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_45.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_5.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_6.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_7.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_8.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/frame_9.png create mode 100644 assets/dolphin/external/L1_3d_printing_128x64/meta.txt create mode 100644 assets/dolphin/external/L1_Wardriving_128x64/frame_0.png create mode 100644 assets/dolphin/external/L1_Wardriving_128x64/frame_1.png create mode 100644 assets/dolphin/external/L1_Wardriving_128x64/frame_2.png create mode 100644 assets/dolphin/external/L1_Wardriving_128x64/frame_3.png create mode 100644 assets/dolphin/external/L1_Wardriving_128x64/frame_4.png create mode 100644 assets/dolphin/external/L1_Wardriving_128x64/frame_5.png create mode 100644 assets/dolphin/external/L1_Wardriving_128x64/frame_6.png create mode 100644 assets/dolphin/external/L1_Wardriving_128x64/frame_7.png create mode 100644 assets/dolphin/external/L1_Wardriving_128x64/meta.txt diff --git a/assets/dolphin/external/L1_3d_printing_128x64/Frame_0.png b/assets/dolphin/external/L1_3d_printing_128x64/Frame_0.png new file mode 100644 index 0000000000000000000000000000000000000000..753497fae07bafdd109fab5b92aede933de1acbc GIT binary patch literal 1449 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_15.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_15.png new file mode 100644 index 0000000000000000000000000000000000000000..351f2c9b82e06f0b0ddaaf6a09d2a5a5c7e481d4 GIT binary patch literal 1284 zcmV+f1^fDmP)Px(yh%hsRCr$PoY8LNAPhx!{{N$=O0^jw3ckl+2$`^NRU8by2iwqeU+?$({crz! zYy@5#fX8b8yYLObv-RHqJY4?`zy@G9fsbZj_W+NUe>Z^-mwyAW0oeBd&t_ov0MFKc z9|Iq*{{~oFRxV{|#RP@wt!%&CeHUON0T4EkHrRU^bwqL%_ z>J|Xhge}Qo*?PwLKB>zqS{{HF(KZAe-=zU)5&vBJDiByb3zwi2d4ly3M=06aCHyb~b? z6(Yh{ItKN-JOHKLUFrdH@#)VhyYvb}h3NH@zfb)x1%Q?Qt>e_u15m;9)V~3k(Fy~+ zYs99hx;6{}z^auaLqK;T2$9Q#`duai5CCf3ehB~?t(^fz;!jmDZtkc+H9MZ}|g{HLMHK0~uao(X{Y(ah0&M+CE!W=I-5uW@M;7%^9RMDqG7l@ZHVMA_-hY{%bL zT?W8x@ka??%K%odaz2a9K&8^C#{U3dUBl*-(KD@;0cMC_@~;Bo-Sw)GR2hK!qcKlm zBw1AgkT*1*`+5REv>>YNMEmOjpcGp=f7L_KY=;0Klv?$#ltD(;zfu=o>&D62sExM= z$agFCLo;$%1|Spj06->2d@7sLE*S}4D*zU-YK^LNtBiM922cwd`JARl?;LsnXo0{y z-u?f-6EFbY#g^=?`|cSj*t>>k5EKBazI*3iv-dREG6T>~nv?)VV^jdV<3xd=0H6(Z zDI1X8o|aa+CJ6wY^qm37o3U0<^BBz9@d|i!6WF@wG}Y zRw1A9y8s1%cc*y-AkEXr`KcQ_x)zU}$EPd-5vKqZ;Iz>swlo+NEK>97Sb0N9=Q`4^ zy9p#jks}aUq&Lvkm{*OpO6pHzQ@t@fOF-|=i);d~60R4f6@|~&^yj1&34q$+6td_* zQojlSwZ1z(|NSN~V#ld5NS#dssK)aI+v~eDcI13Kb02N{0U*MW+G!pkJhpW1JtFqJ zOXqim-SDr1^F?`t9T9ZW(B_%w_*ubyg?JG@rvs25lhiqVRN(VI<6Ew=y--{R068y@ u62DgC$+5{B;MMIf2VnI8ef#`=0Gm3MfT$ppl>h($07*qoM6N<$f&c&*BT0Dx literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_16.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_16.png new file mode 100644 index 0000000000000000000000000000000000000000..e77cb28ecf1924de4b41fe8631093f238e38d821 GIT binary patch literal 1284 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 Ju0mjf006G3QqBMX literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_2.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_2.png new file mode 100644 index 0000000000000000000000000000000000000000..67b294b31632b9280c3c1f79db284d659577159e GIT binary patch literal 1269 zcmVPx(tw}^dRCr$PoZGJBAPhw(|Nqe)wNfg?!FbsiLK@Fo6$fAT_Knld@p`>p|MK5s zBXA@E9*h0Q@g%^r@lOIg9RDOh5+H}bM{^)&fJeihL*T>VPXZ(X@*d#X9LO2q+4$!g z_;CD_07-zn2Y56GZao7W$0353yk@+<9RVolrCgFhCc|wAa01!`^DWbQPTpnt@_SOZ zAbl`ZbL#l2S-@(K6=+GG>b)HSS~q+r0W3YAyb}Qi z1tLOMc?Q+HJpo$T-I@Vv@abo*x{Lxtff&_OzfbjUMSx!U@2yks8Gr&_m;NNcicuKQ zTViZks-n4CjBW2otrxfkR;b@9jAFS zLqIdp5Go`}^T+1l}XlpaD1a%e( zC=eD~i7eH-lmHr#y&msfb4ak>GD!d$DW{wU{`=UOy;w6l37|!2;5h-G62J=-u_2Ox zL|-qYqqc0*0xW@`PE!(;y0x?EnIsR&;8P&Vb7{=TUqV&CXa6>kIzubK87L{R7SQXU zYr(RDf6f+G-#mJ}Gt%@Sa25e-BAlPCAbK)X0;oRAOGZKfZE&T!Z%+UkENQ*85~ioG zY$G-CBlVP(MXD}T|NEykVgFBH?Ig+m^BQ+XnWULILL#Y+5Z|Npl5@mMwkrv+8vMv| zuT_-W5ukSsTRTfHY z5-HtU8zVD77u&N1j0VO+fRR7n(x6G?8}*Y8f%TR$LvQcyg0{Qw5IAa}g#fDtzE%cC zUih?jZ)p(r#FxwwTI<&IUP1uPD4OM7Ihq~T^Lt4^d?tXGHj%ci>0Q1CY6e+@J|Z7Y z1dRYzfW5w125hwg&`FxK01Nn3uQy2b@D|aQGy;$doO*URKa*@PeW2u42|$gGBtXmf znY}w|V5@ChmGN2^L8*tQ9m;8MpxePdE2*vW%PN4? fu#{gDf&Vi%gMgoK%7p*`002ovPDHLkV1fVu&#FYU literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_20.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_20.png new file mode 100644 index 0000000000000000000000000000000000000000..7a80cf5657f40e946881e8f889c386c45dc3c795 GIT binary patch literal 1287 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_22.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_22.png new file mode 100644 index 0000000000000000000000000000000000000000..c83f6d7412494d01c78c8183ca17e2684acdba21 GIT binary patch literal 1278 zcmVPx(xJg7oRCr$Pn(fl+APj_$_kYp8qi1j!OtPC0T597@OCe+*@_{~{&*$@BeQxW4 zM*(nK?cW}=JFQ+1wh>c+?#=_0q(7TwShO+ zzW^uz>K@?M3|zVfcsw4ec*Qlx_2mFyMeoNI3_1*#0iX$LiTN5MJ!jtK&dcwOx zu#z0sSkFAaC$+qz?E#30RuJ&`ZVf<0{I>O7Ah2o{UZ(g+RR3%Xz>N6szd9-P00T(e z-QRB(06}je0hwuchyrI7pzOL^MgUaJ_u`|jzybw@M+P!9h7|(M<9*Jqdl>*MD59-j zMwCIqutK1DywBNnw*x>DrT=cssW0)R=j`cT4FM~JS70=rUH4J|G_j<@Y6M|)&?t02 z7Yhq=x^^$|`hMjaGwW^-z$laifh4{r_}B$h1UMQ^;}}`t-+THCfd4;~A$hln09wwH zBEZNH69F{tDgq!xB8e~XPBs9n3E2Qh^$BZ>HZCI3<%&Sm=6O><0<`Z{-yR77I+@yyUi7BV1I=mxJhrSDdpZC}d?sJ5aLjlw zFuR6TtIWyJP z`iRE(b$J)C0)W0a8c%K1=@|e>E#GV#*!Jp;PVBmu1wbO*drB;jP;{|6fc_aPL^R5w z$brDQJOB})vI{?JzU={c{a!HR#Qt3?9BXUz1rwq*%g(4MXniS-MAsTqr`I>^y04#% zP6Oa*@mZ%Jp{=m19RlxN#^C^XZsFJq8+7Y;gdn3>-jn2k!5aZ)3uLQsWqtdJ0G4Bz z%G%nz(J4XPmlVSXA)n17O>3P zOxi}iKD-M+1~D7Y8j}qI0s!UrtbKX9D;NPJqoZBGPU1`9ch)`xfK>EZ=>+9@sfOrN z9g}Jy0IbnW4KVBAtP_Hq9<_7y!BHn3`Nd2X?II|;>7GLCCUACsw8_lp(bUiYI7|4x zyFzW^N4H-FKof$6(c3YMPCpuiZ2@2%o_UH}>hfN}N|kxk{-XiV{70pL4Ci}q!L0O= zVGlaNY^X=`ShljEIAgz7QK;5ECb(ol6qIWAlo!R0Loy6|A;` zX@Qu{c^!>T9|K>uUAu&Srj4Z9z^H?w;crI>7@Kw!e$?(OO`{aRh(F#HM2eCAooq*D zM1gFZSii0w_x?*5LpL%6&B5f^5rPcidN^n1L;FMi+4cDcfD8kXgFO&<#}Sy6;AioB_4av5O8+Q!vPrEB-DMNs1kL+nA3md8r$oS(*R)lmNBydue=OO;fS0#Z^Z)<=07*qoM6N<$f;%5k@Bjb+ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_23.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_23.png new file mode 100644 index 0000000000000000000000000000000000000000..f3a007fb3b375eadf50ef9065d1b1ce446c90245 GIT binary patch literal 1314 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_25.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_25.png new file mode 100644 index 0000000000000000000000000000000000000000..266d3cd7db086ef12f7b100c1151221e8ce71d71 GIT binary patch literal 1281 zcmV+c1^)VpP)Px(xk*GpRCr$Poa>S+Aqa#|-v6Pqm7FqV8K4`yPKN!NQDNv$1UI{n=kxjemp`|S zz#{>;t@iI1PXO+%e*$oG{S$x$AdA3TGmtgFt>w=m@aFO-00}_e1KgW|tO4$=f3|@) z*FOPB0P-H-)(l*_26#Lks(8sY$Mxj^U`4OTB@8+YmjR#%YKi$8BRyy4a{J}?MqL5` zO;|||YpiFT-;-K?qvHXHh?WrW_#O>FMEvsVr$AuUEWAwdk*NMT7Jwe{-(Pi7>H!9j zc(}i3CIG=`A_19YPsjqh3Q*SWkr@D0^R@V>E3iO8>5+j9jbVjA>v*5Db}s{f1x2*= z8bcA=Y{!p+Kx*#8N%(y^vRT`+lr6lH=9j(2@YsHm;>==8>h zwYwF7)#9^3@d|IXl9o;88B@q<#}RWRegN>?!m$@N=+8GJIMosHv{w*$kx%o z#ufrl7fKdU*2_ylEW3ZSASkG#?e*3GZP&v3kGh?q05H421HdftrRw|S44`!~OuXwc zJy+#a24EcW0KlA0j7>B~+9i>S8rg}~01U9mHafad#@hk_atgh4?{hXidhK{?fOdV9 z{yo`@eaGcpz;5xST{p@n1b|myFMxH&;06Gt66VZ7_W+(iOTqHY_XLj(7FrVlz!uc) zICNxcM;)5?wo@*Gtl*-Gvp75o07;OUXLnnSML}zQT(#p7n5MQ20LTCX017ICYy|+l zAQT^W314?tD4w5p-3fco=DFn8>JDIO6&*i<|s{%z(OQUtQqI8y;fUosN-s4X;)^<`HbAo)*afSFwY zDgdpZvjIoKq30euXzWSim-klN{1ruD6aW-xBr5ydYeyS@qVR1DzK-dU0B8Y^0I)ik z9v@fx#)2Ff_x?_xcL#V1q{!NlIgyut&GFjuj*t4aYTgHc5eBX6WOj$G46wH&S?Svd zJ(xA7e=C^XWnM9umqTM%L3j9ud>{3E2>==dJ9^|ga0q~37XEW*u=9^Y*<)c8?ON?J rvUr{8>x{LRWq=vZ=bHTy_ydIq(SQrq8YBP!002ovPDHLkV1fVuCht*g literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_26.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_26.png new file mode 100644 index 0000000000000000000000000000000000000000..66e30789820f80ede3a0ea937f2bc68760263e9f GIT binary patch literal 1299 zcmV+u1?>8XP)Px(%Sl8*RCr$Po#B$(APj_?_kZYgoT+ap2ii{pV~FX$8v_!%E3r7a>+Ah~zyIui zk450M1@KtzKO5fycsBoA01xMX3t$Uio4`kFV0(Z^)4xsN!|C4w*aFz^0iLaa?E#+6 z|Gox3oc}F=Er9(V;L#db+5@~^uPS+4*G$*T34oHGj@x3Wi(wf7oP=6r9%D++$y`=1 z-#2Or0n~^s(P54Ctn+0Oa);Rfm8h0uISnc1sP8}YA5}vpIEr1bL z7|6S(*tAvGmLUSL&dQz;SV9Fb6o*9eTdo5T0o2@nNdOe=-W{OSiH=+%Bp&l9k;@+e zH6kcM)_f&~C^@BdYo4Vg2F0Lc0+sk~@c^|pSp$iXo6aR|I3M3aP!9arkxFJ&iLqA6 zib!joHFvlGl$onll?S*8nUN~9Isg%&XHTtTtHm|)_W~3s))^l?<0x=j02Haef15Y8T{G&(Yk^)GpD+k6ydaZI5$+hRDJA3pVAI;&MN8>IlfPACgF5(4> z5K`(Ql!#IVpW=>mCmMHo0T5!WiuJZuE8da{jN89nsb)ODky@t&SmH9$?f}Pvx2d-YX|1fM}JIy`*D2w<~!Bl#-6#0(t@M1w@Uf z)k4;(6#+GGHD5rgJw00Ko}>UuPPGIC@c^?5AO}5~XO6+BidVuk#iQN>sKrz8dVMfM zV_r3mR!<)P6u@1?r&sHe0WKEqHw&Of1g&uIjUfkpv=F8h00q2vMb`-QRJd0J=^jQ4 zzjf&&cDAY+_Ik9pTcL>=RX4Ik)Q(D zLxg86&Ao-hmbo;)JH}o9rOPx(#Ysd#RCr$Po7-~aAPhuj{{N#V<*9OtB9gi#Fqfcx+cj81ebB|5yg#4M=aqks zjleqrI9B`b!4rV9^-ll}*FOPB0I~@@nt|*Aj+Q^0z{BNF01|-w4sbRD*#n%de~y8N z>z@E50Qnu@Xa=_K0p9PoDqeEUalIV?tmx&sgh7X48vt%WEitb((sO1ncVGVBsx1J} zgq7s5)_UIa|D={rv^)S2(Gmh4-=zVFh<_h_6$q@Jg_kM567@gJ0?;D<_eY(Set-ca z9`3(86M$eek$}u=SI7dh8c^2mk{JMX^QHJ`DzHF7>5+j9tzm^g@9{oo?QR2r1x0l9 z>yc%UFsu;hJ>KW6-Q@s~L_tE8&)I{I>IJ<1c!k#1vv#)wK>EUW1K_Fo%$^=%utM~( zRlb9@yF38W?Us6gT735JD7&-@!wS)Ar{2%nT?zmz{afSI)dR4C*Qq}N7|{w7cx%L# zsakst1AtX4dxpRQl?a2lB;wz88Gr#m>-Hr8*kJe008%Epe2Iv7?PG=9z65AOumV~8 z5e!jrr1xq)ON$LQ22vA<;G5C|XlYUl5+iQ8mr29*`WAuez~323YE}`9+D%46TKlZM z!vR3eoOY``fs2S4xn))cU?B9IsdcS-xkmn801Ap#4O{}iMk=@tiSJEuK{wQG zPXiFiN@Wx1jZYo#sTqI~pFLnyD8`4VNKy~bB3`7Ki}ntW_W(@bZ|7M7SO`kn=-uo) zQReAA2Hqec|Dz>xO90B-RR_i+x^{Ds$l7yhW{=MCt-i5#w*}x15L@d!qO|~^0U5c$ z_nt_0ItGBKSkcsK*^`7~g^*JC5CE>Plr2Fl_gd*#vr&FD@!pcC-%(MJz1{#=v1NQ= z8tdzKtEK@kTKuPkFUygq1X%uzDs8_2Sf62Q+Sm^^=&RodK^WiBZDOB$GeGNvWc3DY zY#{*bJZl|Z3Si6s;GsHfJ%6&ycma@Ni=Zg`g8Q7>g>;pG*1&iGcuM?wbke|MZ?tz^ z1b`X>uT4$lt{H%F$^!sHCSwz=k-iB;Mk-_{+5<4aBHO5Rql-vS z03Z_C+t@phtX-5%(D`W_;eA<4!egMokwtyyTgS7wv002ovPDHLkV1fVu DklS5m literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_28.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_28.png new file mode 100644 index 0000000000000000000000000000000000000000..897dcb051e1a5119d1cdea6dbc8ff89c55b1797f GIT binary patch literal 1302 zcmV+x1?l>UP)Px(&q+iYj;ZpK;3)|KAH+Fq9A!>NQTz1K%jlR&sn>-A%I1S z=;${>We_kd5NIFobJp&51du?f|JK&j7x>b1HoIq&fCa(}Fj~*ry%hlpSW;uv5}^%H z3v|5~OBNJ#?Ox*b_n9_&>~2qh7AT1X68IY6?G%s-u$r1i8d>u1&Hg07{}0L#yxS;% zRs&TmjbI<^_*e!+ARoxGSeyn(p(<}Tbr9bS&EuUbk@-Tqx~)?027cA z0H8o1;G*wK_Y3u2p0oX231GlyvpHI?86P|#F+_?28GTQhBv4$ZH-SvxQKKTsKLeI0 zkWpeG-`H^s?c-G@*6u9{@Ct@?Z{Q_V*^eO=(|OnXus~=%PUI|Be7@|=o&shlfF^ZF z?zHAF@*dkr_7I@HPw9y@Jb>JSkJyv8DW92NRjKL9~`KXOEVEHd{Tm>b06W3{wEcF%JP4#~7Pvjg%#kjvCqNB|xhLtUW7^ z+be+QkfYbK!SUMl_5khvNd7(j4131qW8lmh906>k8X*FB0rsA!E^}@Ite65k;7h^s zyzdDf8!WUZB7oHNX?uFcK&{j<+0P7rT0#Q&A$yN}cmk7njRZ=hY1d~?qp(XKZBGltGN~q5nS13Tw zoNA3&tUC3sV@cZE)%W_Ty-z8?2;0TafTM?u z1U_mDy~p~pTK+A+DFvAMSpaGPE$7*QBjC_;j~%pjC-BR;)iM979smKNjh4)N-ZcnN zH10{#>-AyP`mZuQk^qq=M0;m-7I{=^wQn=Y@%~DfHv>EkQe^MQJJBF{ZF$#6{aW?j zhX5lCTGz>JhOG>+cOqHo+Xy_EJ*Iytl&$4nFqp%kH7uZ4_=bER^?V5dG!k4fBiDgL z1o&m(KaK0`e;3dOy#Adn3!~Vs^)4fu*O|V~*n61*%y2&U?2o`7UF+9?6!>&c00000 MNkvXXu0mjf090L8RR910 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_29.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_29.png new file mode 100644 index 0000000000000000000000000000000000000000..0aa3c865ac7dee6acbe7532a58a17e50aeeb98b2 GIT binary patch literal 1302 zcmV+x1?l>UP)Px(&`Cr=RCr$Poa=7vAPj`N@Bh$SWhE*^ZO_;^glv3%ZU`99=L;x3ulM`?{;7YD z^}wqDc&zrHjTZpV*1rIFxc&t|0Z>igqZz0k;L-9|6ZmlX3xEQk?g5_7K=lC6*1wK{ z57)l{C;;jn;L!}+x(9f@UaEM-HOKYs0ANM$#}y1Z47UNG32KRXjFFx*?{e|-eWPvx zfF`UYhc(tS&-Y0!?`V4fBBB)pJic245D~v^eHRF$%AKoX_@ZLFz>_|kJWy3dAy6~Zep8qcnKD*&2UQe!oOFgj=y zx}S@M1vy>2mpFbubB&pGw+CPpN`gQVUlV*h1ylxDjiwPsR{Hlw|5X5F9s6cwxm^j+ z43;v$$PkkOH14u9fCQem1hC>+C5fG5n-Y)>09&um1fb6b-iV&Hi(Pvw0JP&=AK~5x zkV(m2aZuznd)fO6sbOuuMQxK#OJcKl#aF?aDB5PVj$NbNEBb7-^^UcA6*eoKF;4aE zY5**8EC4)Ci9}5n1O))-^1WBHnlNmW!-8`*03ImW`i)8vQ-YTD0N%9B3YRr58-T;E zsF2b3#!|%e5laAI#AlC6QK1+cJWZ z_#UJJ!1#1qy3j$QBEmhQN1$TrZWaJc4YxAU*Jc2%)-f9%9TtsH$?;MaiO%2870FKQ zy0u=36nlL1K-<@Z#mz@EY zlz=M%SS>yq6%0ffCBU+0Y@WTavU~l@0G?Yo>(1V)&m+;Sku!qKG@9OZy%}Kkgk+Tl z*0&IVK4n@>mbH1wk?0E^uk**#f%d*Pl00^J0g!x)py>O8W}6m}OjAq&z&ODJz**um z*=IKZMhF;RGg&P)129H;0AR>u)o0Lkd+DIm+yUWcIZY_u@*pbQ|D zC23yIF;FXYOsRzcNS&%?FuDlZ6@c*s5w&y70IMP%foYbCb`ccaG}8bvB%)2Md>+jR z4S=hJ?@w2Bruk+7G$B~ny(fmz>8nB5768`a*-@?u=!tMI1k7DT_CC>kR0%+nFwao~ zq|GtoWXmozFAD;i2iee`uL=Y<{eSv9R01+i0nEl(F&Gf7zQpRd(Yc!0$JkJM>^*BN zl>yM^5yj{X%x-Qb+Qv1mnrn6KXMC&a3o`&Sc{?gv$3SoNc*dg?oG}*z(~iQAzWJvS zDghn<&{$T8Y)7)|B9PGj?)d!kNf<*n+8mn0TjSAu$R4R1p8+A$9*^y3kNpE6V~cHYBsR;&f0dx`^y1nn`9#V%%~gt zjI0Ls3K3`4ybOR51ZfjYmYCVR)F-sxR<513hsZpa0}wH~-pTX8FT1qVfFaO6EC2ui M07*qoM6N<$g5zdby#N3J literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_3.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_3.png new file mode 100644 index 0000000000000000000000000000000000000000..7d3365ee4d4e78bbfa85d5cc9eefa9e110fe106f GIT binary patch literal 1233 zcmV;?1TOoDP)Px(iAh93RCr$Pob7TOAq++9_kU=2>?s+B0{4jT4eGy+wMgh5gtT$;dcWWAKmPC9 z47?nGYqkGw+yS^-e+S@l{T%=Yz?;CU8Soz9YWckhyj*?g6+O0d7|Jj_27n-FP0Wu??sM`iJ1@V_>Jb2x zgw4rOv$d@A`=rkA=y(8HL~{r@zDEPlB7WHVDG=0p7A{k}rRsl<1)xX#?^m4){Qv=^ zJ>CC5=>RllQwd0xJ)sKhYCx&qBP)RF=40{IR8Rv&l}7@ZvY|q#b*!ILzmEZ+21V=W zH=@cYVW<#l9qZ@R?{NU+L{UP;pVN)6>IJOvScNXxQ@@V`AoqqZ0ARWE$ulFwphArB z6|X`49uGk7bmw}2RDAk>Rb6_8p+fZfNuQ^Fj{=}p{%hmZrw5>dr={NkSkVdtyi3H! zrMk2X0ibqQ&P)M4i6BKL6YBSv3P1oTb^AF0Xtulp%*3CnPLj3!R zy8@L$fc-udfRyysa!M0L5?>O3_VuPFr|Nemo(=#Jrerhf)W-}i;6$3@~Ze72}zW9=DCjhI(A6f28Ww%rTdc+&? ztOTI;8a54pzObpCf)(QD{Hs#jlk8drP=2>x0rbn2miP4p0F@Z4Agc6^_7}&O_7oOT zEw&^lbiJP*pf@qOhN>h+&yR1(=HoGuR4=-wva98Q<3MR0TL){1Fu)U+k<)c`b23oCvWpaAGevO1BZx0x9qJ&B|uQ5%a-*#wT50#t%6b1O|kSaXzbE2O8K zel-b50O;sKMj#@dHPJm4fW#9|2n41yUlt!TZHv>}iBrId5RjNoI`y<4Kox+HNjAUh z1W~`Lnvgs^&F1@yA%t$VF|~%J^S0dIDy~ueh1ws9<3yVu07e9-PMQS*i!F`4N5r0I zX?#2Emi}s(^)${%x@APN#=AqnUZ2YWh`VYzqKH5C*t7YxXGyD<6_lP>c0RiefE0vW v64aG4GI0usk-kn^YdZiZCF90e{sOgNdVmw*)Exi-002ovPDHLkV1fVuECoI0 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_30.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_30.png new file mode 100644 index 0000000000000000000000000000000000000000..1142b205b5a36cee02a31d543800a4089632551c GIT binary patch literal 1293 zcmV+o1@iidP)Px(#Ysd#RCr$PThVeOAq>0x|3~L$Go`~&+mel8*}%PYF3WUzYLW@z_37|dA!frb(bT61WNr`d!}CCOV8Qr9!&xk2rs~BJiG3r2vES1j8#j7 zHbE`W^;|4jP|~$~iR<%`YxJzUJONsuBoavAYk;?1z)^tN)HKS-l7DaY-$j5CZC?bU z#~lG$z>)&A9AXqe<6X7_NW|+o0$3oWBac~knIj;Z0PJ}^k^r?DM5?&w(u!SsDFSHi zTwUQ_1IS2n4>+3Hl)4p`l=V0kNKFCC=h_}`A%F&o8syKlt|qUo)OkVAQ~}GYL;+jeqk0?<+gzV6Ku)iaKbO_FVj^yLGxoyz9JCfX+R*!v=bkm831rctBy}%#e01u9d1@%g3K% zInCFujrW1pM+&pnpG8^ffVz@OKr2*~W$0FjZ!{sM5@7UZ&iwui{0Bv>-GFcvvzY(@002ovPDHLkV1fVu DA$U-1 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_31.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_31.png new file mode 100644 index 0000000000000000000000000000000000000000..f1c13be32f10d04ede633eb7904276d265c3aac6 GIT binary patch literal 1266 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_36.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_36.png new file mode 100644 index 0000000000000000000000000000000000000000..e6b6569239eeef1dba1309ba297fc43a817c078c GIT binary patch literal 1302 zcmV+x1?l>UP)Px(&q+iZ_QmwyAW0odOG?#;mN0q(8; z9s_T#{{~8 zs!IT13EPvyTdVBzeNy*Nv^)SA(KZAe-=zV_h+mGr3Itxy!ez>@jQXEt0XQQ5&#yWe z{QvrUDO?C_NI$qBT^AXdm@CwR;%= zJSeiG--s*|2}6a5_EDcxyUPL46Q%yUwx+(s?>(ot`)mlP5URk@dTRGl02Hx$ja3Um z8=zL`dM^q~6!g-3l67=V*0-+#pcSeIK~H>3@OBCqodJ4wUp6yBQW32WV#MxJ8Nf^b z>g`_vfI3rU03hS6l}V`WWdN{f?`44Y&?EzRfy#5&Jb1Eb148UQA{WQeb%)athU)vMIV_3tX?Gp z8Li`&DgmB29sna~_x2bCLA}or0o##M1&G?7+5<#4(<=eJxt@$U6r3{vs6|u-Y{yl4 zj`}?kpJ@O@;uB}pASmls1mJ7F0}$~k09m1k57}E7R&2>tIRM zWX~w8i~^vIo9Y|Q9;cUpGXU^vt0KC0b0eX>&btg8Cj2RI9W_Mm46iU83o zPe%YKAZ5xPwR_nNKm>`_WCE*>l2iz<%w_zfc3=OrX4wA|*w!g>`|I!KYMqM&ww}}A zsQZu30JVKJHuQ`i1z@)LG$^X@bvd#wf%T2UntrA=fh7YdU*YHn8!rRQ5Wi<%y-5lT zY6c(~((7%!(m-K10E&F(b_1eD*;eXGbIyH|07L?0uTk=nxn15 zoF#rc)>+_@^JLbj8NixkRNHEK`7cgU382noG63B>yRLUjG95Lwp9P@J1k|3@Kh*n0 z84r~J;*bJBJwqd|xOi0b>1v()b@4R)9T zXeUiFfFeG%J0fVKK!^b7HGP)<${1*sI(t>0x1AJLC&n*Z8JvwY{hGR zoOR=)OW>9Y0zhPd01yQ=f@}r=M?ok)eoFXyx{Bf{Wq>mQpy_ue09g>)pw9+jSpbgA zskInU79pT!)=)@$B_U&P^gE~oj4(>RgJ**IiQC_cN5&7Dibur1&0Is8K-!Iy4mf+r zOyIM&qW7pTv(vxlH_8Cyqk>1&Spd+0N5VzV6+6+|mBcUSHhAw_8Z<#6vJ>ZDj{0ZMioR91=1po_zPt3@5 zP@)_C&BB*BOaq`mPz#_mUWDe%8n2;S!N1Izly{~Apg6YI&WXTZjO6Knp}2Zl00000 MNkvXXu0mjf0J;8GoB#j- literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_37.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_37.png new file mode 100644 index 0000000000000000000000000000000000000000..dfde848c2a3b60fee042db787574f47e027cead2 GIT binary patch literal 1284 zcmV+f1^fDmP)Px(yh%hsRCr$PoZFJ)APht&|NqgMN-CbB7>RBPf#q2HwgGcdA6>xSeLSDf=TH5) ztpy$hz-_gEZ@d7wxBdmd&Gjz;3Vfl6o5#;%N2nmaYg_z#`hX)rrKtXo1P-BjbJm;13kMn0wB-SZuGo2eHLhjduopR~2t-gXj{qwGXe;D?gBH!YJTyT{kRtvU^W-Qu%GK|+rTFAD&p_3TP*e*jo+;n)kCQ31F^d})5GCt6^z z)&R2w@+gI|utNaaihC5MN1K<7D80h97#kgbMkyy^9%&3K0FrGH6zs<)p#U%jvjDJ4 z{JtR5z+?8~>Ei*Qhrl!M%vCuR0hmQu0N7RVNp}L#m4DPJ0C~POqNR@j3{h|t?$m&c0=$&Wj=d z+TRu1-=Bmrbfe9oF_=8NsxY+9EuNRb|2l^3lePU1fQSVmPqPSN>C3JiL5d;(+8?`) zO#POB6xwgrBJ7M{nvt!?GRt~13Cpf!I{^JL37tMxd0k&;WV^*=1)S}78UW0Bd6oED uj&Fb8IrprHGtZw6z|7+7*?9~60&ybafO~PbbN~PV07*qoM6N<$f&c*QZ8RYO literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_38.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_38.png new file mode 100644 index 0000000000000000000000000000000000000000..873f7650d12ed4f92a6debba1da01c0fb440daea GIT binary patch literal 1308 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 Ju0mjf004gtS-AiJ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_4.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_4.png new file mode 100644 index 0000000000000000000000000000000000000000..b5dd2c1b9518e40bb0598a2b032b9d0b09d70dff GIT binary patch literal 1251 zcmV<91RVQ`P)Px(n@L1LRCr$Po!!#gAPhvC_kZYgoDLa=B6e4n0mtfP%#Y9>3CYQPeZAlB_n-Xt zSP#4sfX8b8Id}r_Z2c2}hwGmJBmh|iKAM570Uj-X7J(0!KLJPp@;$(_8OR#o+4^T2 z_;CFbfCM1l13a36Th{=u*UJ`v(`3xG8Q`HRvCbG2;&H^P>c=C{>brd zHbFwPc17C21l8qN0OR=LRQB*J1XhvQ6OmmL0niGL0I1iCvY|luvjB_^sE8izd`o;r zi0C?m^}{VQKr7st`&5ZgCi+VjQ&T%D7_y-}XZOE70IWllJ zhWNexkq~S|F;|Krsxp?bkzMcj()e=)j2y!()@xxH0HOs!sW*B*)JZ5=XGt5#0KgL0 zI@c1T_x(Myhcafgx3Z2hb`%6xf@ftT6xg5Ea|Hm5VT$9evAxU{8D;IhX8=H%%Q_>v zx)FFw06<1Xh(B6GNy~~YX8`y_KURzKme!$tLG`> zl>!@P09Hh%2t+it1y$<+WxnFPC=d_;ZIm++T#Kq+b<9-@0bq@e1fVC#m3Mb>>{$?6 z8)rp4f_(_utRfIaFKRVs0B{zBV&f{|>+TBq^Ly6S0I*Rv696v=tMKA1f@dna~9HGTjmW?ZTwOpZHtrbVUWh(F2~#6LDUdvCd*-+u0REa78~xt> zSI_$b5MhW?A6Wa%7N4nE{uBT#2v+pS zN=x_4!hf^c%>Oj1EuNIZizQkeH!A3MuunaYI(}LPPz}rRb3E`D#>k$4X8caj00000 NNkvXXu0mjf008siI9LDx literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_40.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_40.png new file mode 100644 index 0000000000000000000000000000000000000000..c6603542e14061c089d5d01e81a18fa03e515475 GIT binary patch literal 1293 zcmV+o1@iidP)Px(#z{m$RCr$Pob7VsAPhw}@Bh&0IJ0yZinvDtV;r;pw#FcIk3Jx2U+?$({crz! ztOs5jfX8b8+4u(F+4^q)9@q*!KXBX5iL6!0Yug#oJsnu5Skb6+IufVX$Gi4FE~dmYBzw+H>+Qik zbqfG2VQX@<#(L)YKB?DIl(@$)m+=vjAp07{{15Y)uC1TRkkI|Ix{vk^wE^zV)Ss{j~r>>DA; z?Mi@Ua4iFr43P|AaW^^x)WGwU04kg+M|7TLO2B9Ul!MN?5V9IpPgIY9GS5WKqw*?W z3bi&J%>cQ;3I>ljYA&l((KV=W#13*_a!CfT>ZIi)t7EsEMe1GL@8Yyc>fUt0o{_?}MLBP<;| z8h~Tnh?q4$QFzLXmi~Wv6G+6T07Qi%Hbfj<0@iyBOyH!WE%i&y4)X5lO(1bRAvIF3 zJrNiM>ns4udA)Eha}RI^0C{>X9pe!_0#&P|)SOO*up-zS9QkSFYcqgV>&S+qsgm>( zwJg#DK!Q7xo#?u^ECECeGA80!l>w}b6iMMz060FWB+epZTItw&zqi>TcAXjZs*KP` zysQLV3BYXeqhe6Vyd|JzPuV*~G6*S&$1m4?GJxk6j$YWPTfZ5C^mfx!{M49wNXsuQv20uXD9%~39Zf7qHuHrrfO*P zI7RlJ#zX)pA@Ho%+SAeiklGY+L<@qRj+)vM1)v84Rk~HiLnVMXz?%WcoM(-p(cxXc z2Y?6!%{%`8e>(vK;N5I(Jm~?HK_l#-bBqQ-0ifzTwOftzoUUXBs2LqS1$e}#>ndlE z=c7PS0Mwd3+RTz@&r2)alLUYoO-cX-pl1BpO=G@KYGH8*dqpPWscj`V37**?EDM0uGkBtG zMhQ^$*qec717O*YN&txnHHKV+dZQ}UX0P*=t&!&u2x%&g*rR|Y0VbP3+KrPAIJ(J* z+Xy|#9H)ONlt#2y46->jh6;LxZOZmh&!+%jL2$(xId_}_;G2a{ zewYS8d7M<|(@})0d0Gt}En8}hm7h-qU}Z-<)wUn(uHb;?_rrGp0000Px(uSrBfRCr$PoKceFAPhv;_dj%|oLZ(RMxt9FFc|G;OhBj~jlh|`KA+F$U;H~Z z16KfWtoGlD2LNa59{?P#e*h2wL=kv215pDUEq@e&hsz%T1ORala5e)`1DvgY90L#6 zKL7{-;vV2=2DYvNuIp083$8h?w*!C`J#QB<=rC*pKoYbj=G{hm&U}|!FYjx$1pu0` zk{q?RmVVwRwLGKc0ca5|AmH&`8h{q@)6`dipjNZ+GPSov_0O^Z^oalcsgqI_np3d=u)?AFNyV~Q zE3ERPP)&$d2PiFd)_p*2t3slUe69ibhy!Sak{aayYh7tyPf3&ZT~>5Y7YG0V#!b+0u3d0CybhajaHIvhGp#!Q^xb0F3xduqmNE31RDk7Ko(yjt77hp9R1IfyGW& zbAS>+EA%XQ!yRA*05xm1i0)kkN=avhCkI)LRCj=iv{6@W9um&P9nn$-b%3*_1s#=;H($SZCHie(FsUg5T^ zYE(V<9;YlZSpkr8i=bd%HM;`9B%wBWNd)f#j0IsO2hdJ3OukWlurvTQC$Ubx)?T_3 z7ztjB1N1_Gwr&JGwFNMuSRH`rb5t94by&ym0ifItn6Yc(vUB_efEf_AVQK(GBpSo> zIh!PQOauVh-`R0cJ4=BL9e}N*83$NN{5*+XARqwPf?9F|X0_*AD?29$02}nq0F?W1 zE2w1-X03R}5wtcG0BC>&No@;gO)4|CcN=!~l;b7J};mw zan0WQ8FD>DiU`aNL&#Nz{pB05K^YiDV<_Q22gI$f@iDbt`Zi42wRIm7ifMcKdV0f1;8UfB$x#POItSf9ua%K%f|OayWzhImWsvu zGX#rPSM(-{y&)^Bc_!Z8*q-?QbO0=g9l7etI^!+Z*j9)h1E2&!3Vi0OLqJ#)yG#4W f0oWx`Z;kT@=X>CQab4k}00000NkvXXu0mjf!Tc$Q literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_42.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_42.png new file mode 100644 index 0000000000000000000000000000000000000000..3e3415af66fd99906d2d0fb24b2ffce515b3b7bd GIT binary patch literal 1314 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_44.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_44.png new file mode 100644 index 0000000000000000000000000000000000000000..4b3e29fe106ddc62c21a8630151619a38f40b693 GIT binary patch literal 1305 zcmV+!1?KvRP)Px((n&-?RCr$Pn^|(>FbqV;_dm2#aurh?hCp|Nizt)%6&EZIo8-LS@Av!P`P zx&IFU2Y~Y)U~2{*-2=Q{FVlI4YmV#30l+#vUw2@zVR#Gx$)TPzuQj#j%w8^DzHikd z0I(cebBDK9Ip_OaUDs%N03uF15b*de4M4>CN%d7A@Ol>BP4She|5+A*7UzF{>SXi- z3?OlH|J`{22wD?4kU4gREO1u?%HF$V20-0>?tC;Ac%Y#4$UuhHu#Q0IsL$DZ9|M2~ zMRfG*k!4E9^iKIvNFNJ8%_+!H`EyqBQN2JZt13?p)Qr8$WdH;~9$_gu(jKMeGkesu zTQ;9HS=BQU{MLEw-OB?|i|$$vP>Z}geOk|>2KEep-CLG|NGfNYXuS|)dZncR@S@+V zt;yhw!^_k^3jj6pd#|?uTm}HE-sP#Az*Yn!%nWfQ@1=Yoe?+B?B08lDA+E6I+ ztwyp1fbwG(Bg1w8C`LgYd&^mm1>kyz(yF)>eN6q&znrOcE-H64D$#wlG7`P3w4G`j z_TKA%t8N2ew)0t|AV*uzRRBDBrUT%;hGjZZPx+d`W;nl=fz(J+V2A?1+~Iu)tLwANM06_JynNkY@V1@1pK+Pc|Yj>G~6;R7Mp6{I%@hAw~QjuN+MU~EtSDt}J6{`tS zXI^9g^?5WSBuMXad_G;Vwx==xV?dMvn!8o-@7|1z>KYY<(&)26SQdbmIn~QBszame zRS=?GeN;ZQk17G^5azj60%~6g?!B=JIWhViIh4&v zD>}ZE{ZEDTfos@mQ z04ST(NLP%etZkK99q(q!)~Xx3fB4VBFF_c?sm@>(^wu$QCHcJuxQ8rvoiPOf3j$?c z9bq5@z#UMI(9;cqng1SMHcv|9HOeqvL$`u_qx4SaPRjtJ6*=d}Ebs$UQrduY#JR@+ P0000Px(zez+vRCr$Po7K@=|2DYvNUayxbUUAKFy&V9o=v_-jN!{Pk@&H6cD+qXemj)mr{-0S1t` zyZ@hA00g~>1Y};jLKJwa0A<%*G6JA#z7-#J1r{hMJTj1>F{}{iJ>KW+y4wI?K@n~J zGNOzWCOhOuA>9^$o+wDD_Bp%pyJ`VxtJj_us2OvYivS3K_Jm!h=W3V&pLsvx%KVQafK@M`xz_W{Hc)mI9=q5U za(WGbiaDmDMUIG`w#YmbiC7`fp1jZ5b*BPgi7!vFtk!#u*5-JfHFDAl0Mgq?{7f-P zll~c&RSagP0QMAHQ4>YADG`7Xp9LT)6k`K&76VoW0J4>g*u}zgI{=L1FUPVQWg+m~ zq_OWr5M%)$*I|9rrcUNiZ3)1YvwIu#h~7Fm1AyMCV^5-1<9H9pF#yPo-t8!K^0h*8 z1OWN5i?QK206b1X75m7$wgupGhcdcx?esDEzx{IN&2v$^qo)$B*GnVOwWRO7zG2t> z{BPA`0L&JjbqW$%d+r6mvS&H~)-^0sky@MYIc$dby$IwzNgf!Y05EG>uP=IUrz`%$-(3IIc>Cj#6hzHIBX$iwU>GR7ML zv`)smw)GVh0Z&B$#vl&>7wyb@N5=F{Nk+e)RRb`vu3>-3TF*&rYQP<|iTOFP$7V8ax^R?mUjA#o@W-RM0RyZaiu(LVDhZ2pfiNasxQ#~kbhQv{tG~cfk-e91fH?1 z?IR+N%w_F+V%+UN3zwVn2z!Uqy=|Gt-X{bcU2{4BvQ0wW2Z}0D2go|(TduLa{&)-k x2|~~CyTtFUg@Eunc9-#w1F(yuzSYjZsgu!w5AD;=00000NkvXXu0mjf003m!P3Qms literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_5.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_5.png new file mode 100644 index 0000000000000000000000000000000000000000..544848d81ee566fd5006a15327b63ae0fbffe31f GIT binary patch literal 1269 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_8.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_8.png new file mode 100644 index 0000000000000000000000000000000000000000..0e018b6df2060d0ee0ac5e3278d0eb96490b3491 GIT binary patch literal 1284 zcmV+f1^fDmP)Px(yh%hsRCr$Pob7VsAPhw}@Bh%*NoMG<6m*Zohw+;J>ll#GJxIby`+C3M??30~ zu@QJ303NITcjE_uXX}3ec)0!tfCIp30w2x5=>Z-s|7ijrF8=}G0C3&|Jez^j13X** za}9jB{s({qzI#`Wz0prYsF4h(G=ZUaCP)DrVEruLl7W#{Gp z8+8i+TEf=ku*Q1U`TwM@?`V4fBBC7#IKEp05D`CZeHRF<(Grss@zW-7*8HZax=Z-3lyFRC*+ksWDUtwT|~WwR;-? zEGVK^zY$qR2}6ZY>v*42yW0U!6GaJ?Kc^dC)eCt2@e17A zphAqWm9IhVZVy21bk}--QhfTm$}YXaP$7Ell=G?GtpKpnzjdAZ^Z-=wGW8z-RHkGx4V?7;o;VK&^3B=}7L) z3;{ifj!>bY)b1r2pe0I62qJDi*DE@O3`M>eM1_c~ZOx%}mjj@hx!R_PptYNUC#bU^ zph9?jC1a`Gr2r@)tr{P_=8#~0#vA}>q=Ms=_@8BG)?(%MNC3!Qdovc<4~glL3_t@y z0Eh}jd?5Bo-OU59QZez%y8c?%+bsa_Y>)~-A;@j3wdtA)fL1fA?5h)9D}nTrP;=bu zzYUxLK&sg_!abtrK^+Z1x$ltxkUdiS(@VfC0FrogqrIt;oX{L1&%Vjpytg#>li>0@a(DRIibqTQASZP2=;oRhI#f-ros?FT0h9x+ zj#`xfvTo0qS{+oWv)5h(01H^OFv3JP%&00V1cX*07bQ)RRG|-?ed35E1}a z37_AtR6KulyBYu*>=hZHgn{~2W{?E@Y7n*spy!@yWtg5*?wRWap$3G<-srKY1dK3B z?!b{?UUB+crNLW^c7I95dEJR%>G(<8Swn~ u9Zr4>&GJS7cXatfcPx(t4TybRCr$Po9%MkFbGAn@Bh%7@y-03e3IM>7yJz@z1lA@Jey2LJ&;d&Tr;k32LKg4wF?;PFx&=!Q&3CH!>06{yvxqZ_gUQn zfSRx+IV@YxI^QRCc}Lp=&>~tu!13K0fEMx3t?vSXHM4M;+AY=mvn>EU;(vbBsWJ}` zK-$y&|C<3oV>Xq5WZ506z*Pe%^}A&SP}6)WzPc4yps4alAX7F}2(^y)IrV!R04yk4 zuYMz{j1q5Xz4uH}Zz7qgX&nNGU5Q7Ra z!dJQm^}9U)rPE!S0dn!_-&J+#6^07Y>nDGo`rQfutNdHnsbdD9g6E|_09Y{!1H5a* zrlq>J3<1FEl`~U7Pa;T>%Y^#frUDQEYSVrR0Gh3+05kEYDi~kxs6aJ4t96{=%}fD3 ziH=kuQ|kAU3Q!ZJCIk^Teb*~Gg$!jrGonJYtZltR{VoSU4RfVU5kX5g15Z+CK|qD@ z#7b+Jn*RRKmOq*W*q!w3NL z#9OzU9Lf>?)9)Mvvgs{=B;GrXQwUPuD!Ft`PbIm+e-40x;FbWKJ-f6yPtsE{M*xtX z_8ffr-_{CL1z-sP3xeAM;N55~akN*G#HSE>Dj!+9*QR>Kp8eawosjpQL z&%SBAJ$6+9j^Vp!;q=|y2}#2F8CUhHQ88NWqt}O30k&-cR|2qF{E_9p1WR5~=sZI0 zJb=KUEV-_f%BfWWR)}BnKeNqPIZ%FQv?EegB>-v3RG@fTK+pf+StmiY z9VLY4l;yT90JK&mb;9Yo2mq24PX(AIem;~MNW78n5UW=VNnJ%oAp zFd75}fSOOd@mpZfy~zqdMIB-+cbFiw^=l;{%mAWSWLI2GF)P$fgdT$JQ z(pQ79EdVs(wAxU+nt+}Y?uDRds;B$%+MyQUUDR_6PyqBK*@|1OOF>ZUs2I-YDi0$x zpfek80V8e!WaCr}Pm-u`#5Y|lA1FQX&ay=nzRC_iPvvLV=YIf<2v8;11A)hu#@-`h&$~3fE9|EKRB&lnJpN6=a-Ljb=Sdv> zLY7bSqvPEkWuH1b1 c{`vU@je~=L>E+pV00000NkvXXu0mjf0EU!KmjD0& literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_3d_printing_128x64/meta.txt b/assets/dolphin/external/L1_3d_printing_128x64/meta.txt new file mode 100644 index 000000000..2fa7c72d2 --- /dev/null +++ b/assets/dolphin/external/L1_3d_printing_128x64/meta.txt @@ -0,0 +1,14 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 46 +Active frames: 0 +Frames order: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 +Active cycles: 0 +Frame rate: 2 +Duration: 360 +Active cooldown: 0 + +Bubble slots: 0 diff --git a/assets/dolphin/external/L1_Wardriving_128x64/frame_0.png b/assets/dolphin/external/L1_Wardriving_128x64/frame_0.png new file mode 100644 index 0000000000000000000000000000000000000000..db2fc0b46cfd5d94d463d6917061cae41ac2d162 GIT binary patch literal 1536 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 To!}pZ00000NkvXXu0mjf)g#sv literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Wardriving_128x64/frame_2.png b/assets/dolphin/external/L1_Wardriving_128x64/frame_2.png new file mode 100644 index 0000000000000000000000000000000000000000..8a33d5543a3daa50a7c85ba7cf1221571d4edd45 GIT binary patch literal 1575 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Wardriving_128x64/frame_4.png b/assets/dolphin/external/L1_Wardriving_128x64/frame_4.png new file mode 100644 index 0000000000000000000000000000000000000000..15678ed0754441ba6fce84c077c64542c5449dc4 GIT binary patch literal 1560 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Wardriving_128x64/frame_7.png b/assets/dolphin/external/L1_Wardriving_128x64/frame_7.png new file mode 100644 index 0000000000000000000000000000000000000000..6431f94353bc4c836b658d1cacea05febab2c1bf GIT binary patch literal 1539 zcmV+e2K@PnP)Px)yGcYrRCr$PT)?w3_r`w_;O6)*0xSZoBJkE6ST(?{;a^4I&Ea1JSOi%20Qcs=ssS#B z|F&(f9cOEIZ`>ll#R%}az1y}l0s1&io*@E!eSJaqJsuBevkY<`IGg|s@cKxL2sw}= zYfS)#5K~fs1$+dowqGR3lYjvm5g@7?=oBylrX0ISkRw3{ua9cQA47_6kBUZ$00_WL zN>~KQ5x}#SB=8gffls{-_P|G^@HSo@H^*(iYnD*j$#yYR01K@CyBU!@bFSY7Eq5BX zup_~T3LpX66RbVw=3IFVE2|#R_X3RU%hS3FFjDko-MI*m_1I5)j~r-biU2d@9&MyO zZ$$XiTFic~WHXfjM;2;}1bNz3!FOwbBMGpSAV&ZVejA;6QDmloGvwr@1egfA;L8?( zPyvq2$BO_l0W7em5a4L}m&aZtut_kZ|9@W<>o0<&)6W3F8TNq1_;VH>_{~o}_Yc(o zqXfvlF+{GrI|1~azg6Tl8)snuT3 z(Mz9Bio*$D$$xjsNJnvZ0(iul-UeCMtd5%j((AJ1zq4n}hvq>=+-_~4SFEAR{*weidqK{vJl~4| zmI6fg`tq4Yk|70PE`Tg)_f*1Cjcg>dU}xvekYF$Hqssn$Nn zV73)xi0I8fQfOLp%HBFdnyl@tb;<&d*%HRQDZe-xPLJTuptN`MGVivY8np8=n(5i)Z|fUxyIRG_uJShsmI zlwcYGB$yTvswp4?glFp&qP;$0RSe~PotHCt8jZE)nxO@8k8~n*8Xw z7c9~JL0E(I?*W)jAd+9bhzB@|AgLA=(jbyV(jS402q5=bRRE6+NFilc(QR4vcz+Yf z5WtFTl&dR%WPsPXzoG&NTq((j!0o)*8bC{TMFl>h0FnR**b0a!1zBXX0MC*D0hZ0l zUXf=-C+a%rNLIIo^ten4$X4162^gD50%s7w0vLf9?J3jJSp@jF0(2*`U}PgKl1-!} zD8+akMs|-Z37CAGwHZkPTKe_cNC{pwz}^JNIs`?$E)*>iRS0k-3Hq6{(LYRpZQGui zmpa)rFbn*stBBgxio#emfz01tOE>a+J%9TdMXy!;rxidGfN@wgx+-Kq-AS~Uk$s}b zs+^@V#v?&?f9^c;-vzJ^W9=#j?qjUF!TJ&jeWu0`TJE#0%S7JD*(zh@p0eOu-N!rS zoQVKTkFCL~5CE-H0qCk@zFh>d(K1T`vOviFF?;ZAn6Xy{G_&vKheHVv$-O*C|I&DG z0yJsu^2!85hDecYS5gkTwq}aKHvuvS*|Q7Kir@jx?l+?V+M&@{6lIz`=&Bh#m+PT6 zQ4&qp4Vq{#0>~}^9ZW_n6Cvz;jGerzVAo~|(4DF~4m}?IZ21SZl<#5;{0Gopi>b*W zf7V|e>(=sH3SdAungDJ6QSdDV)8z8VhU^eYnRgBv2XwcBj2kVBw?iFTlT+}07xyE z0*^^GXdRCzOworTe`*(i7%iVy51m=VvdyfEY|-=4yc^|BMYpt=k~x~?M}npgdv<$00000NkvXXu0mjf0091cyXgP` literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Wardriving_128x64/meta.txt b/assets/dolphin/external/L1_Wardriving_128x64/meta.txt new file mode 100644 index 000000000..5a492afc6 --- /dev/null +++ b/assets/dolphin/external/L1_Wardriving_128x64/meta.txt @@ -0,0 +1,23 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 9 +Active frames: 0 +Frames order: 0 1 2 3 4 5 6 7 7 +Active cycles: 0 +Frame rate: 2 +Duration: 360 +Active cooldown: 0 + +Bubble slots: 1 + +Slot: 0 +X: 55 +Y: 14 +Text: Pwned! +AlignH: Left +AlignV: Center +StartFrame: 7 +EndFrame: 8 diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index 50689ded1..d5e24b143 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -189,3 +189,17 @@ Max butthurt: 14 Min level: 16 Max level: 30 Weight: 5 + +Name: L1_3d_printing_128x64 +Min butthurt: 0 +Max butthurt: 14 +Min level: 6 +Max level: 30 +Weight: 6 + +Name: L1_Wardriving_128x64 +Min butthurt: 0 +Max butthurt: 14 +Min level: 11 +Max level: 30 +Weight: 6 From a6bbfdfaa2182144c50ebcea7f246b023f935a2f Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+Willy-JL@users.noreply.github.com> Date: Wed, 10 Apr 2024 06:28:44 +0100 Subject: [PATCH 090/129] fkin windows with case insensitive paths --- .../{Frame_0.png => frame_0.png} | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename assets/dolphin/external/L1_3d_printing_128x64/{Frame_0.png => frame_0.png} (100%) diff --git a/assets/dolphin/external/L1_3d_printing_128x64/Frame_0.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_0.png similarity index 100% rename from assets/dolphin/external/L1_3d_printing_128x64/Frame_0.png rename to assets/dolphin/external/L1_3d_printing_128x64/frame_0.png From fb9728d5701ef023ae33723195c6186109442fee Mon Sep 17 00:00:00 2001 From: RebornedBrain <138568282+RebornedBrain@users.noreply.github.com> Date: Wed, 10 Apr 2024 12:51:36 +0300 Subject: [PATCH 091/129] [FL-3772] Felica poller (#3570) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * New types for felica poller * New functions for felica data transmissions * Felica memory map extended with new fields * Init/deinit of mbedtls context added for felica encryption * Functions for session key and mac calculations added * Raw felica_poller implementation added * Removed MAC type parameter from check_mac function * Replaced all data fields needed for auth with context structure * Clean up felica_poller.c * Now RC block is filled with random numbers * New parameter for counting well-read blocks * Some cleanups * Felica file save and load logic added * Now we use card key from context for session key calculation * Copying card key to card block from auth context when both authentications succeeded, otherwise decrement blocks count by 1 * New felica poller event added * Moved some data structions to public namespace * FelicaAuthenticationContext struct moved to felica.h * Field type and name changed for better ones * Helper functions for felica_auth added to the app * New scene for felica card key input added * Logic for felica key input added * Auth context request processing added * Added block index definitions and replaced all index numbers with them * More macro defines * Replace nesting with do while block * New function for write operations mac calculation added * Replace nesting with do while block * Make functions static for now because they are used internally * Wrote some comments * Raw felica render implementation * New felica scenes * Adjusted felica dump rendering according design requirements * New felica scene added * Helper for switching scene during unlock added * Added warning scene and transfer to it * Moved unlock scene logic to separate files * Magic number changed * New felica render logic * Felica scenes adjusted according to design requirements * Felica poller cleanups * Some asserts added and some fixed * Replcaed asserts to checks in public api * Fixed pvs warnings in felica_poller * New event for felica_poller added for incomplete read actions * Handling of new poller event added * Update SConscript with felica files * Update api_symbols.csv with felica functions * Sync API versions Co-authored-by: あく --- applications/main/nfc/helpers/felica_auth.c | 21 ++ applications/main/nfc/helpers/felica_auth.h | 17 ++ .../helpers/protocol_support/felica/felica.c | 123 ++++++++- .../protocol_support/felica/felica_render.c | 100 +++++++- .../protocol_support/felica/felica_render.h | 12 + .../mf_ultralight/mf_ultralight.c | 38 +-- .../nfc_protocol_support_unlock_helper.c | 38 +++ .../nfc_protocol_support_unlock_helper.h | 9 + applications/main/nfc/nfc_app.c | 2 + applications/main/nfc/nfc_app_i.h | 2 + .../main/nfc/scenes/nfc_scene_config.h | 2 + .../nfc/scenes/nfc_scene_felica_key_input.c | 46 ++++ .../nfc/scenes/nfc_scene_felica_unlock_warn.c | 59 +++++ lib/nfc/SConscript | 2 + lib/nfc/protocols/felica/felica.c | 211 +++++++++++++++- lib/nfc/protocols/felica/felica.h | 101 ++++++++ lib/nfc/protocols/felica/felica_poller.c | 233 ++++++++++++++++-- lib/nfc/protocols/felica/felica_poller.h | 21 +- lib/nfc/protocols/felica/felica_poller_i.c | 107 +++++++- lib/nfc/protocols/felica/felica_poller_i.h | 104 +++++++- targets/f18/api_symbols.csv | 2 +- targets/f7/api_symbols.csv | 21 +- 22 files changed, 1186 insertions(+), 85 deletions(-) create mode 100644 applications/main/nfc/helpers/felica_auth.c create mode 100644 applications/main/nfc/helpers/felica_auth.h create mode 100644 applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.c create mode 100644 applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.h create mode 100644 applications/main/nfc/scenes/nfc_scene_felica_key_input.c create mode 100644 applications/main/nfc/scenes/nfc_scene_felica_unlock_warn.c diff --git a/applications/main/nfc/helpers/felica_auth.c b/applications/main/nfc/helpers/felica_auth.c new file mode 100644 index 000000000..a8cd0929b --- /dev/null +++ b/applications/main/nfc/helpers/felica_auth.c @@ -0,0 +1,21 @@ +#include "felica_auth.h" + +FelicaAuthenticationContext* felica_auth_alloc() { + FelicaAuthenticationContext* instance = malloc(sizeof(FelicaAuthenticationContext)); + memset(instance->card_key.data, 0, FELICA_DATA_BLOCK_SIZE); + instance->skip_auth = true; + return instance; +} + +void felica_auth_free(FelicaAuthenticationContext* instance) { + furi_assert(instance); + free(instance); +} + +void felica_auth_reset(FelicaAuthenticationContext* instance) { + furi_assert(instance); + memset(instance->card_key.data, 0, FELICA_DATA_BLOCK_SIZE); + instance->skip_auth = true; + instance->auth_status.external = 0; + instance->auth_status.internal = 0; +} \ No newline at end of file diff --git a/applications/main/nfc/helpers/felica_auth.h b/applications/main/nfc/helpers/felica_auth.h new file mode 100644 index 000000000..3d99f1f9c --- /dev/null +++ b/applications/main/nfc/helpers/felica_auth.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +FelicaAuthenticationContext* felica_auth_alloc(); + +void felica_auth_free(FelicaAuthenticationContext* instance); + +void felica_auth_reset(FelicaAuthenticationContext* instance); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica.c b/applications/main/nfc/helpers/protocol_support/felica/felica.c index affa33b86..b0660f3e6 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica.c +++ b/applications/main/nfc/helpers/protocol_support/felica/felica.c @@ -7,6 +7,11 @@ #include "../nfc_protocol_support_common.h" #include "../nfc_protocol_support_gui_common.h" +#include "../nfc_protocol_support_unlock_helper.h" + +enum { + SubmenuIndexUnlock = SubmenuIndexCommonMax, +}; static void nfc_scene_info_on_enter_felica(NfcApp* instance) { const NfcDevice* device = instance->nfc_device; @@ -18,6 +23,35 @@ static void nfc_scene_info_on_enter_felica(NfcApp* instance) { temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); nfc_render_felica_info(data, NfcProtocolFormatTypeFull, temp_str); + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 48, furi_string_get_cstr(temp_str)); + + widget_add_button_element( + instance->widget, + GuiButtonTypeRight, + "More", + nfc_protocol_support_common_widget_callback, + instance); + furi_string_free(temp_str); +} + +static bool nfc_scene_info_on_event_felica(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom && event.event == GuiButtonTypeRight) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMoreInfo); + return true; + } + + return false; +} + +static void nfc_scene_more_info_on_enter_felica(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const FelicaData* data = nfc_device_get_data(device, NfcProtocolFelica); + + FuriString* temp_str = furi_string_alloc(); + + nfc_render_felica_dump(data, temp_str); + widget_add_text_scroll_element( instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); @@ -29,29 +63,75 @@ static NfcCommand nfc_scene_read_poller_callback_felica(NfcGenericEvent event, v NfcApp* instance = context; const FelicaPollerEvent* felica_event = event.event_data; + NfcCommand command = NfcCommandContinue; if(felica_event->type == FelicaPollerEventTypeReady) { nfc_device_set_data( instance->nfc_device, NfcProtocolFelica, nfc_poller_get_data(instance->poller)); view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); - return NfcCommandStop; + command = NfcCommandStop; + } else if( + felica_event->type == FelicaPollerEventTypeError || + felica_event->type == FelicaPollerEventTypeIncomplete) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolFelica, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventPollerIncomplete); + command = NfcCommandStop; + } else if(felica_event->type == FelicaPollerEventTypeRequestAuthContext) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected); + FelicaAuthenticationContext* ctx = felica_event->data->auth_context; + ctx->skip_auth = instance->felica_auth->skip_auth; + memcpy(ctx->card_key.data, instance->felica_auth->card_key.data, FELICA_DATA_BLOCK_SIZE); } - return NfcCommandContinue; + return command; } static void nfc_scene_read_on_enter_felica(NfcApp* instance) { + nfc_unlock_helper_setup_from_state(instance); nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_felica, instance); } +bool nfc_scene_read_on_event_felica(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventCardDetected) { + nfc_unlock_helper_card_detected_handler(instance); + } else if(event.event == NfcCustomEventPollerIncomplete) { + notification_message(instance->notifications, &sequence_semi_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + } + } + return true; +} + static void nfc_scene_read_success_on_enter_felica(NfcApp* instance) { const NfcDevice* device = instance->nfc_device; const FelicaData* data = nfc_device_get_data(device, NfcProtocolFelica); FuriString* temp_str = furi_string_alloc(); - furi_string_cat_printf( - temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); - nfc_render_felica_info(data, NfcProtocolFormatTypeShort, temp_str); + + if(!scene_manager_has_previous_scene(instance->scene_manager, NfcSceneFelicaUnlockWarn)) { + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_felica_info(data, NfcProtocolFormatTypeShort, temp_str); + } else { + bool all_unlocked = data->blocks_read == data->blocks_total; + furi_string_cat_printf( + temp_str, + "\e#%s\n", + all_unlocked ? "All Blocks Are Unlocked" : "Some Blocks Are Locked"); + nfc_render_felica_idm(data, NfcProtocolFormatTypeShort, temp_str); + uint8_t* ck_data = instance->felica_auth->card_key.data; + furi_string_cat_printf(temp_str, "Key:"); + for(uint8_t i = 0; i < 7; i++) { + furi_string_cat_printf(temp_str, " %02X", ck_data[i]); + if(i == 6) furi_string_cat_printf(temp_str, "..."); + } + nfc_render_felica_blocks_count(data, temp_str, false); + } + felica_auth_reset(instance->felica_auth); widget_add_text_scroll_element( instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); @@ -74,23 +154,50 @@ static void nfc_scene_emulate_on_enter_felica(NfcApp* instance) { nfc_listener_start(instance->listener, NULL, NULL); } +static void nfc_scene_read_menu_on_enter_felica(NfcApp* instance) { + const FelicaData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolFelica); + if(data->blocks_read != data->blocks_total) { + submenu_add_item( + instance->submenu, + "Unlock", + SubmenuIndexUnlock, + nfc_protocol_support_common_submenu_callback, + instance); + } +} + +static bool nfc_scene_read_menu_on_event_felica(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexUnlock) { + scene_manager_next_scene(instance->scene_manager, NfcSceneFelicaKeyInput); + return true; + } + } + return false; +} + const NfcProtocolSupportBase nfc_protocol_support_felica = { .features = NfcProtocolFeatureEmulateUid, .scene_info = { .on_enter = nfc_scene_info_on_enter_felica, + .on_event = nfc_scene_info_on_event_felica, + }, + .scene_more_info = + { + .on_enter = nfc_scene_more_info_on_enter_felica, .on_event = nfc_protocol_support_common_on_event_empty, }, .scene_read = { .on_enter = nfc_scene_read_on_enter_felica, - .on_event = nfc_protocol_support_common_on_event_empty, + .on_event = nfc_scene_read_on_event_felica, }, .scene_read_menu = { - .on_enter = nfc_protocol_support_common_on_enter_empty, - .on_event = nfc_protocol_support_common_on_event_empty, + .on_enter = nfc_scene_read_menu_on_enter_felica, + .on_event = nfc_scene_read_menu_on_event_felica, }, .scene_read_success = { diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica_render.c b/applications/main/nfc/helpers/protocol_support/felica/felica_render.c index 3142b2c6d..6c57fb24b 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica_render.c +++ b/applications/main/nfc/helpers/protocol_support/felica/felica_render.c @@ -1,19 +1,107 @@ #include "felica_render.h" +void nfc_render_felica_blocks_count( + const FelicaData* data, + FuriString* str, + bool render_auth_notification) { + furi_string_cat_printf(str, "\nBlocks Read: %u/%u", data->blocks_read, data->blocks_total); + if(render_auth_notification && data->blocks_read != data->blocks_total) { + furi_string_cat_printf(str, "\nAuth-protected blocks!"); + } +} + +void nfc_render_felica_idm( + const FelicaData* data, + NfcProtocolFormatType format_type, + FuriString* str) { + furi_string_cat_printf(str, (format_type == NfcProtocolFormatTypeFull) ? "IDm:\n" : "IDm:"); + + for(size_t i = 0; i < FELICA_IDM_SIZE; i++) { + furi_string_cat_printf( + str, + (format_type == NfcProtocolFormatTypeFull) ? "%02X " : " %02X", + data->idm.data[i]); + } +} + void nfc_render_felica_info( const FelicaData* data, NfcProtocolFormatType format_type, FuriString* str) { - furi_string_cat_printf(str, "IDm:"); - - for(size_t i = 0; i < FELICA_IDM_SIZE; i++) { - furi_string_cat_printf(str, " %02X", data->idm.data[i]); + if(format_type == NfcProtocolFormatTypeFull) { + furi_string_cat_printf(str, "Tech: JIS X 6319-4,\nISO 18092 [NFC-F]\n"); } + nfc_render_felica_idm(data, format_type, str); + if(format_type == NfcProtocolFormatTypeFull) { - furi_string_cat_printf(str, "\nPMm:"); + furi_string_cat_printf(str, "\nPMm:\n"); for(size_t i = 0; i < FELICA_PMM_SIZE; ++i) { - furi_string_cat_printf(str, " %02X", data->pmm.data[i]); + furi_string_cat_printf(str, "%02X ", data->pmm.data[i]); } } + nfc_render_felica_blocks_count(data, str, true); +} + +static void nfc_render_felica_block_name( + const char* name, + FuriString* str, + uint8_t prefix_separator_cnt, + uint8_t suffix_separator_cnt) { + for(uint8_t i = 0; i < prefix_separator_cnt; i++) { + furi_string_cat_printf(str, ":"); + } + furi_string_cat_printf(str, "[ %s ]", name); + for(uint8_t i = 0; i < suffix_separator_cnt; i++) { + furi_string_cat_printf(str, ":"); + } +} + +static void nfc_render_felica_block_data(const FelicaBlock* block, FuriString* str) { + furi_string_cat_printf(str, "\nSF1=%02X; SF2=%02X\n", block->SF1, block->SF2); + for(size_t j = 0; j < FELICA_DATA_BLOCK_SIZE; j++) { + if((j != 0) && (j % 8 == 0)) furi_string_cat_printf(str, "\n"); + furi_string_cat_printf(str, "%02X ", block->data[j]); + } + furi_string_cat_printf(str, "\n"); +} + +static void nfc_render_felica_block( + const FelicaBlock* block, + FuriString* str, + const char* name, + uint8_t prefix_separator_cnt, + uint8_t suffix_separator_cnt) { + nfc_render_felica_block_name(name, str, prefix_separator_cnt, suffix_separator_cnt); + nfc_render_felica_block_data(block, str); +} + +void nfc_render_felica_dump(const FelicaData* data, FuriString* str) { + FuriString* name = furi_string_alloc(); + for(size_t i = 0; i < 14; i++) { + furi_string_printf(name, "S_PAD%d", i); + uint8_t suf_cnt = 18; + if(i == 1) { + suf_cnt = 19; + } else if((i == 10) || (i == 12) || (i == 13)) { + suf_cnt = 16; + } + nfc_render_felica_block( + &data->data.fs.spad[i], str, furi_string_get_cstr(name), 20, suf_cnt); + } + furi_string_free(name); + nfc_render_felica_block(&data->data.fs.reg, str, "REG", 23, 23); + nfc_render_felica_block(&data->data.fs.rc, str, "RC", 25, 25); + nfc_render_felica_block(&data->data.fs.mac, str, "MAC", 23, 23); + nfc_render_felica_block(&data->data.fs.id, str, "ID", 25, 25); + nfc_render_felica_block(&data->data.fs.d_id, str, "D_ID", 22, 24); + nfc_render_felica_block(&data->data.fs.ser_c, str, "SER_C", 20, 21); + nfc_render_felica_block(&data->data.fs.sys_c, str, "SYS_C", 20, 21); + nfc_render_felica_block(&data->data.fs.ckv, str, "CKV", 23, 23); + nfc_render_felica_block(&data->data.fs.ck, str, "CK", 25, 25); + nfc_render_felica_block(&data->data.fs.mc, str, "MC", 25, 24); + nfc_render_felica_block(&data->data.fs.wcnt, str, "WCNT", 22, 20); + nfc_render_felica_block(&data->data.fs.mac_a, str, "MAC_A", 20, 20); + nfc_render_felica_block(&data->data.fs.state, str, "STATE", 20, 21); + nfc_render_felica_block(&data->data.fs.crc_check, str, "CRC_CHCK", 15, 17); } diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica_render.h b/applications/main/nfc/helpers/protocol_support/felica/felica_render.h index 6d9816fc6..3d32e8d14 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica_render.h +++ b/applications/main/nfc/helpers/protocol_support/felica/felica_render.h @@ -4,7 +4,19 @@ #include "../nfc_protocol_support_render_common.h" +void nfc_render_felica_blocks_count( + const FelicaData* data, + FuriString* str, + bool render_auth_notification); + void nfc_render_felica_info( const FelicaData* data, NfcProtocolFormatType format_type, FuriString* str); + +void nfc_render_felica_dump(const FelicaData* data, FuriString* str); + +void nfc_render_felica_idm( + const FelicaData* data, + NfcProtocolFormatType format_type, + FuriString* str); \ No newline at end of file diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index bd2015889..cd16374bc 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -8,6 +8,7 @@ #include "../nfc_protocol_support_common.h" #include "../nfc_protocol_support_gui_common.h" +#include "../nfc_protocol_support_unlock_helper.h" enum { SubmenuIndexUnlock = SubmenuIndexCommonMax, @@ -157,48 +158,15 @@ static NfcCommand return NfcCommandContinue; } -enum { - NfcSceneMfUltralightReadMenuStateCardSearch, - NfcSceneMfUltralightReadMenuStateCardFound, -}; - -static void nfc_scene_read_setup_view(NfcApp* instance) { - Popup* popup = instance->popup; - popup_reset(popup); - uint32_t state = scene_manager_get_scene_state(instance->scene_manager, NfcSceneRead); - - if(state == NfcSceneMfUltralightReadMenuStateCardSearch) { - popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); - popup_set_header(instance->popup, "Unlocking", 97, 15, AlignCenter, AlignTop); - popup_set_text( - instance->popup, "Hold card next\nto Flipper's back", 94, 27, AlignCenter, AlignTop); - } else { - popup_set_header(instance->popup, "Don't move", 85, 27, AlignCenter, AlignTop); - popup_set_icon(instance->popup, 12, 20, &A_Loading_24); - } - - view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); -} - static void nfc_scene_read_on_enter_mf_ultralight(NfcApp* instance) { - bool unlocking = - scene_manager_has_previous_scene(instance->scene_manager, NfcSceneMfUltralightUnlockWarn); - - uint32_t state = unlocking ? NfcSceneMfUltralightReadMenuStateCardSearch : - NfcSceneMfUltralightReadMenuStateCardFound; - - scene_manager_set_scene_state(instance->scene_manager, NfcSceneRead, state); - - nfc_scene_read_setup_view(instance); + nfc_unlock_helper_setup_from_state(instance); nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_mf_ultralight, instance); } bool nfc_scene_read_on_event_mf_ultralight(NfcApp* instance, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventCardDetected) { - scene_manager_set_scene_state( - instance->scene_manager, NfcSceneRead, NfcSceneMfUltralightReadMenuStateCardFound); - nfc_scene_read_setup_view(instance); + nfc_unlock_helper_card_detected_handler(instance); } else if((event.event == NfcCustomEventPollerIncomplete)) { notification_message(instance->notifications, &sequence_semi_success); scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.c new file mode 100644 index 000000000..f1d504d24 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.c @@ -0,0 +1,38 @@ +#include "nfc_protocol_support_unlock_helper.h" + +static void nfc_scene_read_setup_view(NfcApp* instance) { + Popup* popup = instance->popup; + popup_reset(popup); + uint32_t state = scene_manager_get_scene_state(instance->scene_manager, NfcSceneRead); + + if(state == NfcSceneReadMenuStateCardSearch) { + popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); + popup_set_header(instance->popup, "Unlocking", 97, 15, AlignCenter, AlignTop); + popup_set_text( + instance->popup, "Hold card next\nto Flipper's back", 94, 27, AlignCenter, AlignTop); + } else { + popup_set_header(instance->popup, "Don't move", 85, 27, AlignCenter, AlignTop); + popup_set_icon(instance->popup, 12, 20, &A_Loading_24); + } + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); +} + +void nfc_unlock_helper_setup_from_state(NfcApp* instance) { + bool unlocking = + scene_manager_has_previous_scene( + instance->scene_manager, NfcSceneMfUltralightUnlockWarn) || + scene_manager_has_previous_scene(instance->scene_manager, NfcSceneFelicaUnlockWarn); + + uint32_t state = unlocking ? NfcSceneReadMenuStateCardSearch : NfcSceneReadMenuStateCardFound; + + scene_manager_set_scene_state(instance->scene_manager, NfcSceneRead, state); + + nfc_scene_read_setup_view(instance); +} + +void nfc_unlock_helper_card_detected_handler(NfcApp* instance) { + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneRead, NfcSceneReadMenuStateCardFound); + nfc_scene_read_setup_view(instance); +} \ No newline at end of file diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.h new file mode 100644 index 000000000..65da33240 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.h @@ -0,0 +1,9 @@ +#include "nfc/nfc_app_i.h" + +typedef enum { + NfcSceneReadMenuStateCardSearch, + NfcSceneReadMenuStateCardFound, +} NfcSceneUnlockReadState; + +void nfc_unlock_helper_setup_from_state(NfcApp* instance); +void nfc_unlock_helper_card_detected_handler(NfcApp* instance); \ No newline at end of file diff --git a/applications/main/nfc/nfc_app.c b/applications/main/nfc/nfc_app.c index 02f852e34..d0557de2a 100644 --- a/applications/main/nfc/nfc_app.c +++ b/applications/main/nfc/nfc_app.c @@ -50,6 +50,7 @@ NfcApp* nfc_app_alloc(void) { instance->nfc = nfc_alloc(); + instance->felica_auth = felica_auth_alloc(); instance->mf_ul_auth = mf_ultralight_auth_alloc(); instance->slix_unlock = slix_unlock_alloc(); instance->mfc_key_cache = mf_classic_key_cache_alloc(); @@ -141,6 +142,7 @@ void nfc_app_free(NfcApp* instance) { nfc_free(instance->nfc); + felica_auth_free(instance->felica_auth); mf_ultralight_auth_free(instance->mf_ul_auth); slix_unlock_free(instance->slix_unlock); mf_classic_key_cache_free(instance->mfc_key_cache); diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 706815e62..64f7fc6e7 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -32,6 +32,7 @@ #include "helpers/mfkey32_logger.h" #include "helpers/mf_classic_key_cache.h" #include "helpers/nfc_supported_cards.h" +#include "helpers/felica_auth.h" #include "helpers/slix_unlock.h" #include @@ -129,6 +130,7 @@ struct NfcApp { NfcScanner* scanner; NfcListener* listener; + FelicaAuthenticationContext* felica_auth; MfUltralightAuth* mf_ul_auth; SlixUnlock* slix_unlock; NfcMfClassicDictAttackContext nfc_dict_context; diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index 8a5e29ff3..3017d16a4 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -33,6 +33,8 @@ ADD_SCENE(nfc, mf_ultralight_unlock_menu, MfUltralightUnlockMenu) ADD_SCENE(nfc, mf_ultralight_unlock_warn, MfUltralightUnlockWarn) ADD_SCENE(nfc, mf_ultralight_key_input, MfUltralightKeyInput) ADD_SCENE(nfc, mf_ultralight_capture_pass, MfUltralightCapturePass) +ADD_SCENE(nfc, felica_key_input, FelicaKeyInput) +ADD_SCENE(nfc, felica_unlock_warn, FelicaUnlockWarn) ADD_SCENE(nfc, mf_desfire_more_info, MfDesfireMoreInfo) ADD_SCENE(nfc, mf_desfire_app, MfDesfireApp) diff --git a/applications/main/nfc/scenes/nfc_scene_felica_key_input.c b/applications/main/nfc/scenes/nfc_scene_felica_key_input.c new file mode 100644 index 000000000..b04f12dae --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_felica_key_input.c @@ -0,0 +1,46 @@ +#include "../nfc_app_i.h" + +void nfc_scene_felica_key_input_byte_input_callback(void* context) { + NfcApp* nfc = context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventByteInputDone); +} + +void nfc_scene_felica_key_input_on_enter(void* context) { + NfcApp* nfc = context; + + // Setup view + ByteInput* byte_input = nfc->byte_input; + byte_input_set_header_text(byte_input, "Enter key in hex"); + byte_input_set_result_callback( + byte_input, + nfc_scene_felica_key_input_byte_input_callback, + NULL, + nfc, + nfc->felica_auth->card_key.data, + FELICA_DATA_BLOCK_SIZE); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewByteInput); +} + +bool nfc_scene_felica_key_input_on_event(void* context, SceneManagerEvent event) { + NfcApp* nfc = context; + UNUSED(event); + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventByteInputDone) { + nfc->felica_auth->skip_auth = false; + scene_manager_next_scene(nfc->scene_manager, NfcSceneFelicaUnlockWarn); + consumed = true; + } + } + return consumed; +} + +void nfc_scene_felica_key_input_on_exit(void* context) { + NfcApp* nfc = context; + + // Clear view + byte_input_set_result_callback(nfc->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(nfc->byte_input, ""); +} diff --git a/applications/main/nfc/scenes/nfc_scene_felica_unlock_warn.c b/applications/main/nfc/scenes/nfc_scene_felica_unlock_warn.c new file mode 100644 index 000000000..15b61dfa5 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_felica_unlock_warn.c @@ -0,0 +1,59 @@ +#include "../nfc_app_i.h" + +void nfc_scene_felica_unlock_warn_dialog_callback(DialogExResult result, void* context) { + NfcApp* nfc = context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, result); +} + +void nfc_scene_felica_unlock_warn_on_enter(void* context) { + NfcApp* nfc = context; + + const char* message = "Risky Action!"; + DialogEx* dialog_ex = nfc->dialog_ex; + dialog_ex_set_context(dialog_ex, nfc); + dialog_ex_set_result_callback(dialog_ex, nfc_scene_felica_unlock_warn_dialog_callback); + + dialog_ex_set_header(dialog_ex, message, 64, 0, AlignCenter, AlignTop); + + FuriString* str = furi_string_alloc(); + furi_string_cat_printf(str, "Unlock with key: "); + for(uint8_t i = 0; i < FELICA_DATA_BLOCK_SIZE; i++) + furi_string_cat_printf(str, "%02X ", nfc->felica_auth->card_key.data[i]); + furi_string_cat_printf(str, "?"); + + nfc_text_store_set(nfc, furi_string_get_cstr(str)); + furi_string_free(str); + + dialog_ex_set_text(dialog_ex, nfc->text_store, 0, 12, AlignLeft, AlignTop); + + dialog_ex_set_left_button_text(dialog_ex, "Cancel"); + dialog_ex_set_right_button_text(dialog_ex, "Unlock"); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx); +} + +bool nfc_scene_felica_unlock_warn_on_event(void* context, SceneManagerEvent event) { + NfcApp* nfc = context; + UNUSED(event); + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == DialogExResultRight) { + nfc->felica_auth->skip_auth = false; + scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); + consumed = true; + } else if(event.event == DialogExResultLeft) { + scene_manager_previous_scene(nfc->scene_manager); + consumed = true; + } + } + return consumed; +} + +void nfc_scene_felica_unlock_warn_on_exit(void* context) { + NfcApp* nfc = context; + + dialog_ex_reset(nfc->dialog_ex); + nfc_text_store_clear(nfc); +} diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index 82317918b..f047101e6 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -24,6 +24,7 @@ env.Append( File("protocols/mf_desfire/mf_desfire.h"), File("protocols/slix/slix.h"), File("protocols/st25tb/st25tb.h"), + File("protocols/felica/felica.h"), # Pollers File("protocols/iso14443_3a/iso14443_3a_poller.h"), File("protocols/iso14443_3b/iso14443_3b_poller.h"), @@ -33,6 +34,7 @@ env.Append( File("protocols/mf_classic/mf_classic_poller.h"), File("protocols/mf_desfire/mf_desfire_poller.h"), File("protocols/st25tb/st25tb_poller.h"), + File("protocols/felica/felica_poller.h"), # Listeners File("protocols/iso14443_3a/iso14443_3a_listener.h"), File("protocols/iso14443_4a/iso14443_4a_listener.h"), diff --git a/lib/nfc/protocols/felica/felica.c b/lib/nfc/protocols/felica/felica.c index 7de1310bf..bc47e0642 100644 --- a/lib/nfc/protocols/felica/felica.c +++ b/lib/nfc/protocols/felica/felica.c @@ -13,6 +13,14 @@ static const uint32_t felica_data_format_version = 1; +/** @brief This is used in felica_prepare_first_block to define which + * type of block needs to be prepared. +*/ +typedef enum { + FelicaMACTypeRead, + FelicaMACTypeWrite, +} FelicaMACType; + const NfcDeviceBase nfc_device_felica = { .protocol_name = FELICA_PROTOCOL_NAME, .alloc = (NfcDeviceAlloc)felica_alloc, @@ -35,18 +43,18 @@ FelicaData* felica_alloc(void) { } void felica_free(FelicaData* data) { - furi_assert(data); - + furi_check(data); free(data); } void felica_reset(FelicaData* data) { + furi_check(data); memset(data, 0, sizeof(FelicaData)); } void felica_copy(FelicaData* data, const FelicaData* other) { - furi_assert(data); - furi_assert(other); + furi_check(data); + furi_check(other); *data = *other; } @@ -59,7 +67,7 @@ bool felica_verify(FelicaData* data, const FuriString* device_type) { } bool felica_load(FelicaData* data, FlipperFormat* ff, uint32_t version) { - furi_assert(data); + furi_check(data); bool parsed = false; @@ -77,13 +85,32 @@ bool felica_load(FelicaData* data, FlipperFormat* ff, uint32_t version) { break; parsed = true; + uint32_t blocks_total = 0; + uint32_t blocks_read = 0; + if(!flipper_format_read_uint32(ff, "Blocks total", &blocks_total, 1)) break; + if(!flipper_format_read_uint32(ff, "Blocks read", &blocks_read, 1)) break; + data->blocks_total = (uint8_t)blocks_total; + data->blocks_read = (uint8_t)blocks_read; + + FuriString* temp_str = furi_string_alloc(); + for(uint8_t i = 0; i < data->blocks_total; i++) { + furi_string_printf(temp_str, "Block %d", i); + if(!flipper_format_read_hex( + ff, + furi_string_get_cstr(temp_str), + (&data->data.dump[i * sizeof(FelicaBlock)]), + sizeof(FelicaBlock))) { + parsed = false; + break; + } + } } while(false); return parsed; } bool felica_save(const FelicaData* data, FlipperFormat* ff) { - furi_assert(data); + furi_check(data); bool saved = false; @@ -98,15 +125,33 @@ bool felica_save(const FelicaData* data, FlipperFormat* ff) { ff, FELICA_MANUFACTURE_PARAMETER, data->pmm.data, FELICA_PMM_SIZE)) break; + uint32_t blocks_total = data->blocks_total; + uint32_t blocks_read = data->blocks_read; + if(!flipper_format_write_uint32(ff, "Blocks total", &blocks_total, 1)) break; + if(!flipper_format_write_uint32(ff, "Blocks read", &blocks_read, 1)) break; + saved = true; + FuriString* temp_str = furi_string_alloc(); + for(uint8_t i = 0; i < blocks_total; i++) { + furi_string_printf(temp_str, "Block %d", i); + if(!flipper_format_write_hex( + ff, + furi_string_get_cstr(temp_str), + (&data->data.dump[i * sizeof(FelicaBlock)]), + sizeof(FelicaBlock))) { + saved = false; + break; + } + } + furi_string_free(temp_str); } while(false); return saved; } bool felica_is_equal(const FelicaData* data, const FelicaData* other) { - furi_assert(data); - furi_assert(other); + furi_check(data); + furi_check(other); return memcmp(data, other, sizeof(FelicaData)) == 0; } @@ -119,7 +164,7 @@ const char* felica_get_device_name(const FelicaData* data, NfcDeviceNameType nam } const uint8_t* felica_get_uid(const FelicaData* data, size_t* uid_len) { - furi_assert(data); + furi_check(data); // Consider Manufacturer ID as UID if(uid_len) { @@ -130,7 +175,7 @@ const uint8_t* felica_get_uid(const FelicaData* data, size_t* uid_len) { } bool felica_set_uid(FelicaData* data, const uint8_t* uid, size_t uid_len) { - furi_assert(data); + furi_check(data); // Consider Manufacturer ID as UID const bool uid_valid = uid_len == FELICA_IDM_SIZE; @@ -145,3 +190,149 @@ FelicaData* felica_get_base_data(const FelicaData* data) { UNUSED(data); furi_crash("No base data"); } + +static void felica_reverse_copy_block(const uint8_t* array, uint8_t* reverse_array) { + furi_assert(array); + furi_assert(reverse_array); + + for(int i = 0; i < 8; i++) { + reverse_array[i] = array[7 - i]; + } +} + +void felica_calculate_session_key( + mbedtls_des3_context* ctx, + const uint8_t* ck, + const uint8_t* rc, + uint8_t* out) { + furi_check(ctx); + furi_check(ck); + furi_check(rc); + furi_check(out); + + uint8_t iv[8]; + memset(iv, 0, 8); + + uint8_t ck_reversed[16]; + felica_reverse_copy_block(ck, ck_reversed); + felica_reverse_copy_block(ck + 8, ck_reversed + 8); + + uint8_t rc_reversed[16]; + felica_reverse_copy_block(rc, rc_reversed); + felica_reverse_copy_block(rc + 8, rc_reversed + 8); + + mbedtls_des3_set2key_enc(ctx, ck_reversed); + mbedtls_des3_crypt_cbc(ctx, MBEDTLS_DES_ENCRYPT, FELICA_DATA_BLOCK_SIZE, iv, rc_reversed, out); +} + +static bool felica_calculate_mac( + mbedtls_des3_context* ctx, + const uint8_t* session_key, + const uint8_t* rc, + const uint8_t* first_block, + const uint8_t* data, + const size_t length, + uint8_t* mac) { + furi_check((length % 8) == 0); + + uint8_t reverse_data[8]; + uint8_t iv[8]; + uint8_t out[8]; + mbedtls_des3_set2key_enc(ctx, session_key); + + felica_reverse_copy_block(rc, iv); + felica_reverse_copy_block(first_block, reverse_data); + uint8_t i = 0; + bool error = false; + do { + if(mbedtls_des3_crypt_cbc(ctx, MBEDTLS_DES_ENCRYPT, 8, iv, reverse_data, out) == 0) { + memcpy(iv, out, sizeof(iv)); + felica_reverse_copy_block(data + i, reverse_data); + i += 8; + } else { + error = true; + break; + } + } while(i <= length); + + if(!error) { + felica_reverse_copy_block(out, mac); + } + return !error; +} + +static void felica_prepare_first_block( + FelicaMACType operation_type, + const uint8_t* blocks, + const uint8_t block_count, + uint8_t* out) { + furi_check(blocks); + furi_check(out); + if(operation_type == FelicaMACTypeRead) { + memset(out, 0xFF, 8); + for(uint8_t i = 0, j = 0; i < block_count; i++, j += 2) { + out[j] = blocks[i]; + out[j + 1] = 0; + } + } else { + furi_check(block_count == 4); + memset(out, 0, 8); + out[0] = blocks[0]; + out[1] = blocks[1]; + out[2] = blocks[2]; + out[4] = blocks[3]; + out[6] = FELICA_BLOCK_INDEX_MAC_A; + } +} + +bool felica_check_mac( + mbedtls_des3_context* ctx, + const uint8_t* session_key, + const uint8_t* rc, + const uint8_t* blocks, + const uint8_t block_count, + uint8_t* data) { + furi_check(ctx); + furi_check(session_key); + furi_check(rc); + furi_check(blocks); + furi_check(data); + + uint8_t first_block[8]; + uint8_t mac[8]; + felica_prepare_first_block(FelicaMACTypeRead, blocks, block_count, first_block); + + uint8_t data_size_without_mac = FELICA_DATA_BLOCK_SIZE * (block_count - 1); + felica_calculate_mac(ctx, session_key, rc, first_block, data, data_size_without_mac, mac); + + uint8_t* mac_ptr = data + data_size_without_mac; + return !memcmp(mac, mac_ptr, 8); +} + +void felica_calculate_mac_write( + mbedtls_des3_context* ctx, + const uint8_t* session_key, + const uint8_t* rc, + const uint8_t* wcnt, + const uint8_t* data, + uint8_t* mac) { + furi_check(ctx); + furi_check(session_key); + furi_check(rc); + furi_check(wcnt); + furi_check(data); + furi_check(mac); + + const uint8_t WCNT_length = 3; + uint8_t block_data[WCNT_length + 1]; + uint8_t first_block[8]; + + memcpy(block_data, wcnt, WCNT_length); + block_data[3] = FELICA_BLOCK_INDEX_STATE; + felica_prepare_first_block(FelicaMACTypeWrite, block_data, WCNT_length + 1, first_block); + + uint8_t session_swapped[FELICA_DATA_BLOCK_SIZE]; + memcpy(session_swapped, session_key + 8, 8); + memcpy(session_swapped + 8, session_key, 8); + felica_calculate_mac(ctx, session_swapped, rc, first_block, data, FELICA_DATA_BLOCK_SIZE, mac); +} \ No newline at end of file diff --git a/lib/nfc/protocols/felica/felica.h b/lib/nfc/protocols/felica/felica.h index e1820d4dc..d032943d3 100644 --- a/lib/nfc/protocols/felica/felica.h +++ b/lib/nfc/protocols/felica/felica.h @@ -2,6 +2,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -9,6 +10,23 @@ extern "C" { #define FELICA_IDM_SIZE (8U) #define FELICA_PMM_SIZE (8U) +#define FELICA_DATA_BLOCK_SIZE (16U) + +#define FELICA_BLOCKS_TOTAL_COUNT (27U) +#define FELICA_BLOCK_INDEX_REG (0x0EU) +#define FELICA_BLOCK_INDEX_RC (0x80U) +#define FELICA_BLOCK_INDEX_MAC (0x81U) +#define FELICA_BLOCK_INDEX_ID (0x82U) +#define FELICA_BLOCK_INDEX_D_ID (0x83U) +#define FELICA_BLOCK_INDEX_SER_C (0x84U) +#define FELICA_BLOCK_INDEX_SYS_C (0x85U) +#define FELICA_BLOCK_INDEX_CKV (0x86U) +#define FELICA_BLOCK_INDEX_CK (0x87U) +#define FELICA_BLOCK_INDEX_MC (0x88U) +#define FELICA_BLOCK_INDEX_WCNT (0x90U) +#define FELICA_BLOCK_INDEX_MAC_A (0x91U) +#define FELICA_BLOCK_INDEX_STATE (0x92U) +#define FELICA_BLOCK_INDEX_CRC_CHECK (0xA0U) #define FELICA_GUARD_TIME_US (20000U) #define FELICA_FDT_POLL_FC (10000U) @@ -23,6 +41,7 @@ extern "C" { #define FELICA_TIME_SLOT_8 (0x07U) #define FELICA_TIME_SLOT_16 (0x0FU) +/** @brief Type of possible Felica errors */ typedef enum { FelicaErrorNone, FelicaErrorNotPresent, @@ -35,17 +54,78 @@ typedef enum { FelicaErrorTimeout, } FelicaError; +/** @brief Separate type for card key block. Used in authentication process */ +typedef struct { + uint8_t data[FELICA_DATA_BLOCK_SIZE]; +} FelicaCardKey; + +/** @brief In Felica there two types of auth. Internal is the first one, after + * which external became possible. Here are two flags representing which one + * was passed */ +typedef struct { + bool internal : 1; + bool external : 1; +} FelicaAuthenticationStatus; + +/** @brief Struct which controls the process of authentication and can be passed as + * a parameter to the application level. In order to force user to fill card key block data. */ +typedef struct { + bool skip_auth; /**< By default it is true, so auth is skipped. By setting this to false several auth steps will be performed in order to pass auth*/ + FelicaCardKey + card_key; /**< User must fill this field with known card key in order to pass auth*/ + FelicaAuthenticationStatus auth_status; /**< Authentication status*/ +} FelicaAuthenticationContext; + +/** @brief Felica ID block */ typedef struct { uint8_t data[FELICA_IDM_SIZE]; } FelicaIDm; +/** @brief Felica PMm block */ typedef struct { uint8_t data[FELICA_PMM_SIZE]; } FelicaPMm; +/** @brief Felica block with status flags indicating last operation with it. + * See Felica manual for more details on status codes. */ +typedef struct { + uint8_t SF1; /**< Status flag 1, equals to 0 when success*/ + uint8_t SF2; /**< Status flag 2, equals to 0 when success*/ + uint8_t data[FELICA_DATA_BLOCK_SIZE]; /**< Block data */ +} FelicaBlock; + +/** @brief Felica filesystem structure */ +typedef struct { + FelicaBlock spad[14]; + FelicaBlock reg; + FelicaBlock rc; + FelicaBlock mac; + FelicaBlock id; + FelicaBlock d_id; + FelicaBlock ser_c; + FelicaBlock sys_c; + FelicaBlock ckv; + FelicaBlock ck; + FelicaBlock mc; + FelicaBlock wcnt; + FelicaBlock mac_a; + FelicaBlock state; + FelicaBlock crc_check; +} FelicaFileSystem; + +/** @brief Union which represents filesystem in junction with plain data dump */ +typedef union { + FelicaFileSystem fs; + uint8_t dump[sizeof(FelicaFileSystem)]; +} FelicaFSUnion; + +/** @brief Structure used to store Felica data and additional values about reading */ typedef struct { FelicaIDm idm; FelicaPMm pmm; + uint8_t blocks_total; + uint8_t blocks_read; + FelicaFSUnion data; } FelicaData; extern const NfcDeviceBase nfc_device_felica; @@ -74,6 +154,27 @@ bool felica_set_uid(FelicaData* data, const uint8_t* uid, size_t uid_len); FelicaData* felica_get_base_data(const FelicaData* data); +void felica_calculate_session_key( + mbedtls_des3_context* ctx, + const uint8_t* ck, + const uint8_t* rc, + uint8_t* out); + +bool felica_check_mac( + mbedtls_des3_context* ctx, + const uint8_t* session_key, + const uint8_t* rc, + const uint8_t* blocks, + const uint8_t block_count, + uint8_t* data); + +void felica_calculate_mac_write( + mbedtls_des3_context* ctx, + const uint8_t* session_key, + const uint8_t* rc, + const uint8_t* wcnt, + const uint8_t* data, + uint8_t* mac); #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/felica/felica_poller.c b/lib/nfc/protocols/felica/felica_poller.c index 23b1604e1..720daf238 100644 --- a/lib/nfc/protocols/felica/felica_poller.c +++ b/lib/nfc/protocols/felica/felica_poller.c @@ -3,6 +3,11 @@ #include #include +#include + +#define TAG "FelicaPoller" + +typedef NfcCommand (*FelicaPollerReadHandler)(FelicaPoller* instance); const FelicaData* felica_poller_get_data(FelicaPoller* instance) { furi_assert(instance); @@ -23,6 +28,8 @@ static FelicaPoller* felica_poller_alloc(Nfc* nfc) { nfc_set_guard_time_us(instance->nfc, FELICA_GUARD_TIME_US); nfc_set_fdt_poll_fc(instance->nfc, FELICA_FDT_POLL_FC); nfc_set_fdt_poll_poll_us(instance->nfc, FELICA_POLL_POLL_MIN_US); + + mbedtls_des3_init(&instance->auth.des_context); instance->data = felica_alloc(); instance->felica_event.data = &instance->felica_event_data; @@ -40,6 +47,7 @@ static void felica_poller_free(FelicaPoller* instance) { furi_assert(instance->rx_buffer); furi_assert(instance->data); + mbedtls_des3_free(&instance->auth.des_context); bit_buffer_free(instance->tx_buffer); bit_buffer_free(instance->rx_buffer); felica_free(instance->data); @@ -55,6 +63,212 @@ static void instance->context = context; } +NfcCommand felica_poller_state_handler_idle(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Idle"); + felica_reset(instance->data); + instance->state = FelicaPollerStateActivated; + + return NfcCommandContinue; +} + +NfcCommand felica_poller_state_handler_activate(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Activate"); + + NfcCommand command = NfcCommandContinue; + + FelicaError error = felica_poller_activate(instance, instance->data); + if(error == FelicaErrorNone) { + furi_hal_random_fill_buf(instance->data->data.fs.rc.data, FELICA_DATA_BLOCK_SIZE); + + instance->felica_event.type = FelicaPollerEventTypeRequestAuthContext; + instance->felica_event_data.auth_context = &instance->auth.context; + + instance->callback(instance->general_event, instance->context); + + bool skip_auth = instance->auth.context.skip_auth; + instance->state = skip_auth ? FelicaPollerStateReadBlocks : + FelicaPollerStateAuthenticateInternal; + } else if(error != FelicaErrorTimeout) { + instance->felica_event.type = FelicaPollerEventTypeError; + instance->felica_event_data.error = error; + instance->state = FelicaPollerStateReadFailed; + } + return command; +} + +NfcCommand felica_poller_state_handler_auth_internal(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Auth Internal"); + + felica_calculate_session_key( + &instance->auth.des_context, + instance->auth.context.card_key.data, + instance->data->data.fs.rc.data, + instance->auth.session_key.data); + + instance->state = FelicaPollerStateReadBlocks; + + uint8_t blocks[3] = {FELICA_BLOCK_INDEX_RC, 0, 0}; + FelicaPollerWriteCommandResponse* tx_resp; + do { + FelicaError error = felica_poller_write_blocks( + instance, 1, blocks, instance->data->data.fs.rc.data, &tx_resp); + if((error != FelicaErrorNone) || (tx_resp->SF1 != 0) || (tx_resp->SF2 != 0)) break; + + blocks[0] = FELICA_BLOCK_INDEX_ID; + blocks[1] = FELICA_BLOCK_INDEX_WCNT; + blocks[2] = FELICA_BLOCK_INDEX_MAC_A; + FelicaPollerReadCommandResponse* rx_resp; + error = felica_poller_read_blocks(instance, sizeof(blocks), blocks, &rx_resp); + if(error != FelicaErrorNone || rx_resp->SF1 != 0 || rx_resp->SF2 != 0) break; + + if(felica_check_mac( + &instance->auth.des_context, + instance->auth.session_key.data, + instance->data->data.fs.rc.data, + blocks, + rx_resp->block_count, + rx_resp->data)) { + instance->auth.context.auth_status.internal = true; + instance->data->data.fs.wcnt.SF1 = 0; + instance->data->data.fs.wcnt.SF2 = 0; + memcpy( + instance->data->data.fs.wcnt.data, + rx_resp->data + FELICA_DATA_BLOCK_SIZE, + FELICA_DATA_BLOCK_SIZE); + instance->state = FelicaPollerStateAuthenticateExternal; + } + } while(false); + + return NfcCommandContinue; +} + +NfcCommand felica_poller_state_handler_auth_external(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Auth External"); + instance->state = FelicaPollerStateReadBlocks; + uint8_t blocks[2]; + + instance->data->data.fs.state.data[0] = 1; + FelicaAuthentication* auth = &instance->auth; + felica_calculate_mac_write( + &auth->des_context, + auth->session_key.data, + instance->data->data.fs.rc.data, + instance->data->data.fs.wcnt.data, + instance->data->data.fs.state.data, + instance->data->data.fs.mac_a.data); + + memcpy(instance->data->data.fs.mac_a.data + 8, instance->data->data.fs.wcnt.data, 3); //-V1086 + + uint8_t tx_data[FELICA_DATA_BLOCK_SIZE * 2]; + memcpy(tx_data, instance->data->data.fs.state.data, FELICA_DATA_BLOCK_SIZE); + memcpy( + tx_data + FELICA_DATA_BLOCK_SIZE, + instance->data->data.fs.mac_a.data, + FELICA_DATA_BLOCK_SIZE); + + do { + blocks[0] = FELICA_BLOCK_INDEX_STATE; + blocks[1] = FELICA_BLOCK_INDEX_MAC_A; + FelicaPollerWriteCommandResponse* tx_resp; + FelicaError error = felica_poller_write_blocks(instance, 2, blocks, tx_data, &tx_resp); + if(error != FelicaErrorNone || tx_resp->SF1 != 0 || tx_resp->SF2 != 0) break; + + FelicaPollerReadCommandResponse* rx_resp; + error = felica_poller_read_blocks(instance, 1, blocks, &rx_resp); + if(error != FelicaErrorNone || rx_resp->SF1 != 0 || rx_resp->SF2 != 0) break; + + instance->data->data.fs.state.SF1 = 0; + instance->data->data.fs.state.SF2 = 0; + memcpy(instance->data->data.fs.state.data, rx_resp->data, FELICA_DATA_BLOCK_SIZE); + instance->auth.context.auth_status.external = instance->data->data.fs.state.data[0]; + } while(false); + instance->state = FelicaPollerStateReadBlocks; + return NfcCommandContinue; +} + +NfcCommand felica_poller_state_handler_read_blocks(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Read Blocks"); + + uint8_t block_count = 1; + uint8_t block_list[4] = {0, 0, 0, 0}; + block_list[0] = instance->block_index; + + instance->block_index++; + if(instance->block_index == FELICA_BLOCK_INDEX_REG + 1) { + instance->block_index = FELICA_BLOCK_INDEX_RC; + } else if(instance->block_index == FELICA_BLOCK_INDEX_MC + 1) { + instance->block_index = FELICA_BLOCK_INDEX_WCNT; + } else if(instance->block_index == FELICA_BLOCK_INDEX_STATE + 1) { + instance->block_index = FELICA_BLOCK_INDEX_CRC_CHECK; + } + + FelicaPollerReadCommandResponse* response; + FelicaError error = felica_poller_read_blocks(instance, block_count, block_list, &response); + if(error == FelicaErrorNone) { + block_count = (response->SF1 == 0) ? response->block_count : block_count; + uint8_t* data_ptr = + instance->data->data.dump + instance->data->blocks_total * sizeof(FelicaBlock); + + *data_ptr++ = response->SF1; + *data_ptr++ = response->SF2; + + if(response->SF1 == 0) { + uint8_t* response_data_ptr = response->data; + instance->data->blocks_read++; + memcpy(data_ptr, response_data_ptr, FELICA_DATA_BLOCK_SIZE); + } else { + memset(data_ptr, 0, FELICA_DATA_BLOCK_SIZE); + } + instance->data->blocks_total++; + + if(instance->data->blocks_total == FELICA_BLOCKS_TOTAL_COUNT) { + instance->state = FelicaPollerStateReadSuccess; + } + } else { + instance->felica_event.type = FelicaPollerEventTypeError; + instance->felica_event_data.error = error; + instance->state = FelicaPollerStateReadFailed; + } + + return NfcCommandContinue; +} + +NfcCommand felica_poller_state_handler_read_success(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Read Success"); + + if(!instance->auth.context.auth_status.internal || + !instance->auth.context.auth_status.external) { + instance->data->blocks_read--; + instance->felica_event.type = FelicaPollerEventTypeIncomplete; + } else { + memcpy( + instance->data->data.fs.ck.data, + instance->auth.context.card_key.data, + FELICA_DATA_BLOCK_SIZE); + instance->felica_event.type = FelicaPollerEventTypeReady; + } + + instance->felica_event_data.error = FelicaErrorNone; + return instance->callback(instance->general_event, instance->context); +} + +NfcCommand felica_poller_state_handler_read_failed(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Read Fail"); + instance->callback(instance->general_event, instance->context); + + return NfcCommandStop; +} + +static const FelicaPollerReadHandler felica_poller_handler[FelicaPollerStateNum] = { + [FelicaPollerStateIdle] = felica_poller_state_handler_idle, + [FelicaPollerStateActivated] = felica_poller_state_handler_activate, + [FelicaPollerStateAuthenticateInternal] = felica_poller_state_handler_auth_internal, + [FelicaPollerStateAuthenticateExternal] = felica_poller_state_handler_auth_external, + [FelicaPollerStateReadBlocks] = felica_poller_state_handler_read_blocks, + [FelicaPollerStateReadSuccess] = felica_poller_state_handler_read_success, + [FelicaPollerStateReadFailed] = felica_poller_state_handler_read_failed, +}; + static NfcCommand felica_poller_run(NfcGenericEvent event, void* context) { furi_assert(context); furi_assert(event.protocol == NfcProtocolInvalid); @@ -65,24 +279,7 @@ static NfcCommand felica_poller_run(NfcGenericEvent event, void* context) { NfcCommand command = NfcCommandContinue; if(nfc_event->type == NfcEventTypePollerReady) { - if(instance->state != FelicaPollerStateActivated) { - FelicaError error = felica_poller_activate(instance, instance->data); - if(error == FelicaErrorNone) { - instance->felica_event.type = FelicaPollerEventTypeReady; - instance->felica_event_data.error = error; - command = instance->callback(instance->general_event, instance->context); - } else { - instance->felica_event.type = FelicaPollerEventTypeError; - instance->felica_event_data.error = error; - command = instance->callback(instance->general_event, instance->context); - // Add delay to switch context - furi_delay_ms(100); - } - } else { - instance->felica_event.type = FelicaPollerEventTypeReady; - instance->felica_event_data.error = FelicaErrorNone; - command = instance->callback(instance->general_event, instance->context); - } + command = felica_poller_handler[instance->state](instance); } return command; diff --git a/lib/nfc/protocols/felica/felica_poller.h b/lib/nfc/protocols/felica/felica_poller.h index b0e6778a0..d4366e767 100644 --- a/lib/nfc/protocols/felica/felica_poller.h +++ b/lib/nfc/protocols/felica/felica_poller.h @@ -19,14 +19,33 @@ typedef struct FelicaPoller FelicaPoller; */ typedef enum { FelicaPollerEventTypeError, /**< An error occured during activation procedure. */ - FelicaPollerEventTypeReady, /**< The card was activated by the poller. */ + FelicaPollerEventTypeReady, /**< The card was activated and fully read by the poller. */ + FelicaPollerEventTypeIncomplete, /**< The card was activated and partly read by the poller. */ + FelicaPollerEventTypeRequestAuthContext, /**< Authentication context was requested by poller. */ } FelicaPollerEventType; +/** + * @brief Stucture for holding Felica session key which is calculated from rc and ck. +*/ +typedef struct { + uint8_t data[FELICA_DATA_BLOCK_SIZE]; +} FelicaSessionKey; + +/** + * @brief Structure used to hold authentication related fields. +*/ +typedef struct { + mbedtls_des3_context des_context; /**< Context for mbedtls des functions. */ + FelicaSessionKey session_key; /**< Calculated session key. */ + FelicaAuthenticationContext context; /**< Public auth context provided to upper levels. */ +} FelicaAuthentication; + /** * @brief Felica poller event data. */ typedef union { FelicaError error; /**< Error code indicating card activation fail reason. */ + FelicaAuthenticationContext* auth_context; /**< Authentication context to be filled by user. */ } FelicaPollerEventData; /** diff --git a/lib/nfc/protocols/felica/felica_poller_i.c b/lib/nfc/protocols/felica/felica_poller_i.c index bfbf150ef..f7726be32 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.c +++ b/lib/nfc/protocols/felica/felica_poller_i.c @@ -3,6 +3,11 @@ #include #define TAG "FelicaPoller" +#define FELICA_CMD_READ_WITHOUT_ENCRYPTION (0x06U) +#define FELICA_CMD_WRITE_WITHOUT_ENCRYPTION (0x08U) + +#define FELICA_SERVICE_RW_ACCESS (0x0009U) +#define FELICA_SERVICE_RO_ACCESS (0x000BU) static FelicaError felica_poller_process_error(NfcError error) { switch(error) { @@ -15,8 +20,8 @@ static FelicaError felica_poller_process_error(NfcError error) { } } -static FelicaError felica_poller_frame_exchange( - FelicaPoller* instance, +FelicaError felica_poller_frame_exchange( + const FelicaPoller* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer, uint32_t fwt) { @@ -93,11 +98,105 @@ FelicaError felica_poller_polling( return error; } +static void felica_poller_prepare_tx_buffer( + const FelicaPoller* instance, + const uint8_t command, + const uint16_t service_code, + const uint8_t block_count, + const uint8_t* const blocks, + const uint8_t data_block_count, + const uint8_t* data) { + FelicaCommandHeader cmd = { + .code = command, + .idm = instance->data->idm, + .service_num = 1, + .service_code = service_code, + .block_count = block_count, + }; + + FelicaBlockListElement block_list[4] = {{0}, {0}, {0}, {0}}; + for(uint8_t i = 0; i < block_count; i++) { + block_list[i].length = 1; + block_list[i].block_number = blocks[i]; + } + + uint8_t block_list_count = block_count; + uint8_t block_list_size = block_list_count * sizeof(FelicaBlockListElement); + uint8_t total_size = sizeof(FelicaCommandHeader) + 1 + block_list_size + + data_block_count * FELICA_DATA_BLOCK_SIZE; + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_byte(instance->tx_buffer, total_size); + bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)&cmd, sizeof(FelicaCommandHeader)); + bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)&block_list, block_list_size); + + if(data_block_count != 0) { + bit_buffer_append_bytes( + instance->tx_buffer, data, data_block_count * FELICA_DATA_BLOCK_SIZE); + } +} + +FelicaError felica_poller_read_blocks( + FelicaPoller* instance, + const uint8_t block_count, + const uint8_t* const block_numbers, + FelicaPollerReadCommandResponse** const response_ptr) { + furi_assert(instance); + furi_assert(block_count <= 4); + furi_assert(block_numbers); + furi_assert(response_ptr); + + felica_poller_prepare_tx_buffer( + instance, + FELICA_CMD_READ_WITHOUT_ENCRYPTION, + FELICA_SERVICE_RO_ACCESS, + block_count, + block_numbers, + 0, + NULL); + bit_buffer_reset(instance->rx_buffer); + + FelicaError error = felica_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, FELICA_POLLER_POLLING_FWT); + if(error == FelicaErrorNone) { + *response_ptr = (FelicaPollerReadCommandResponse*)bit_buffer_get_data(instance->rx_buffer); + } + return error; +} + +FelicaError felica_poller_write_blocks( + const FelicaPoller* instance, + const uint8_t block_count, + const uint8_t* const block_numbers, + const uint8_t* data, + FelicaPollerWriteCommandResponse** const response_ptr) { + furi_assert(instance); + furi_assert(block_count <= 2); + furi_assert(block_numbers); + furi_assert(data); + furi_assert(response_ptr); + + felica_poller_prepare_tx_buffer( + instance, + FELICA_CMD_WRITE_WITHOUT_ENCRYPTION, + FELICA_SERVICE_RW_ACCESS, + block_count, + block_numbers, + block_count, + data); + bit_buffer_reset(instance->rx_buffer); + + FelicaError error = felica_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, FELICA_POLLER_POLLING_FWT); + if(error == FelicaErrorNone) { + *response_ptr = + (FelicaPollerWriteCommandResponse*)bit_buffer_get_data(instance->rx_buffer); + } + return error; +} + FelicaError felica_poller_activate(FelicaPoller* instance, FelicaData* data) { furi_assert(instance); - felica_reset(data); - FelicaError ret; do { diff --git a/lib/nfc/protocols/felica/felica_poller_i.h b/lib/nfc/protocols/felica/felica_poller_i.h index 3bd4d91f9..f7df4c845 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.h +++ b/lib/nfc/protocols/felica/felica_poller_i.h @@ -1,7 +1,6 @@ #pragma once #include "felica_poller.h" - #include #ifdef __cplusplus @@ -18,11 +17,20 @@ extern "C" { typedef enum { FelicaPollerStateIdle, FelicaPollerStateActivated, + FelicaPollerStateAuthenticateInternal, + FelicaPollerStateAuthenticateExternal, + FelicaPollerStateReadBlocks, + FelicaPollerStateReadSuccess, + FelicaPollerStateReadFailed, + + FelicaPollerStateNum } FelicaPollerState; struct FelicaPoller { Nfc* nfc; FelicaPollerState state; + FelicaAuthentication auth; + FelicaData* data; BitBuffer* tx_buffer; BitBuffer* rx_buffer; @@ -31,6 +39,7 @@ struct FelicaPoller { FelicaPollerEvent felica_event; FelicaPollerEventData felica_event_data; NfcGenericCallback callback; + uint8_t block_index; void* context; }; @@ -46,13 +55,106 @@ typedef struct { uint8_t request_data[2]; } FelicaPollerPollingResponse; +typedef struct { + uint8_t service_code : 4; + uint8_t access_mode : 3; + uint8_t length : 1; + uint8_t block_number; +} FelicaBlockListElement; + +#pragma pack(push, 1) +typedef struct { + uint8_t code; + FelicaIDm idm; + uint8_t service_num; + uint16_t service_code; + uint8_t block_count; +} FelicaCommandHeader; +#pragma pack(pop) + +typedef struct { + uint8_t length; + uint8_t response_code; + FelicaIDm idm; + uint8_t SF1; + uint8_t SF2; + uint8_t block_count; + uint8_t data[]; +} FelicaPollerReadCommandResponse; + +typedef struct { + uint8_t length; + uint8_t response_code; + FelicaIDm idm; + uint8_t SF1; + uint8_t SF2; +} FelicaPollerWriteCommandResponse; + const FelicaData* felica_poller_get_data(FelicaPoller* instance); +/** + * @brief Performs felica polling operation as part of the activation process + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] cmd Pointer to polling command structure + * @param[out] resp Pointer to the response structure + * @return FelicaErrorNone on success, an error code on failure +*/ FelicaError felica_poller_polling( FelicaPoller* instance, const FelicaPollerPollingCommand* cmd, FelicaPollerPollingResponse* resp); +/** + * @brief Performs felica read operation for blocks provided as parameters + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] block_count Amount of blocks involved in reading procedure + * @param[in] block_numbers Array with block indexes according to felica docs + * @param[out] response_ptr Pointer to the response structure + * @return FelicaErrorNone on success, an error code on failure. +*/ +FelicaError felica_poller_read_blocks( + FelicaPoller* instance, + const uint8_t block_count, + const uint8_t* const block_numbers, + FelicaPollerReadCommandResponse** const response_ptr); + +/** + * @brief Performs felica write operation with data provided as parameters + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] block_count Amount of blocks involved in writing procedure + * @param[in] block_numbers Array with block indexes according to felica docs + * @param[in] data Data of blocks provided in block_numbers + * @param[out] response_ptr Pointer to the response structure + * @return FelicaErrorNone on success, an error code on failure. +*/ +FelicaError felica_poller_write_blocks( + const FelicaPoller* instance, + const uint8_t block_count, + const uint8_t* const block_numbers, + const uint8_t* data, + FelicaPollerWriteCommandResponse** const response_ptr); + +/** + * @brief Perform frame exchange procedure. + * + * Prepares data for sending by adding crc, after that performs + * low level calls to send package data to the card + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer with data to be transmitted + * @param[out] rx_buffer pointer to the buffer with received data from card + * @param[in] fwt timeout window + * @return FelicaErrorNone on success, an error code on failure. + */ +FelicaError felica_poller_frame_exchange( + const FelicaPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt); + #ifdef __cplusplus } #endif diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 7944bc58f..6e597e7d0 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.6,, +Version,+,60.7,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 8fd9f4c9b..09b8b9ee8 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.6,, +Version,+,60.7,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -133,6 +133,8 @@ Header,+,lib/nfc/nfc_device.h,, Header,+,lib/nfc/nfc_listener.h,, Header,+,lib/nfc/nfc_poller.h,, Header,+,lib/nfc/nfc_scanner.h,, +Header,+,lib/nfc/protocols/felica/felica.h,, +Header,+,lib/nfc/protocols/felica/felica_poller.h,, Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a.h,, Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h,, Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h,, @@ -987,6 +989,22 @@ Function,-,fdim,double,"double, double" Function,-,fdimf,float,"float, float" Function,-,fdiml,long double,"long double, long double" Function,-,fdopen,FILE*,"int, const char*" +Function,+,felica_alloc,FelicaData*, +Function,+,felica_calculate_mac_write,void,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t*, uint8_t*" +Function,+,felica_calculate_session_key,void,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, uint8_t*" +Function,+,felica_check_mac,_Bool,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t, uint8_t*" +Function,+,felica_copy,void,"FelicaData*, const FelicaData*" +Function,+,felica_free,void,FelicaData* +Function,+,felica_get_base_data,FelicaData*,const FelicaData* +Function,+,felica_get_device_name,const char*,"const FelicaData*, NfcDeviceNameType" +Function,+,felica_get_uid,const uint8_t*,"const FelicaData*, size_t*" +Function,+,felica_is_equal,_Bool,"const FelicaData*, const FelicaData*" +Function,+,felica_load,_Bool,"FelicaData*, FlipperFormat*, uint32_t" +Function,+,felica_poller_activate,FelicaError,"FelicaPoller*, FelicaData*" +Function,+,felica_reset,void,FelicaData* +Function,+,felica_save,_Bool,"const FelicaData*, FlipperFormat*" +Function,+,felica_set_uid,_Bool,"FelicaData*, const uint8_t*, size_t" +Function,+,felica_verify,_Bool,"FelicaData*, const FuriString*" Function,-,feof,int,FILE* Function,-,feof_unlocked,int,FILE* Function,-,ferror,int,FILE* @@ -3739,6 +3757,7 @@ Variable,+,message_red_255,const NotificationMessage, Variable,+,message_sound_off,const NotificationMessage, Variable,+,message_vibro_off,const NotificationMessage, Variable,+,message_vibro_on,const NotificationMessage, +Variable,-,nfc_device_felica,const NfcDeviceBase, Variable,-,nfc_device_mf_classic,const NfcDeviceBase, Variable,-,nfc_device_mf_desfire,const NfcDeviceBase, Variable,-,nfc_device_mf_ultralight,const NfcDeviceBase, From 744ef8138e88fa23defa30ee9afed23457648947 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 10 Apr 2024 15:26:47 +0300 Subject: [PATCH 092/129] subghz add manually fixes --- applications/main/subghz/scenes/subghz_scene_set_type.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index 90b30f076..b88e449b1 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -445,8 +445,8 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { .type = GenKeeloq, .mod = "FM476", .freq = 434420000, - .keeloq.serial = key & 0x0FFFFFFF, - .keeloq.btn = 0x04, + .keeloq.serial = (key & 0x0000FFFF) | 0x07150000, + .keeloq.btn = 0x02, .keeloq.cnt = 0x03, .keeloq.manuf = "Sommer(fsk476)"}; break; @@ -455,8 +455,8 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { .type = GenKeeloq, .mod = "FM476", .freq = 868800000, - .keeloq.serial = key & 0x0FFFFFFF, - .keeloq.btn = 0x04, + .keeloq.serial = (key & 0x0000FFFF) | 0x07150000, + .keeloq.btn = 0x02, .keeloq.cnt = 0x03, .keeloq.manuf = "Sommer(fsk476)"}; break; From 2443a702c97e82a381b0d6b140aba63a78f0aba4 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 10 Apr 2024 15:38:57 +0300 Subject: [PATCH 093/129] update nfc parser by zacharyweiss --- .../nfc/plugins/supported_cards/charliecard.c | 985 +++++++++++------- 1 file changed, 601 insertions(+), 384 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/charliecard.c b/applications/main/nfc/plugins/supported_cards/charliecard.c index 7a405fffb..9880ca3ad 100644 --- a/applications/main/nfc/plugins/supported_cards/charliecard.c +++ b/applications/main/nfc/plugins/supported_cards/charliecard.c @@ -23,29 +23,41 @@ * — Reverse engineer passes (sectors 4 & 5?), impl. * — Infer transaction flag meanings * — Infer remaining unknown bytes in the balance sectors (2 & 3) - * – ASCII art &/or unified read function for the balance sectors, - * to improve readability / interpretability by others? * — Improve string output formatting, esp. of transaction log + * — Mapping of buses to garages, and subsequently, route subsets via + * http://roster.transithistory.org/ data + * — Mapping of stations to lines + * — Add'l data fields for side of station fare gates are on? Some stations + * separate inbound & outbound sides, so direction could be inferred + * from gates used. * — Continually gather data on fare gate ID mappings, update as collected; * check locations this might be scrapable / inferrable from: * [X] MBTA GTFS spec (https://www.mbta.com/developers/gtfs) features & IDs * seem too-coarse-grained & uncorrelated - * [X] MBTA ArcGIS (https://mbta-massdot.opendata.arcgis.com/) & Tableau (https://public.tableau.com/app/profile/mbta.office.of.performance.management.and.innovation/vizzes) + * [X] MBTA ArcGIS (https://mbta-massdot.opendata.arcgis.com/) & Tableau + * (https://public.tableau.com/app/profile/mbta.office.of.performance.management.and.innovation/vizzes) * files don't seem to have anything of that resolution (only down to ridership by station) * [X] (skim of) MBTA public GitHub (https://github.com/mbta) repos make no reference to fare-gate-level data * [X] (skim of) MBTA public engineering docs (https://www.mbta.com/engineering) unfruitful; - * Closest mention spotted is 2014 "Ridership and Service Statistics" (https://cdn.mbta.com/sites/default/files/fmcb-meeting-docs/reports-policies/2014-07-mbta-bluebook-ed14.pdf) + * Closest mention spotted is 2014 "Ridership and Service Statistics" + * (https://cdn.mbta.com/sites/default/files/fmcb-meeting-docs/reports-policies/2014-07-mbta-bluebook-ed14.pdf) * where on pg.40, "Equipment at Stations" is enumerated, and fare gates counts are given, - * listed as "AFC Gates" (presumably standing for "Automated Fare Control") + * listed as "AFC Gates" (presumably standing for "Automated Fare Collection") * [X] Josiah Zachery criminal trial public evidence — convicted partially on * data on his CharlieCard, appeals partially on basis of legality of this search. * Prev. court case (gag order mentioned in preamble) leaked some data in the files * entered into evidence. Seemingly did not happen here; fare gate IDs unmentioned, * only ever the nature of stored/saved data and methods of retrieval. - * Appelate case dockets 2019-P-0401, SJC-12952, SJ-2017-0390 (https://www.ma-appellatecourts.org/party) - * Trial court case 04/02/2015 #1584CR10265 @Suffolk County Criminal Superior Court (https://www.masscourts.org/eservices/home.page.16) - * [ ] FOIA / public records request? (https://massachusettsdot.mycusthelp.com/WEBAPP/_rs/(S(tbcygdlm0oojy35p1wv0y2y5))/supporthome.aspx) - * [ ] MBTA data blog? (https://www.massdottracker.com/datablog/) + * Appelate case dockets 2019-P-0401, SJC-12952, SJ-2017-0390 + * (https://www.ma-appellatecourts.org/party) + * Trial court indictment 04/02/2015, Case# 1584CR10265 @Suffolk County Criminal Superior Court + * (https://www.masscourts.org/eservices/home.page.16) + * [ ] FOIA / public records request? + * (https://massachusettsdot.mycusthelp.com/WEBAPP/_rs/(S(tbcygdlm0oojy35p1wv0y2y5))/supporthome.aspx) + * [X] MBTA data blog? (https://www.massdottracker.com/datablog/) + * [ ] MassDOT developers Google group? (https://groups.google.com/g/massdotdevelopers) + * [X] preexisting posts + * [ ] ask directly? * [ ] Other? * * This program is free software: you can redistribute it and/or modify it @@ -82,12 +94,8 @@ // timestep is one minute #define CHARLIE_TIME_DELTA_SECS 60 #define CHARLIE_END_VALID_DELTA_SECS 60 * 8 -#define CHARLIE_N_TRIP_HISTORY 10 - -enum CharlieActiveSector { - CHARLIE_ACTIVE_SECTOR_2, - CHARLIE_ACTIVE_SECTOR_3, -}; +#define CHARLIE_N_TRANSACTION_HISTORY 10 +#define CHARLIE_N_PASSES 4 typedef struct { uint64_t a; @@ -138,8 +146,27 @@ typedef struct { uint16_t gate; uint8_t g_flag; Money fare; - uint8_t f_flag; -} Trip; + uint16_t f_flag; +} Transaction; + +typedef struct { + bool valid; + uint16_t pre; + uint16_t post; + DateTime date; +} Pass; + +typedef struct { + uint16_t n_uses; + uint8_t active_balance_sector; +} CounterSector; + +typedef struct { + Money balance; + uint16_t type; + DateTime issued; + DateTime end_validity; +} BalanceSector; // IdMapping approach borrowed from Jeremy Cooper's 'clipper.c' typedef struct { @@ -147,14 +174,14 @@ typedef struct { const char* name; } IdMapping; -// this should be a complete accounting of types, +// this should be a complete accounting of types, (1 and 7 day pass types maybe missing?) static const IdMapping charliecard_types[] = { // Regular card types {.id = 367, .name = "Adult"}, {.id = 366, .name = "SV Adult"}, {.id = 418, .name = "Student"}, {.id = 419, .name = "Senior"}, - {.id = 420, .name = "Tap"}, + {.id = 420, .name = "TAP"}, {.id = 417, .name = "Blind"}, {.id = 426, .name = "Child"}, {.id = 410, .name = "Employee ID Without Passback"}, @@ -164,16 +191,16 @@ static const IdMapping charliecard_types[] = { // Passes {.id = 135, .name = "30 Day Local Bus Pass"}, - {.id = 136, .name = "30 Day Inner Express Bus Pass"}, // - {.id = 137, .name = "30 Day Outer Express Bus Pass"}, // - {.id = 138, .name = "30 Day LinkPass"}, // - {.id = 139, .name = "30 Day Senior LinkPass"}, // + {.id = 136, .name = "30 Day Inner Express Bus Pass"}, + {.id = 137, .name = "30 Day Outer Express Bus Pass"}, + {.id = 138, .name = "30 Day LinkPass"}, + {.id = 139, .name = "30 Day Senior LinkPass"}, {.id = 148, .name = "30 Day TAP LinkPass"}, {.id = 150, .name = "Monthly Student LinkPass"}, - {.id = 424, .name = "Monthly TAP LinkPass"}, // 0b0110101000 - {.id = 425, .name = "Monthly Senior LinkPass"}, // 0b0110101001 - {.id = 421, .name = "Senior TAP/Permit"}, // 0b0110100101 - {.id = 422, .name = "Senior TAP/Permit 30 Days"}, // 0b0110100110 + {.id = 424, .name = "Monthly TAP LinkPass"}, + {.id = 425, .name = "Monthly Senior LinkPass"}, + {.id = 421, .name = "Senior TAP/Permit"}, + {.id = 422, .name = "Senior TAP/Permit 30 Days"}, // Commuter rail passes {.id = 166, .name = "30 Day Commuter Rail Zone 1A Pass"}, @@ -388,9 +415,7 @@ static const IdMapping charliecard_fare_gate_ids[] = { {.id = 6647, .name = "Malden Center"}, {.id = 6648, .name = "Malden Center"}, // Chinatown - {.id = 6704, - .name = - "Malden Center"}, // Entry error? Placed after "Chinatown" divider, but with name Malden Center + {.id = 6704, .name = "Chinatown"}, {.id = 6705, .name = "Chinatown"}, {.id = 2099, .name = "Chinatown"}, {.id = 7003, .name = "Chinatown"}, @@ -471,7 +496,7 @@ static const IdMapping charliecard_fare_gate_ids[] = { {.id = 7016, .name = "Forest Hills"}, {.id = 6950, .name = "Forest Hills"}, {.id = 6951, .name = "Forest Hills"}, - {.id = 604, .name = "Forest Hills"}, // Entry error? + {.id = 604, .name = "Forest Hills"}, {.id = 7096, .name = "Forest Hills"}, // South Station {.id = 7039, .name = "South Station"}, @@ -546,7 +571,7 @@ static const IdMapping charliecard_fare_gate_ids[] = { {.id = 2010, .name = "Wood Island"}, {.id = 6971, .name = "Wood Island"}, // Orient Heights - {.id = 6621, .name = "Orient Heights"}, // marked as needs checking + {.id = 6621, .name = "Orient Heights"}, {.id = 6622, .name = "Orient Heights"}, {.id = 6623, .name = "Orient Heights"}, {.id = 2014, .name = "Orient Heights"}, @@ -578,8 +603,13 @@ static const IdMapping charliecard_fare_gate_ids[] = { }; static const size_t kNumFareGateIds = COUNT_OF(charliecard_fare_gate_ids); +// ********************************************************** +// ********************* MISC HELPERS *********************** +// ********************************************************** + static const uint8_t* pos_to_ptr(const MfClassicData* data, uint8_t sector_num, uint8_t block_num, uint8_t byte_num) { + // returns pointer to specified sector/block/byte of MFClassic card data uint8_t block_offset = mf_classic_get_first_block_num_of_sector(sector_num); return &data->block[block_offset + block_num].data[byte_num]; } @@ -590,16 +620,29 @@ static uint64_t pos_to_num( uint8_t block_num, uint8_t byte_num, uint8_t byte_len) { + // returns numeric values at specified card location, for given byte length. + // assumes big endian. return bit_lib_bytes_to_num_be(pos_to_ptr(data, sector_num, block_num, byte_num), byte_len); } static DateTime dt_delta(DateTime dt, uint64_t delta_secs) { + // returns shifted DateTime, from initial DateTime and time offset in seconds DateTime dt_shifted = {0}; datetime_timestamp_to_datetime(datetime_datetime_to_timestamp(&dt) + delta_secs, &dt_shifted); return dt_shifted; } +static bool dt_ge(DateTime dt1, DateTime dt2) { + // compares two DateTimes + return datetime_datetime_to_timestamp(&dt1) >= datetime_datetime_to_timestamp(&dt2); +} + +static bool dt_eq(DateTime dt1, DateTime dt2) { + // compares two DateTimes + return datetime_datetime_to_timestamp(&dt1) == datetime_datetime_to_timestamp(&dt2); +} + static bool get_map_item(uint16_t id, const IdMapping* map, size_t sz, const char** out) { // code borrowed from Jeremy Cooper's 'clipper.c'. Used as follows: // const char* s; if(!get_map_item(_,_,_,&s)) {s="Default str";} @@ -614,8 +657,533 @@ static bool get_map_item(uint16_t id, const IdMapping* map, size_t sz, const cha return false; } +uint32_t time_now() { + return furi_hal_rtc_get_timestamp(); +} + +static bool is_debug() { + return furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug); +} + +// ********************************************************** +// ******************** FIELD PARSING *********************** +// ********************************************************** + +static Money money_parse( + const MfClassicData* data, + uint8_t sector_num, + uint8_t block_num, + uint8_t byte_num) { + // CharlieCards store all money values in two bytes as half-cents + // bitmask removes sign/flag, bitshift converts half-cents to cents, div & mod yield dollars & cents + uint16_t amt = (pos_to_num(data, sector_num, block_num, byte_num, 2) & 0x7FFF) >> 1; + return (Money){amt / 100, amt % 100}; +} + +static DateTime + date_parse(const MfClassicData* data, uint8_t sector_num, uint8_t block_num, uint8_t byte_num) { + // Dates are 3 bytes, in minutes since 2003/1/1 ("CHARLIE_EPOCH") + uint32_t ts_charlie = pos_to_num(data, sector_num, block_num, byte_num, 3); + return dt_delta(CHARLIE_EPOCH, ts_charlie * CHARLIE_TIME_DELTA_SECS); +} + +static DateTime end_validity_parse( + const MfClassicData* data, + uint8_t sector_num, + uint8_t block_num, + uint8_t byte_num) { + // End validity field is weird; shares first byte with another variable (the card type field), + // occupying the last 5 bits (and subsequent two bytes), hence bitmask + uint32_t ts_charlie_ev = pos_to_num(data, sector_num, block_num, byte_num, 3) & 0x1FFFFF; + + // additionally, instead of minute deltas, is in 8 minute increments + // relative to CHARLIE_EPOCH (2003/1/1), per DEFCON31 researcher's work + return dt_delta(CHARLIE_EPOCH, ts_charlie_ev * CHARLIE_END_VALID_DELTA_SECS); +} + +static Pass + pass_parse(const MfClassicData* data, uint8_t sector_num, uint8_t block_num, uint8_t byte_num) { + // WIP; testing only. Speculating it may be structured as follows + // Sub-byte field divisions not drawn to scale, see code for exact bit offsets + // + // 0 1 2 3 4 5 + // +----.----.----.----+----.----+ + // | uk1 | date | uk2 | + // +----.----.----.----+----.----+ + // + // "Blank" entries are as follows: + // 0 1 2 3 4 5 + // +----.----.----.----.----.----+ + // | 00 20 00 00 00 00 | + // +----.----.----.----.----.----+ + // + // even when not blank, uk1 LSB seems to always be set to 1... + // the sole bit set to 1 on the blank entry seems to divide + // the uk1 and date fields, and is always set to 1 regardless + // same is true of type & end-validity split found in balance sector + // + // likely fields incl + // — type #, + // — a secondary date field (eg start/end, end validity or normal format) + // — ID of FVM from which the pass was loaded + + // check for empty, if so, return struct filled w/ 0s + // (incl "valid" field: hence, "valid" is false-y) + if(pos_to_num(data, sector_num, block_num, byte_num, 6) == 0x002000000000) { + return (Pass){0}; + } + + // const DateTime start = date_parse(data, sector_num, block_num, byte_num + 1); + + const uint16_t pre = pos_to_num(data, sector_num, block_num, byte_num, 2) >> 6; + const uint16_t post = (pos_to_num(data, sector_num, block_num, byte_num + 4, 2) >> 2) & 0x3ff; + + // these values make sense for a date, but implied position of type + // before end validity, as seen in balance sector, doesn't seem + // to produce sensible values + const DateTime date = end_validity_parse(data, sector_num, block_num, byte_num + 1); + + // DateTime start = date_parse(data, sector_num, block_num, byte_num); + // uint16_t type = 0; // pos_to_num(data, sector_num, block_num, byte_num + 3, 2) >> 6; + + return (Pass){true, pre, post, date}; +} + +static Transaction + transaction_parse(const MfClassicData* data, uint8_t sector, uint8_t block, uint8_t byte) { + // This function parses individual transactions. Each transaction packs 7 bytes, stored as follows: + // + // 0 1 2 3 4 5 6 + // +----.----.----+----.--+-+----.----+ + // | date | loc |f| amt | + // +----.----.----+----.--+-+----.----+ + // + // Where date is in the typical format, loc represents the fare gate tapped, and amt is the fare amount. + // Amount appears to contain some flag bits, however, it is unclear what precisely their function is. + // + // Gate ID ("loc") is only the first 13 bits of 0x3:0x5, the final three bits appear to be flags ("f"). + // Least significant flag bit seems to indicate: + // — When f & 1 == 1, fare (the amount by which balance is decremented) + // — When f & 1 == 0, refill (the amount by which balance is incremented) + // MSB (sign bit) of amt seems to serve the same role, just inverted, ie + // — When amt & 0x8000 == 0, fare + // — When amt & 0x8000 == 0x8000, refill + // Only contradiction between the two observed is on cards w/ passes; + // MSB of amt seems to be set for every transaction when (remaining bits of) amt is 0 on a card w/ a pass + // Hence, using f's LSB as method for inferring fare v. refill + // + // Remaining unknown bits: + // — f & 0b100; seems to be set on fares where the card has a pass, and amt is 0 + // — f & 0b010 + // — amt & 1; does not seem to correspond with card type, last transaction, first transaction, refill v. fare, etc + + const DateTime date = date_parse(data, sector, block, byte); + const uint16_t gate = pos_to_num(data, sector, block, byte + 3, 2) >> 3; + const uint8_t g_flag = pos_to_num(data, sector, block, byte + 3, 2) & 0b111; + const Money fare = money_parse(data, sector, block, byte + 5); + const uint16_t f_flag = pos_to_num(data, sector, block, byte + 5, 2) & 0x8001; + return (Transaction){date, gate, g_flag, fare, f_flag}; +} + +// ********************************************************** +// ******************* SECTOR PARSING *********************** +// ********************************************************** + +static uint32_t mfg_sector_parse(const MfClassicData* data) { + // Manufacturer data (Sector 0) + // + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + // +----.----.----.----+----+----.----.----.----+----+----.----.----.----.----+----+ + // 0x000 | UID | rc | 88 04 00 C8 | uk | 00 20 00 00 00 | uk | + // +----.----.----.----+----+----.----.----.----+----+----.----.----.----.----+----+ + // 0x010 | 4E 0F 04 10 04 10 04 10 04 10 04 10 04 10 04 10 | + // +----.----.----.----.----.----.----.----.----.----.----.----.----.----.----.----+ + // 0x020 | ... 00 00 ... | + // +----.----.----.----.----.----.----.----.----.----.----.----.----.----.----.----+ + // + // rc := "redundancy check" (lrc / bcc) + // uk := "unknown" + + size_t uid_len = 0; + const uint8_t* uid = mf_classic_get_uid(data, &uid_len); + const uint32_t card_number = bit_lib_bytes_to_num_be(uid, 4); + + return card_number; +} + +static CounterSector counter_sector_parse(const MfClassicData* data) { + // Trip/transaction counters (Sector 1) + // + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + // +----.----.----.----.----.----.----.----.----.----.----.----.----.----.----.----+ + // 0x040 | 04 10 23 45 66 77 ... 00 00 ... | + // +----.----+----+----.----.----.----.----.----.----.----.----.----.----.----.----+ + // 0x050 | uses1 | uk | ... 00 00 ... | + // +----.----+----+----.----.----.----.----.----.----.----.----.----.----.----.----+ + // 0x060 | uses2 | uk | ... 00 00 ... | + // +----.----+----+----.----.----.----.----.----.----.----.----.----.----.----.----+ + // + // uk := "unknown"; if nonzero, seems to only occupy the first 4 bits (ie, uk & 0xF0 == uk), + // with the remaining 4 zero + + // Card has two sectors (2 & 3) containing balance data, with two + // corresponding trip counters in 0x50:0x51 & 0x60:0x61 (sector 1, byte 0:1 of blocks 1 & 2). + + // The *lower* of the two values *minus one* is the true use count, + // and corresponds to the active balance sector, + // (0x50 counter lower -> sector 2 active, 0x60 counter lower -> 3 active) + // per DEFCON31 researcher's findings + + const uint16_t n_uses1 = pos_to_num(data, 1, 1, 0, 2); + const uint16_t n_uses2 = pos_to_num(data, 1, 2, 0, 2); + + const bool is_sec2_active = n_uses1 <= n_uses2; + const uint8_t active_sector = is_sec2_active ? 2 : 3; + const uint16_t n_uses = (is_sec2_active ? n_uses1 : n_uses2) - 1; + + return (CounterSector){n_uses, active_sector}; +} + +static BalanceSector balance_sector_parse(const MfClassicData* data, uint8_t active_sector) { + // Balance & misc card info (Sector 2 or 3) + // + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + // +----+----.----.----+----.----+----.----.----+----.----+----.----+----+----.----+ + // 0x080 | 11 | date last | loc last| date issued | 65 00 | unknown | 00 | crc | 0x0C0 + // +----+----.----.----+----+----+----+----+----+----.----+----.----+----+----.----+ + // 0x090 | type |end validity| uk | balance | 00 | unknown | crc | 0x0D0 + // +----.----.----.----+----+----.----+----+----.----.----.----.----.----+----.----+ + // 0x0A0 | 20 ... 00 00 ... 04 | crc | 0x0E0 + // +----.----.----.----.----.----.----.----.----.----.----.----.----.----+----.----+ + // + // "Active" balance sector alternates between 2 and 3 + // Last trip/transaction info in balance sector ("date last" & "loc last") + // is also included in transaction log, hence don't bother to read here + // + // Inactive balance sector represent the transaction N-1 version + // (where active sector represents data from transaction N). + + const DateTime issued = date_parse(data, active_sector, 0, 6); + const DateTime end_validity = end_validity_parse(data, active_sector, 1, 1); + // Card type data stored in the first 10bits of block 1 + // (0x90 or 0xD0 depending on active sector) + // bitshift (2bytes = 16 bits) by 6bits for just first 10bits + const uint16_t type = pos_to_num(data, active_sector, 1, 0, 2) >> 6; + const Money bal = money_parse(data, active_sector, 1, 5); + + return (BalanceSector){bal, type, issued, end_validity}; +} + +static Pass* passes_parse(const MfClassicData* data) { + // Passes, speculative (Sectors 4 &/or 5) + // + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + // +----.----.----.----.----.----+----+----.----.----.----.----.----+----+----.----+ + // 0x100 | pass0/2? | 00 | pass1/3? | 00 | crc | 0x140 + // +----.----.----.----.----.----+----+----.----.----.----.----.----+----+----.----+ + // 0x110 | ... 00 00 ... | crc | 0x150 + // +----.----.----.----.----.----.----.----.----.----.----.----.----.----+----.----+ + // 0x120 | ... 00 ... 05 | crc | 0x160 + // +----.----.----.----.----.----.----.----.----.----.----.----.----.----+----.----+ + // + // WIP. Read in all speculative passes into array + // 4 separate fields? active vs inactive sector for 2 passes? + // something else entirely? + + Pass* passes = malloc(sizeof(Pass) * CHARLIE_N_PASSES); + + for(size_t i = 0; i < CHARLIE_N_PASSES; i++) { + passes[i] = pass_parse(data, 4 + (i / 2), 0, (i % 2) * 7); + } + + return passes; +} + +static Transaction* transactions_parse(const MfClassicData* data) { + // Transaction history (Sectors 6–7) + // + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + // +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ + // 0x180 | transaction0 | transaction1 | crc | + // +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ + // ... ... ... ... + // +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ + // 0x1D0 | transaction8 | transaction9 | crc | + // +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ + // 0x1E0 | ... 00 00 ... | crc | + // +----.----.----.----.----.----.----.----.----.----.----.----.----.----+----.----+ + // + // Transactions are not sorted, rather, appear to get overwritten + // sequentially. (eg, sorted modulo array rotation) + + Transaction* transactions = malloc(sizeof(Transaction) * CHARLIE_N_TRANSACTION_HISTORY); + + // Parse each transaction field using some modular math magic to get the offsets: + // move from sector 6 -> 7 after the first 6 transactions + // move a block within a given sector every 2 transactions, reset every 3 blocks (as sector has changed) + // alternate between a start byte of 0 and 7 with every iteration + for(size_t i = 0; i < CHARLIE_N_TRANSACTION_HISTORY; i++) { + transactions[i] = transaction_parse(data, 6 + (i / 6), (i / 2) % 3, (i % 2) * 7); + } + + // Iterate through the array to find the maximum (newest) date value + int max_idx = 0; + for(int i = 1; i < CHARLIE_N_TRANSACTION_HISTORY; i++) { + if(dt_ge(transactions[i].date, transactions[max_idx].date)) { + max_idx = i; + } + } + + // Sort by rotating + for(int r = 0; r < (max_idx + 1); r++) { + // Store the first element + Transaction temp = transactions[0]; + // Shift elements to the left + for(int i = 0; i < CHARLIE_N_TRANSACTION_HISTORY - 1; i++) { + transactions[i] = transactions[i + 1]; + } + // Move the first element to the last + transactions[CHARLIE_N_TRANSACTION_HISTORY - 1] = temp; + } + + // Reverse order, such that newest is first, oldest last + for(int i = 0; i < CHARLIE_N_TRANSACTION_HISTORY / 2; i++) { + // Swap elements at index i and size - i - 1 + Transaction temp = transactions[i]; + transactions[i] = transactions[CHARLIE_N_TRANSACTION_HISTORY - i - 1]; + transactions[CHARLIE_N_TRANSACTION_HISTORY - i - 1] = temp; + } + + return transactions; +} + +/* +static DateTime expiry(DateTime iss) { + // Per Metrodroid CharlieCard parser (https://github.com/metrodroid/metrodroid/blob/master/src/commonMain/kotlin/au/id/micolous/metrodroid/transit/charlie/CharlieCardTransitData.kt) + // Expiry not explicitly stored in card data; rather, calculated from date of issue + // Cards were first issued in 2006, expired in 5 years, w/ no printed expiry date + // Cards issued after 2011 expire in 10 years + // + // Per DEFCON31 researcher's work (cited above): + // Student cards last one school year and expire at the end of August the following year + // Pre-2011 issued cards expire in 7 years, not 5 as claimed by Metrodroid + // Post-2011 expire in 10 years, less one day + // Redundant function given the existance of the end validity field? + // Any important distinctions between the two? + + + // perhaps additionally clipping to 2030-12-__ in anticipation of upcoming system migration? + // need to get a new card to confirm. + + // TODO add card type logic for student card expiry + DateTime exp; + if(iss.year < 2011) { + // add 7 years; assumes average year of 8766 hrs (to account for leap years) + // may be off by a few hours as a result + exp = dt_delta(iss, 7 * 8766 * 60 * 60); + } else { + // add 10 years, subtract a day. Same assumption as above + exp = dt_delta(iss, ((10 * 8766) - 24) * 60 * 60); + } + + return exp; +} + +static bool expired(DateTime expiry, DateTime last_transaction) { + // if a card has sat unused for >2 years, expired (verify this claim?) + // else expired if current date > expiry date + + uint32_t ts_exp = datetime_datetime_to_timestamp(&expiry); + uint32_t ts_last = datetime_datetime_to_timestamp(&last_transaction); + uint32_t ts_now = time_now(); + + return (ts_exp <= ts_now) | ((ts_now - ts_last) >= (2 * 365 * 24 * 60 * 60)); +} +*/ + +// ********************************************************** +// ****************** STRING FORMATTING ********************* +// ********************************************************** + +void locale_format_dt_cat(FuriString* out, const DateTime* dt) { + // helper to print datetimes + FuriString* s = furi_string_alloc(); + + LocaleDateFormat date_format = locale_get_date_format(); + const char* separator = (date_format == LocaleDateFormatDMY) ? "." : "/"; + locale_format_date(s, dt, date_format, separator); + furi_string_cat(out, s); + locale_format_time(s, dt, locale_get_time_format(), false); + furi_string_cat_printf(out, " "); + furi_string_cat(out, s); + + furi_string_free(s); +} + +void type_format_cat(FuriString* out, uint16_t type) { + const char* s; + if(!get_map_item(type, charliecard_types, kNumTypes, &s)) { + s = ""; + furi_string_cat_printf(out, "Unknown-%u", type); + } + + furi_string_cat_str(out, s); +} + +void pass_format_cat(FuriString* out, Pass pass) { + furi_string_cat_printf(out, "\n-Pre: %b", pass.pre); + // type_format_cat(out, pass.type); + furi_string_cat_printf(out, "\n-Post: "); + type_format_cat(out, pass.post); + // locale_format_dt_cat(out, &pass.start); + furi_string_cat_printf(out, "\n-Date: "); + locale_format_dt_cat(out, &pass.date); +} + +void passes_format_cat(FuriString* out, Pass* passes) { + // only print passes if DEBUG on + if(!is_debug()) { + return; + } + + // only print if there is at least 1 valid pass to print + bool any_valid = false; + for(size_t i = 0; i < CHARLIE_N_PASSES; i++) { + any_valid |= passes[i].valid; + } + if(!any_valid) { + return; + } + + furi_string_cat_printf(out, "\nPasses (DEBUG / WIP):"); + for(size_t i = 0; i < CHARLIE_N_PASSES; i++) { + if(passes[i].valid) { + furi_string_cat_printf(out, "\nPass %u", i + 1); + pass_format_cat(out, passes[i]); + furi_string_cat_printf(out, "\n"); + } + } +} + +void money_format_cat(FuriString* out, Money money) { + furi_string_cat_printf(out, "$%u.%02u", money.dollars, money.cents); +} + +void transaction_format_cat(FuriString* out, Transaction transaction) { + const char* sep = " "; + const char* sta; + + locale_format_dt_cat(out, &transaction.date); + furi_string_cat_printf(out, "\n%s", !!(transaction.g_flag & 0x1) ? "-" : "+"); + money_format_cat(out, transaction.fare); + if(!!(transaction.g_flag & 0x1) && (transaction.fare.dollars == FARE_BUS.dollars) && + (transaction.fare.cents == FARE_BUS.cents)) { + // if not a refill, and the fare amount is equal to bus fare (any better approach? flag bits for modality?) + // format for bus — supposedly some correlation between gate ID & bus #, haven't investigated + furi_string_cat_printf(out, "%s#%u", sep, transaction.gate); + } else if(get_map_item(transaction.gate, charliecard_fare_gate_ids, kNumFareGateIds, &sta)) { + // station found in fare gate ID map, append station name + furi_string_cat_str(out, sep); + furi_string_cat_str(out, sta); + } else { + // no found station in fare gate ID map & not a bus, just print ID w/o add'l info + furi_string_cat_printf(out, "%s#%u", sep, transaction.gate); + } + // print flags for debugging purposes + if(is_debug()) { + furi_string_cat_printf(out, "%s%x%s%x", sep, transaction.g_flag, sep, transaction.f_flag); + } +} + +void transactions_format_cat(FuriString* out, Transaction* transactions) { + furi_string_cat_printf(out, "\nTransactions:"); + for(size_t i = 0; i < CHARLIE_N_TRANSACTION_HISTORY; i++) { + furi_string_cat_printf(out, "\n"); + transaction_format_cat(out, transactions[i]); + furi_string_cat_printf(out, "\n"); + } +} + +// ********************************************************** +// **************** NFC PLUGIN BOILERPLATE ****************** +// ********************************************************** + +static bool charliecard_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + + do { + // Verify card type + if(data->type != MfClassicType1k) break; + + // Verify key + // arbitrary sector in the main data portion + const uint8_t verify_sector = 3; + const MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, verify_sector); + + const uint64_t key_a = + bit_lib_bytes_to_num_be(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); + const uint64_t key_b = + bit_lib_bytes_to_num_be(sec_tr->key_b.data, COUNT_OF(sec_tr->key_b.data)); + if(key_a != charliecard_1k_keys[verify_sector].a) break; + if(key_b != charliecard_1k_keys[verify_sector].b) break; + + // parse card data + const uint32_t card_number = mfg_sector_parse(data); + const CounterSector counter_sector = counter_sector_parse(data); + const BalanceSector balance_sector = + balance_sector_parse(data, counter_sector.active_balance_sector); + Pass* passes = passes_parse(data); + Transaction* transactions = transactions_parse(data); + + // print/append card data + furi_string_cat_printf(parsed_data, "\e#CharlieCard"); + furi_string_cat_printf(parsed_data, "\nSerial: 5-%lu", card_number); + + // Type and balance 0 on some (Perq) cards + // (ie no "main" type / balance / end validity, + // essentially only pass & trip info) + // skip/change formatting for that case? + furi_string_cat_printf(parsed_data, "\nBal: "); + money_format_cat(parsed_data, balance_sector.balance); + + furi_string_cat_printf(parsed_data, "\nType: "); + type_format_cat(parsed_data, balance_sector.type); + + furi_string_cat_printf(parsed_data, "\nTrip Count: %u", counter_sector.n_uses); + + furi_string_cat_printf(parsed_data, "\nIssued: "); + locale_format_dt_cat(parsed_data, &balance_sector.issued); + + if(!dt_eq(balance_sector.end_validity, CHARLIE_EPOCH) & + dt_ge(balance_sector.end_validity, balance_sector.issued)) { + // sometimes (seen on Perq cards) end validity field is all 0 + // When this is the case, calc'd end validity is equal to CHARLIE_EPOCH). + // Only print if not 0, & end validity after issuance date + furi_string_cat_printf(parsed_data, "\nExpiry: "); + locale_format_dt_cat(parsed_data, &balance_sector.end_validity); + } + + // const DateTime last = date_parse(data, active_sector, 0, 1); + // furi_string_cat_printf(parsed_data, "\nExpired: %s", expired(e_v, last) ? "Yes" : "No"); + + transactions_format_cat(parsed_data, transactions); + free(transactions); + + passes_format_cat(parsed_data, passes); + free(passes); + + parsed = true; + } while(false); + + return parsed; +} + static bool charliecard_verify(Nfc* nfc) { - // does this suffice? Or should I check add'l keys/data/etc? bool verified = false; do { @@ -687,357 +1255,6 @@ static bool charliecard_read(Nfc* nfc, NfcDevice* device) { return is_read; } -uint32_t time_now() { - return furi_hal_rtc_get_timestamp(); -} - -static Money money_parse( - const MfClassicData* data, - uint8_t sector_num, - uint8_t block_num, - uint8_t byte_num) { - // CharlieCards store all money values in two bytes as half-cents - // bitmask removes sign/flag, bitshift converts half-cents to cents, div & mod yield dollars & cents - uint16_t amt = (pos_to_num(data, sector_num, block_num, byte_num, 2) & 0x7FFF) >> 1; - return (Money){amt / 100, amt % 100}; -} - -static DateTime - date_parse(const MfClassicData* data, uint8_t sector_num, uint8_t block_num, uint8_t byte_num) { - // Dates are 3 bytes, in minutes since 2003/1/1 ("CHARLIE_EPOCH") - uint32_t ts_charlie = pos_to_num(data, sector_num, block_num, byte_num, 3); - return dt_delta(CHARLIE_EPOCH, ts_charlie * CHARLIE_TIME_DELTA_SECS); -} - -static DateTime - end_validity_parse(const MfClassicData* data, enum CharlieActiveSector active_sec) { - // End validity field is a bit odd; shares byte 1 with another variable (the card type field), - // occupying only the last 3 bits (and subsequent two bytes), hence bitmask - // TODO; what are the add'l 3 bits between type & end validity fields? - uint32_t ts_charlie_ev = - pos_to_num(data, (active_sec == CHARLIE_ACTIVE_SECTOR_2) ? 2 : 3, 1, 1, 3); - ts_charlie_ev = ts_charlie_ev & 0x1FFFFF; - - // additionally, instead of minute deltas, is in 8 minute increments - // relative to CHARLIE_EPOCH (2003/1/1), per DEFCON31 researcher's work - return dt_delta(CHARLIE_EPOCH, ts_charlie_ev * CHARLIE_END_VALID_DELTA_SECS); -} - -static Trip - trip_parse(const MfClassicData* data, uint8_t sector_num, uint8_t block_num, uint8_t byte_num) { - /* This function parses individual trips. Each trip packs 7 bytes, stored as follows: - - 0 1 2 3 4 5 6 - +----.----.----+----.--+-+----.----+ - | date | loc |f| amt | - +----.----.----+----.--+-+----.----+ - - Where date is in the typical format, loc represents the fare gate tapped, and amt is the fare amount. - Amount appears to contain some flag bits, however, it is unclear what precisely their function is. - - Gate ID ("loc") is only the first 13 bits of 0x3:0x5, the final three bits appear to be flags ("f"). - Least significant flag bit (ie "loc & 0x1") seems to indicate: - — When 0, fare (the amount by which balance is decremented) - — When 1, refill (the amount by which balance is incremented) - - On monthly pass cards, MSB of amt will be set: 0x8000 (negative zero) - Seemingly randomly (irrespective of card type, last trip, etc) 0x0001 will be set on amt in addition to - whatever the regular fare is (a half cent more). I am uncertain what this flag indicates. - */ - const DateTime date = date_parse(data, sector_num, block_num, byte_num); - const uint16_t gate = pos_to_num(data, sector_num, block_num, byte_num + 3, 2) >> 3; - const uint8_t g_flag = pos_to_num(data, sector_num, block_num, byte_num + 3, 2) & 0b111; - const Money fare = money_parse(data, sector_num, block_num, byte_num + 5); - const uint8_t f_flag = pos_to_num(data, sector_num, block_num, byte_num + 5, 2) & 0x8001; - return (Trip){date, gate, g_flag, fare, f_flag}; -} - -static bool date_ge(DateTime dt1, DateTime dt2) { - return datetime_datetime_to_timestamp(&dt1) >= datetime_datetime_to_timestamp(&dt2); -} - -static Trip* trips_parse(const MfClassicData* data) { - /* Sectors 6 & 7 store the last 10 trips. Overall layout as follows: - - 0 1 2 3 4 5 6 7 8 9 A B C D E F - +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ - 0x180 | trip0 | trip1 | crc1 | - +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ - ... ... ... ... - +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ - 0x1D0 | trip8 | trip9 | crc5 | - +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ - 0x1E0 | empty | crc6 | - +----.----.----.----.----.----.----.----.----.----.----.----.----.----+----.----+ - - "empty" is all 0s. Trips are not sorted, rather, appear to get overwritten sequentially. (eg, sorted modulo array rotation) - */ - Trip* trips = malloc(sizeof(Trip) * CHARLIE_N_TRIP_HISTORY); - - // Parse each trip field using some modular math magic to get the offsets: - // move from sector 6 -> 7 after the first 6 trips - // move a block within a given sector every 2 trips, reset every 3 blocks (as sector has changed) - // alternate between a start byte of 0 and 7 with every iteration - for(size_t i = 0; i < CHARLIE_N_TRIP_HISTORY; i++) { - trips[i] = trip_parse(data, 6 + (i / 6), (i / 2) % 3, (i % 2) * 7); - } - - // Iterate through the array to find the maximum (newest) date value - int max_idx = 0; - for(int i = 1; i < CHARLIE_N_TRIP_HISTORY; i++) { - if(date_ge(trips[i].date, trips[max_idx].date)) { - max_idx = i; - } - } - - // Sort by rotating - for(int r = 0; r < (max_idx + 1); r++) { - // Store the first element - Trip temp = trips[0]; - // Shift elements to the left - for(int i = 0; i < CHARLIE_N_TRIP_HISTORY - 1; i++) { - trips[i] = trips[i + 1]; - } - // Move the first element to the last - trips[CHARLIE_N_TRIP_HISTORY - 1] = temp; - } - - // Reverse order, such that newest is first, oldest last - for(int i = 0; i < CHARLIE_N_TRIP_HISTORY / 2; i++) { - // Swap elements at index i and size - i - 1 - Trip temp = trips[i]; - trips[i] = trips[CHARLIE_N_TRIP_HISTORY - i - 1]; - trips[CHARLIE_N_TRIP_HISTORY - i - 1] = temp; - } - - return trips; -} - -static uint16_t n_uses(const MfClassicData* data, const enum CharlieActiveSector active_sector) { - /* First two bytes of applicable block (sector 1, block 1 or 2 depending on active_sector) - The *lower* of the two values *minus one* is the true use count, - per DEFCON31 researcher's findings - */ - return pos_to_num(data, 1, 1 + active_sector, 0, 2) - 1; -} - -static enum CharlieActiveSector get_active_sector(const MfClassicData* data) { - /* Card has two transaction sectors (2 & 3) containing balance data, with two - corresponding trip counters in 0x50:0x51 & 0x60:0x61 (sector 1, byte 0:1 of blocks 1 & 2). - - The *lower* count variable corresponds to the active sector - (0x5_ lower -> 2 active, 0x6_ lower -> 3 active) - - Sectors 2 & 3 are (largely) identical, save for trip data. - Card seems to alternate between the two, with active sector storing - the current balance & recent trip/transaction, & the inactive sector storing - the N-1 trip/transaction version of the same data. - - Here I check both the trip count and the stored transaction date, - for my own sanity, to confirm the active sector. - */ - - // active sector based on trip counters - const bool active_trip = n_uses(data, CHARLIE_ACTIVE_SECTOR_2) <= - n_uses(data, CHARLIE_ACTIVE_SECTOR_3); - - // active sector based on transaction date - DateTime ds2 = date_parse(data, 2, 0, 1); - DateTime ds3 = date_parse(data, 3, 0, 1); - const bool active_date = datetime_datetime_to_timestamp(&ds2) >= - datetime_datetime_to_timestamp(&ds3); - - // with all tested cards so far, this has been true - furi_assert(active_trip == active_date); - - return active_trip ? CHARLIE_ACTIVE_SECTOR_2 : CHARLIE_ACTIVE_SECTOR_3; -} - -static uint16_t type_parse(const MfClassicData* data) { - /* Card type data stored in the first 10bits of block 1 of sectors 2 & 3 (Block 9 & Block 13, from card start) - To my knowledge, card type should never change, so we can check either - without caring which is active. For my sanity, I check both, and assert equal. - */ - - // bitshift (2bytes = 16 bits) by 6bits for just first 10bits - const uint16_t type1 = pos_to_num(data, 2, 1, 0, 2) >> 6; - const uint16_t type2 = pos_to_num(data, 3, 1, 0, 2) >> 6; - furi_assert(type1 == type2); - - return type1; -} - -/* -static DateTime expiry(DateTime iss) { - // Per Metrodroid CharlieCard parser (https://github.com/metrodroid/metrodroid/blob/master/src/commonMain/kotlin/au/id/micolous/metrodroid/transit/charlie/CharlieCardTransitData.kt) - // Expiry not explicitly stored in card data; rather, calculated from date of issue - // Cards were first issued in 2006, expired in 5 years, w/ no printed expiry date - // Cards issued after 2011 expire in 10 years - // - // Per DEFCON31 researcher's work (cited above): - // Student cards last one school year and expire at the end of August the following year - // Pre-2011 issued cards expire in 7 years, not 5 as claimed by Metrodroid - // Post-2011 expire in 10 years, less one day - // Redundant function given the existance of the end validity field? - // Any important distinctions between the two? - - - // perhaps additionally clipping to 2030-12-__ in anticipation of upcoming system migration? - // need to get a new card to confirm. - - // TODO add card type logic for student card expiry - DateTime exp; - if(iss.year < 2011) { - // add 7 years; assumes average year of 8766 hrs (to account for leap years) - // may be off by a few hours as a result - exp = dt_delta(iss, 7 * 8766 * 60 * 60); - } else { - // add 10 years, subtract a day. Same assumption as above - exp = dt_delta(iss, ((10 * 8766) - 24) * 60 * 60); - } - - return exp; -}*/ - -static bool expired(DateTime expiry, DateTime last_trip) { - // if a card has sat unused for >2 years, expired (verify this claim?) - // else expired if current date > expiry date - - uint32_t ts_exp = datetime_datetime_to_timestamp(&expiry); - uint32_t ts_last = datetime_datetime_to_timestamp(&last_trip); - uint32_t ts_now = time_now(); - - return (ts_exp <= ts_now) | ((ts_now - ts_last) >= (2 * 365 * 24 * 60 * 60)); -} - -void locale_format_dt_cat(FuriString* out, const DateTime* dt) { - // helper to print datetimes - FuriString* s = furi_string_alloc(); - - LocaleDateFormat date_format = locale_get_date_format(); - const char* separator = (date_format == LocaleDateFormatDMY) ? "." : "/"; - locale_format_date(s, dt, date_format, separator); - furi_string_cat(out, s); - locale_format_time(s, dt, locale_get_time_format(), false); - furi_string_cat_printf(out, " "); - furi_string_cat(out, s); - - furi_string_free(s); -} - -void type_format_cat(FuriString* out, uint16_t type) { - const char* s; - if(!get_map_item(type, charliecard_types, kNumTypes, &s)) { - s = ""; - furi_string_cat_printf(out, "Unknown-%u", type); - } - - furi_string_cat_str(out, s); -} - -void money_format_cat(FuriString* out, Money money) { - furi_string_cat_printf(out, "$%u.%02u", money.dollars, money.cents); -} - -void trip_format_cat(FuriString* out, Trip trip) { - const char* sep = " "; - const char* sta; - - locale_format_dt_cat(out, &trip.date); - furi_string_cat_printf(out, "\n%s", !!(trip.g_flag & 0x1) ? "-" : "+"); - money_format_cat(out, trip.fare); - if(!!(trip.g_flag & 0x1) && (trip.fare.dollars == FARE_BUS.dollars) && - (trip.fare.cents == FARE_BUS.cents)) { - // if not a refill, and the fare amount is equal to bus fare (any better approach? flag bits for modality?) - // format for bus (gate ID on busses = posted bus #) - furi_string_cat_printf(out, "%sBus#%u", sep, trip.gate); - } else if(get_map_item(trip.gate, charliecard_fare_gate_ids, kNumFareGateIds, &sta)) { - // station found in fare gate ID map, append station name - furi_string_cat_str(out, sep); - furi_string_cat_str(out, sta); - } else { - // no found station in fare gate ID map & not a bus, just print ID w/o add'l info - furi_string_cat_printf(out, "%s%u", sep, trip.gate); - } - // print flags for debugging purposes - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - furi_string_cat_printf(out, "%s%u%s%u", sep, trip.g_flag, sep, trip.f_flag); - } -} - -static bool charliecard_parse(const NfcDevice* device, FuriString* parsed_data) { - furi_assert(device); - - const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); - - bool parsed = false; - - do { - // Verify card type - if(data->type != MfClassicType1k) break; - - // Verify key - // arbitrary sector in the main data portion - const uint8_t verify_sector = 3; - const MfClassicSectorTrailer* sec_tr = - mf_classic_get_sector_trailer_by_sector(data, verify_sector); - - const uint64_t key_a = - bit_lib_bytes_to_num_be(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); - const uint64_t key_b = - bit_lib_bytes_to_num_be(sec_tr->key_b.data, COUNT_OF(sec_tr->key_b.data)); - if(key_a != charliecard_1k_keys[verify_sector].a) break; - if(key_b != charliecard_1k_keys[verify_sector].b) break; - - // TODO: Verify add'l? - - const enum CharlieActiveSector active_sec_enum = get_active_sector(data); - const uint8_t active_sector = (active_sec_enum == CHARLIE_ACTIVE_SECTOR_2) ? 2 : 3; - - furi_string_cat_printf(parsed_data, "\e#CharlieCard"); - - size_t uid_len = 0; - const uint8_t* uid = mf_classic_get_uid(data, &uid_len); - uint32_t card_number = bit_lib_bytes_to_num_be(uid, 4); - furi_string_cat_printf(parsed_data, "\nSerial: 5-%lu", card_number); - - Money bal = money_parse(data, active_sector, 1, 5); - furi_string_cat_printf(parsed_data, "\nBal: "); - money_format_cat(parsed_data, bal); - - const uint16_t type = type_parse(data); - furi_string_cat_printf(parsed_data, "\nType: "); - type_format_cat(parsed_data, type); - - const uint16_t n_trips = n_uses(data, active_sec_enum); - furi_string_cat_printf(parsed_data, "\nTrip Count: %u", n_trips); - - const DateTime iss = date_parse(data, active_sector, 0, 6); - furi_string_cat_printf(parsed_data, "\nIssued: "); - locale_format_dt_cat(parsed_data, &iss); - - const DateTime e_v = end_validity_parse(data, active_sec_enum); - furi_string_cat_printf(parsed_data, "\nExpiry: "); - locale_format_dt_cat(parsed_data, &e_v); - - DateTime last = date_parse(data, active_sector, 0, 1); - furi_string_cat_printf(parsed_data, "\nExpired: %s", expired(e_v, last) ? "Yes" : "No"); - - Trip* trips = trips_parse(data); - furi_string_cat_printf(parsed_data, "\nTransactions:"); - for(size_t i = 0; i < CHARLIE_N_TRIP_HISTORY; i++) { - furi_string_cat_printf(parsed_data, "\n"); - trip_format_cat(parsed_data, trips[i]); - furi_string_cat_printf(parsed_data, "\n"); - } - free(trips); - - parsed = true; - } while(false); - - return parsed; -} - /* Actual implementation of app<>plugin interface */ static const NfcSupportedCardsPlugin charliecard_plugin = { .protocol = NfcProtocolMfClassic, From c1e8cbf3fdb1f13e27b24d25cf66a9c6de8cbc91 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 10 Apr 2024 15:46:20 +0300 Subject: [PATCH 094/129] update changelog --- CHANGELOG.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70b8bdf9f..9206f1b13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,26 +1,28 @@ ## New changes * NFC: CharlieCard parser (by @zacharyweiss) +* SubGHz: Add Manually - Sommer FM fixes * SubGHz: Enabled tx-rx state on unused gpio pin by default (**external amp option was removed and is enabled by default now**) -* SubGHz: Status output !TX/RX on the GDO2 CC1101 pin (by @quen0n | PR #742) +* SubGHz: **Status output !TX/RX on the GDO2 CC1101 pin** (by @quen0n | PR #742) * SubGHz: Reworked saved settings (by @xMasterX and @Willy-JL) * Desktop: Fixes for animation unload (by @Willy-JL) * Misc: Added `void` due to `-Wstrict-prototypes` * Misc: Some code cleanup and proper log levels in nfc parsers * Infrared: Allow external apps to use infrared settings (by @Willy-JL) * JS & HAL: Various fixes and FURI_HAL_RANDOM_MAX define added (by @Willy-JL) -* JS: BadUSB layout support (by @Willy-JL) +* JS: **BadUSB layout support** (by @Willy-JL) * JS: Module `widget` and path globals (by @jamisonderek) * Apps: NFC Magic - **Gen2 writing support, Gen4 NTAG password and PACK fixes** (by @Astrrra) -* Apps: MFKey - fixed crashes (by @noproto) +* Apps: MFKey - **fixed crashes** (by @noproto) * Apps: **Check out Apps updates by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* OFW: **Felica poller** (NFC-F) * OFW: Desktop/Loader: Unload animations before loading FAPs * OFW: JS Documentation -* OFW: Update radio stack to v1.19.0 -* OFW: Move crypto1 to helpers, add it to the public API +* OFW: **Update radio stack to v1.19.0** +* OFW: **Move crypto1 to helpers, add it to the public API** * OFW: Explain RNG differences, add FURI_HAL_RANDOM_MAX * OFW: Furi: Add "out of memory" and "malloc(0)" crash messages * OFW: IR: Fix crash on duty_cycle=1 -* OFW: Desktop: ensure that animation is unloaded before app start (fixes some out of memory crashes) +* OFW: **Desktop: ensure that animation is unloaded before app start (fixes some out of memory crashes)** * OFW: Hide unlock with reader for MFU-C * OFW: fbt: fixed missing FBT_FAP_DEBUG_ELF_ROOT to dist env * OFW: fbt: added -Wstrict-prototypes for main firmware @@ -30,19 +32,19 @@ * OFW: L1_Mods animation update : adding VGM visual * OFW: RFID Improvements * OFW: Fixed plugins and UI -* OFW: NFC: Fix mf desfire detect +* OFW: **NFC: Fix mf desfire detect** * OFW: infrared_transmit.h was missing `#pragma once` * OFW: Show the wrong PIN Attempt count on the login screen * OFW: SavedStruct: Introduce saved_struct_get_metadata * OFW: JS CLI command * OFW: Add ChromeOS Bad USB demo -* OFW: Configurable Infrared TX output (previous UL version is replaced with OFW version, new features added "AutoDetect" and saving settings) +* OFW: **Configurable Infrared TX output** (previous UL version is replaced with OFW version, new features added "AutoDetect" and saving settings) * OFW: BadUSB: BLE, media keys, Fn/Globe key commands -* OFW: NFC: Slix privacy password reveal and Desfire detect fix +* OFW: NFC: Slix privacy password reveal ->(was included in previous UL release) and **Desfire detect fix** * OFW: github: additional pre-upload checks for doxygen workflow * OFW: NFC UI fixes * OFW: Gui: unicode support, new canvas API -* OFW: Api Symbols: replace asserts with checks +* OFW: **Api Symbols: replace asserts with checks**

#### Known NFC post-refactor regressions list: - Mifare Mini clones reading is broken (original mini working fine) (OFW) From e4107040e9c6875153858ead386e3184a15c26b0 Mon Sep 17 00:00:00 2001 From: SG Date: Wed, 10 Apr 2024 21:08:19 +0300 Subject: [PATCH 095/129] aligned_alloc: check that alignment is correct --- furi/core/memmgr_heap.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/furi/core/memmgr_heap.c b/furi/core/memmgr_heap.c index 5afeb8f67..4edb9c5bd 100644 --- a/furi/core/memmgr_heap.c +++ b/furi/core/memmgr_heap.c @@ -246,6 +246,11 @@ extern void* pvPortAllocAligned(size_t xSize, size_t xAlignment) { furi_crash("memmgt in ISR"); } + // alignment must be power of 2 + if((xAlignment & (xAlignment - 1)) != 0) { + furi_crash("invalid alignment"); + } + memmgr_lock(); // allocate block From ece7a2d868e2f70aa03da1f0b1fd388339ddc96a Mon Sep 17 00:00:00 2001 From: SG Date: Wed, 10 Apr 2024 21:08:32 +0300 Subject: [PATCH 096/129] unit test: malloc --- .../debug/unit_tests/furi/furi_memmgr_test.c | 65 +++++++++++++++++-- .../debug/unit_tests/furi/furi_test.c | 2 + 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/applications/debug/unit_tests/furi/furi_memmgr_test.c b/applications/debug/unit_tests/furi/furi_memmgr_test.c index 01e2c17f6..158ece552 100644 --- a/applications/debug/unit_tests/furi/furi_memmgr_test.c +++ b/applications/debug/unit_tests/furi/furi_memmgr_test.c @@ -1,8 +1,5 @@ #include "../minunit.h" -#include -#include -#include -#include +#include void test_furi_memmgr(void) { void* ptr; @@ -37,3 +34,63 @@ void test_furi_memmgr(void) { } free(ptr); } + +static void test_memmgr_malloc(const size_t allocation_size) { + uint8_t* ptr = NULL; + const char* error_message = NULL; + + FURI_CRITICAL_ENTER(); + + ptr = malloc(allocation_size); + + // test that we can allocate memory + if(ptr == NULL) { + error_message = "malloc failed"; + } + + // test that memory is zero-initialized after allocation + for(size_t i = 0; i < allocation_size; i++) { + if(ptr[i] != 0) { + error_message = "memory is not zero-initialized after malloc"; + break; + } + } + memset(ptr, 0x55, allocation_size); + free(ptr); + + // test that memory is zero-initialized after free + // allocator can use this memory for inner purposes + // so we check that memory at least partially zero-initialized + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuse-after-free" + + size_t zero_count = 0; + for(size_t i = 0; i < allocation_size; i++) { + if(ptr[i] == 0) { + zero_count++; + } + } + +#pragma GCC diagnostic pop + + // chech that at least 75% of memory is zero-initialized + if(zero_count < (allocation_size * 0.75)) { + error_message = "seems memory is not zero-initialized after free"; + } + + FURI_CRITICAL_EXIT(); + + if(error_message != NULL) { + mu_fail(error_message); + } +} + +void test_furi_memmgr_advanced(void) { + test_memmgr_malloc(50); + test_memmgr_malloc(100); + test_memmgr_malloc(500); + test_memmgr_malloc(1000); + test_memmgr_malloc(5000); + test_memmgr_malloc(10000); +} \ No newline at end of file diff --git a/applications/debug/unit_tests/furi/furi_test.c b/applications/debug/unit_tests/furi/furi_test.c index e287f9927..e0b5916d5 100644 --- a/applications/debug/unit_tests/furi/furi_test.c +++ b/applications/debug/unit_tests/furi/furi_test.c @@ -9,6 +9,7 @@ void test_furi_concurrent_access(void); void test_furi_pubsub(void); void test_furi_memmgr(void); +void test_furi_memmgr_advanced(void); static int foo = 0; @@ -37,6 +38,7 @@ MU_TEST(mu_test_furi_memmgr) { // this test is not accurate, but gives a basic understanding // that memory management is working fine test_furi_memmgr(); + test_furi_memmgr_advanced(); } MU_TEST_SUITE(test_suite) { From efc355537e6e11ae8df3d145556ef92d0a914142 Mon Sep 17 00:00:00 2001 From: SG Date: Wed, 10 Apr 2024 21:32:02 +0300 Subject: [PATCH 097/129] unit tests: realloc and test with memory fragmentation --- .../debug/unit_tests/furi/furi_memmgr_test.c | 142 ++++++++++++++++-- 1 file changed, 133 insertions(+), 9 deletions(-) diff --git a/applications/debug/unit_tests/furi/furi_memmgr_test.c b/applications/debug/unit_tests/furi/furi_memmgr_test.c index 158ece552..2ea8140df 100644 --- a/applications/debug/unit_tests/furi/furi_memmgr_test.c +++ b/applications/debug/unit_tests/furi/furi_memmgr_test.c @@ -59,7 +59,7 @@ static void test_memmgr_malloc(const size_t allocation_size) { free(ptr); // test that memory is zero-initialized after free - // allocator can use this memory for inner purposes + // we know that allocator can use this memory for inner purposes // so we check that memory at least partially zero-initialized #pragma GCC diagnostic push @@ -74,9 +74,97 @@ static void test_memmgr_malloc(const size_t allocation_size) { #pragma GCC diagnostic pop - // chech that at least 75% of memory is zero-initialized + // check that at least 75% of memory is zero-initialized if(zero_count < (allocation_size * 0.75)) { - error_message = "seems memory is not zero-initialized after free"; + error_message = "seems that memory is not zero-initialized after free"; + } + + FURI_CRITICAL_EXIT(); + + if(error_message != NULL) { + mu_fail(error_message); + } +} + +static void test_memmgr_realloc(const size_t allocation_size) { + uint8_t* ptr = NULL; + const char* error_message = NULL; + + FURI_CRITICAL_ENTER(); + + ptr = realloc(ptr, allocation_size); + + // test that we can allocate memory + if(ptr == NULL) { + error_message = "realloc(NULL) failed"; + } + + // test that memory is zero-initialized after allocation + for(size_t i = 0; i < allocation_size; i++) { + if(ptr[i] != 0) { + error_message = "memory is not zero-initialized after realloc(NULL)"; + break; + } + } + + memset(ptr, 0x55, allocation_size); + + ptr = realloc(ptr, allocation_size * 2); + + // test that we can reallocate memory + if(ptr == NULL) { + error_message = "realloc failed"; + } + + // test that memory content is preserved + for(size_t i = 0; i < allocation_size; i++) { + if(ptr[i] != 0x55) { + error_message = "memory is not reallocated after realloc"; + break; + } + } + + // test that remaining memory is zero-initialized + size_t non_zero_count = 0; + for(size_t i = allocation_size; i < allocation_size * 2; i++) { + if(ptr[i] != 0) { + non_zero_count += 1; + } + } + + // check that at most of memory is zero-initialized + // we know that allocator not always can restore content size from a pointer + // so we check against small threshold + if(non_zero_count > 4) { + error_message = "seems that memory is not zero-initialized after realloc"; + } + + uint8_t* null_ptr = realloc(ptr, 0); + + // test that we can free memory + if(null_ptr != NULL) { + error_message = "realloc(0) failed"; + } + + // test that memory is zero-initialized after realloc(0) + // we know that allocator can use this memory for inner purposes + // so we check that memory at least partially zero-initialized + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuse-after-free" + + size_t zero_count = 0; + for(size_t i = 0; i < allocation_size; i++) { + if(ptr[i] == 0) { + zero_count++; + } + } + +#pragma GCC diagnostic pop + + // check that at least 75% of memory is zero-initialized + if(zero_count < (allocation_size * 0.75)) { + error_message = "seems that memory is not zero-initialized after realloc(0)"; } FURI_CRITICAL_EXIT(); @@ -87,10 +175,46 @@ static void test_memmgr_malloc(const size_t allocation_size) { } void test_furi_memmgr_advanced(void) { - test_memmgr_malloc(50); - test_memmgr_malloc(100); - test_memmgr_malloc(500); - test_memmgr_malloc(1000); - test_memmgr_malloc(5000); - test_memmgr_malloc(10000); + const size_t sizes[] = {50, 100, 500, 1000, 5000, 10000}; + const size_t sizes_count = sizeof(sizes) / sizeof(sizes[0]); + + // do test without memory fragmentation + { + for(size_t i = 0; i < sizes_count; i++) { + test_memmgr_malloc(sizes[i]); + } + + for(size_t i = 0; i < sizes_count; i++) { + test_memmgr_realloc(sizes[i]); + } + } + + // do test with memory fragmentation + { + void* blocks[sizes_count]; + void* guards[sizes_count - 1]; + + for(size_t i = 0; i < sizes_count; i++) { + blocks[i] = malloc(sizes[i]); + if(i < sizes_count - 1) { + guards[i] = malloc(sizes[i]); + } + } + + for(size_t i = 0; i < sizes_count; i++) { + free(blocks[i]); + } + + for(size_t i = 0; i < sizes_count; i++) { + test_memmgr_malloc(sizes[i]); + } + + for(size_t i = 0; i < sizes_count; i++) { + test_memmgr_realloc(sizes[i]); + } + + for(size_t i = 0; i < sizes_count - 1; i++) { + free(guards[i]); + } + } } \ No newline at end of file From 9317a07d994e1262e28e4f2b1042ad18b5cac150 Mon Sep 17 00:00:00 2001 From: SG Date: Wed, 10 Apr 2024 21:42:50 +0300 Subject: [PATCH 098/129] unit tests: aligned_alloc --- .../debug/unit_tests/furi/furi_memmgr_test.c | 75 ++++++++++++++++++- furi/core/memmgr_heap.c | 1 + 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/applications/debug/unit_tests/furi/furi_memmgr_test.c b/applications/debug/unit_tests/furi/furi_memmgr_test.c index 2ea8140df..399e2d418 100644 --- a/applications/debug/unit_tests/furi/furi_memmgr_test.c +++ b/applications/debug/unit_tests/furi/furi_memmgr_test.c @@ -76,7 +76,7 @@ static void test_memmgr_malloc(const size_t allocation_size) { // check that at least 75% of memory is zero-initialized if(zero_count < (allocation_size * 0.75)) { - error_message = "seems that memory is not zero-initialized after free"; + error_message = "seems that memory is not zero-initialized after free (malloc)"; } FURI_CRITICAL_EXIT(); @@ -174,9 +174,67 @@ static void test_memmgr_realloc(const size_t allocation_size) { } } +static void test_memmgr_alloc_aligned(const size_t allocation_size, const size_t alignment) { + uint8_t* ptr = NULL; + const char* error_message = NULL; + + FURI_CRITICAL_ENTER(); + + ptr = aligned_alloc(alignment, allocation_size); + + // test that we can allocate memory + if(ptr == NULL) { + error_message = "aligned_alloc failed"; + } + + // test that memory is aligned + if(((uintptr_t)ptr % alignment) != 0) { + error_message = "memory is not aligned after aligned_alloc"; + } + + // test that memory is zero-initialized after allocation + for(size_t i = 0; i < allocation_size; i++) { + if(ptr[i] != 0) { + error_message = "memory is not zero-initialized after aligned_alloc"; + break; + } + } + memset(ptr, 0x55, allocation_size); + free(ptr); + + // test that memory is zero-initialized after free + // we know that allocator can use this memory for inner purposes + // so we check that memory at least partially zero-initialized + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuse-after-free" + + size_t zero_count = 0; + for(size_t i = 0; i < allocation_size; i++) { + if(ptr[i] == 0) { + zero_count++; + } + } + +#pragma GCC diagnostic pop + + // check that at least 75% of memory is zero-initialized + if(zero_count < (allocation_size * 0.75)) { + error_message = "seems that memory is not zero-initialized after free (aligned_alloc)"; + } + + FURI_CRITICAL_EXIT(); + + if(error_message != NULL) { + mu_fail(error_message); + } +} + void test_furi_memmgr_advanced(void) { const size_t sizes[] = {50, 100, 500, 1000, 5000, 10000}; const size_t sizes_count = sizeof(sizes) / sizeof(sizes[0]); + const size_t alignments[] = {4, 8, 16, 32, 64, 128, 256, 512, 1024}; + const size_t alignments_count = sizeof(alignments) / sizeof(alignments[0]); // do test without memory fragmentation { @@ -187,6 +245,12 @@ void test_furi_memmgr_advanced(void) { for(size_t i = 0; i < sizes_count; i++) { test_memmgr_realloc(sizes[i]); } + + for(size_t i = 0; i < sizes_count; i++) { + for(size_t j = 0; j < alignments_count; j++) { + test_memmgr_alloc_aligned(sizes[i], alignments[j]); + } + } } // do test with memory fragmentation @@ -194,6 +258,7 @@ void test_furi_memmgr_advanced(void) { void* blocks[sizes_count]; void* guards[sizes_count - 1]; + // setup guards for(size_t i = 0; i < sizes_count; i++) { blocks[i] = malloc(sizes[i]); if(i < sizes_count - 1) { @@ -205,6 +270,7 @@ void test_furi_memmgr_advanced(void) { free(blocks[i]); } + // do test for(size_t i = 0; i < sizes_count; i++) { test_memmgr_malloc(sizes[i]); } @@ -213,6 +279,13 @@ void test_furi_memmgr_advanced(void) { test_memmgr_realloc(sizes[i]); } + for(size_t i = 0; i < sizes_count; i++) { + for(size_t j = 0; j < alignments_count; j++) { + test_memmgr_alloc_aligned(sizes[i], alignments[j]); + } + } + + // cleanup guards for(size_t i = 0; i < sizes_count - 1; i++) { free(guards[i]); } diff --git a/furi/core/memmgr_heap.c b/furi/core/memmgr_heap.c index 4edb9c5bd..3dfc7f5ac 100644 --- a/furi/core/memmgr_heap.c +++ b/furi/core/memmgr_heap.c @@ -327,6 +327,7 @@ extern void* pvPortRealloc(void* pv, size_t xSize) { memmgr_unlock(); // clear remain block content, if the new size is bigger + // can't guarantee that all data will be zeroed, cos tlsf_block_size is not always the same as xSize if(xSize > old_size) { memset((uint8_t*)data + old_size, 0, xSize - old_size); } From 7626a319deada3efe8edffc722ea475bfeff7901 Mon Sep 17 00:00:00 2001 From: SG Date: Wed, 10 Apr 2024 21:49:09 +0300 Subject: [PATCH 099/129] update api --- targets/f18/api_symbols.csv | 12 +++++------- targets/f7/api_symbols.csv | 12 +++++------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 6e597e7d0..e8b2bf6ae 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.7,, +Version,+,61.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -514,9 +514,7 @@ Function,-,acosh,double,double Function,-,acoshf,float,float Function,-,acoshl,long double,long double Function,-,acosl,long double,long double -Function,-,aligned_alloc,void*,"size_t, size_t" -Function,+,aligned_free,void,void* -Function,+,aligned_malloc,void*,"size_t, size_t" +Function,+,aligned_alloc,void*,"size_t, size_t" Function,-,arc4random,__uint32_t, Function,-,arc4random_buf,void,"void*, size_t" Function,-,arc4random_uniform,__uint32_t,__uint32_t @@ -1973,7 +1971,8 @@ Function,+,memchr,void*,"const void*, int, size_t" Function,+,memcmp,int,"const void*, const void*, size_t" Function,+,memcpy,void*,"void*, const void*, size_t" Function,-,memmem,void*,"const void*, size_t, const void*, size_t" -Function,-,memmgr_alloc_from_pool,void*,size_t +Function,+,memmgr_aux_pool_alloc,void*,size_t +Function,+,memmgr_aux_pool_get_free,size_t, Function,+,memmgr_get_free_heap,size_t, Function,+,memmgr_get_minimum_free_heap,size_t, Function,+,memmgr_get_total_heap,size_t, @@ -1981,8 +1980,7 @@ Function,+,memmgr_heap_disable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_enable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_get_max_free_block,size_t, Function,+,memmgr_heap_get_thread_memory,size_t,FuriThreadId -Function,+,memmgr_heap_printf_free_blocks,void, -Function,-,memmgr_pool_get_free,size_t, +Function,+,memmgr_heap_walk_blocks,void,"BlockWalker, void*" Function,-,memmgr_pool_get_max_block,size_t, Function,+,memmove,void*,"void*, const void*, size_t" Function,-,mempcpy,void*,"void*, const void*, size_t" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 09b8b9ee8..eeaafd34b 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.7,, +Version,+,61.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -585,9 +585,7 @@ Function,-,acosh,double,double Function,-,acoshf,float,float Function,-,acoshl,long double,long double Function,-,acosl,long double,long double -Function,-,aligned_alloc,void*,"size_t, size_t" -Function,+,aligned_free,void,void* -Function,+,aligned_malloc,void*,"size_t, size_t" +Function,+,aligned_alloc,void*,"size_t, size_t" Function,-,arc4random,__uint32_t, Function,-,arc4random_buf,void,"void*, size_t" Function,-,arc4random_uniform,__uint32_t,__uint32_t @@ -2382,7 +2380,8 @@ Function,+,memchr,void*,"const void*, int, size_t" Function,+,memcmp,int,"const void*, const void*, size_t" Function,+,memcpy,void*,"void*, const void*, size_t" Function,-,memmem,void*,"const void*, size_t, const void*, size_t" -Function,-,memmgr_alloc_from_pool,void*,size_t +Function,+,memmgr_aux_pool_alloc,void*,size_t +Function,+,memmgr_aux_pool_get_free,size_t, Function,+,memmgr_get_free_heap,size_t, Function,+,memmgr_get_minimum_free_heap,size_t, Function,+,memmgr_get_total_heap,size_t, @@ -2390,8 +2389,7 @@ Function,+,memmgr_heap_disable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_enable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_get_max_free_block,size_t, Function,+,memmgr_heap_get_thread_memory,size_t,FuriThreadId -Function,+,memmgr_heap_printf_free_blocks,void, -Function,-,memmgr_pool_get_free,size_t, +Function,+,memmgr_heap_walk_blocks,void,"BlockWalker, void*" Function,-,memmgr_pool_get_max_block,size_t, Function,+,memmove,void*,"void*, const void*, size_t" Function,-,mempcpy,void*,"void*, const void*, size_t" From 6bb605f8ced38c254d38c63c522a46e22d8d5377 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 11 Apr 2024 01:20:39 +0300 Subject: [PATCH 100/129] Allow setting view dispatcher callbacks to NULL --- applications/services/gui/view_dispatcher.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/applications/services/gui/view_dispatcher.c b/applications/services/gui/view_dispatcher.c index 222ba817f..d4c2f61e7 100644 --- a/applications/services/gui/view_dispatcher.c +++ b/applications/services/gui/view_dispatcher.c @@ -51,7 +51,6 @@ void view_dispatcher_set_navigation_event_callback( ViewDispatcher* view_dispatcher, ViewDispatcherNavigationEventCallback callback) { furi_check(view_dispatcher); - furi_check(callback); view_dispatcher->navigation_event_callback = callback; } @@ -59,7 +58,6 @@ void view_dispatcher_set_custom_event_callback( ViewDispatcher* view_dispatcher, ViewDispatcherCustomEventCallback callback) { furi_check(view_dispatcher); - furi_check(callback); view_dispatcher->custom_event_callback = callback; } @@ -68,7 +66,6 @@ void view_dispatcher_set_tick_event_callback( ViewDispatcherTickEventCallback callback, uint32_t tick_period) { furi_check(view_dispatcher); - furi_check(callback); view_dispatcher->tick_event_callback = callback; view_dispatcher->tick_period = tick_period; } From 6c508779e22acca81c533cefc65aebdc8046a49a Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Thu, 11 Apr 2024 03:33:15 +0100 Subject: [PATCH 101/129] JS: Fix badusb double free crash with quit() --- applications/system/js_app/modules/js_badusb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/applications/system/js_app/modules/js_badusb.c b/applications/system/js_app/modules/js_badusb.c index 42e677842..d2e30b334 100644 --- a/applications/system/js_app/modules/js_badusb.c +++ b/applications/system/js_app/modules/js_badusb.c @@ -88,6 +88,7 @@ static void js_badusb_quit_free(JsBadusbInst* badusb) { } if(badusb->hid_cfg) { free(badusb->hid_cfg); + badusb->hid_cfg = NULL; } } From 56dbcb17f741cd31ae49153da6b5dfbdaacfc8b4 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Thu, 11 Apr 2024 13:31:06 +0100 Subject: [PATCH 102/129] Sync apps - Picopass: Save as LFRFID and improvements (by bettse) - NFC Magic: Gen4 sync and fixes (by xMasterX) --- applications/external | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/external b/applications/external index b069a9c43..862ed2cd2 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit b069a9c43cd13b62249982f9a1d8113a1ab6e858 +Subproject commit 862ed2cd26253a9bc73555bc4697423ae685ba2b From 9b3f7f4968f935c073ad2878ef2ee4dd3aa95b8f Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Thu, 11 Apr 2024 13:34:37 +0100 Subject: [PATCH 103/129] Format --- applications/external | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/external b/applications/external index 862ed2cd2..2ee30f7b2 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit 862ed2cd26253a9bc73555bc4697423ae685ba2b +Subproject commit 2ee30f7b2dbea8f919800b589cc106570298ba1a From b2996049321649068a1aa9355c5a35527fa09648 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 12 Apr 2024 20:02:38 +0300 Subject: [PATCH 104/129] js fix badusb double free crash with quit() by Willy-JL --- applications/system/js_app/modules/js_badusb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/applications/system/js_app/modules/js_badusb.c b/applications/system/js_app/modules/js_badusb.c index 42e677842..d2e30b334 100644 --- a/applications/system/js_app/modules/js_badusb.c +++ b/applications/system/js_app/modules/js_badusb.c @@ -88,6 +88,7 @@ static void js_badusb_quit_free(JsBadusbInst* badusb) { } if(badusb->hid_cfg) { free(badusb->hid_cfg); + badusb->hid_cfg = NULL; } } From f4ffd29bbc2282f86043e840127c17e3a3e618b9 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 12 Apr 2024 20:16:42 +0300 Subject: [PATCH 105/129] Allow no prefix usage of name_generator_make_detailed_datetime Remove extra check for NULL since check is present in code already --- lib/toolbox/name_generator.c | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/toolbox/name_generator.c b/lib/toolbox/name_generator.c index 7c4d10b6b..0423c8ade 100644 --- a/lib/toolbox/name_generator.c +++ b/lib/toolbox/name_generator.c @@ -74,7 +74,6 @@ void name_generator_make_detailed_datetime( DateTime* custom_time) { furi_check(name); furi_check(max_name_size); - furi_check(prefix); DateTime dateTime; if(custom_time) { From f702ab41a72d4c2170ec676ea89c68171f8c8a57 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 13 Apr 2024 04:15:17 +0100 Subject: [PATCH 106/129] BLE Spam: Fix back event deadlocks --- applications/external | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/external b/applications/external index 2ee30f7b2..f6adf0695 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit 2ee30f7b2dbea8f919800b589cc106570298ba1a +Subproject commit f6adf0695636a0c4d048eec8756f66284636c789 From ff1592275677e9284e83172dc5480370a40ba554 Mon Sep 17 00:00:00 2001 From: Kuronons <110337784+Kuronons@users.noreply.github.com> Date: Sun, 14 Apr 2024 00:04:10 +0200 Subject: [PATCH 107/129] MNTM Pack - Adding RFID Receive/Send yappy graphic assets Last 2 ones of the static graphic assets ! O_oV --- .../Icons/RFID/RFIDDolphinReceive_97x61.png | Bin 0 -> 2374 bytes .../Icons/RFID/RFIDDolphinSend_97x61.png | Bin 0 -> 2378 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/packs/Momentum/Icons/RFID/RFIDDolphinReceive_97x61.png create mode 100644 assets/packs/Momentum/Icons/RFID/RFIDDolphinSend_97x61.png diff --git a/assets/packs/Momentum/Icons/RFID/RFIDDolphinReceive_97x61.png b/assets/packs/Momentum/Icons/RFID/RFIDDolphinReceive_97x61.png new file mode 100644 index 0000000000000000000000000000000000000000..dacc6aae6c82871c8748e0618175f3e337bdec10 GIT binary patch literal 2374 zcmcImc~BEq91hq6aj-BR6)SaT@xYPI9@&KKjzJ+C1c!iPDpbYtvbzbZBpb5}3Dh0} zr9~-QNQZ);e{@gK?_J*jihU)(%?7s?(0*)fsdg>sb1hBxpNI`KL*C_r2ZU z`+nd1e&4>A`ssO7<6=j~>U6reoNTTDUI9ETG12f$r7RD^%RpcD>xxb{Xoz-1>DH_r zq0_yvQF0WiMfO~lmpytQ$WBoo^!Q-3PB&?C&-iKT2XBkFiV*4Q-KvnrBB2Q zL`5$5^P;Un^s32`WWG{am6cNY8Qgjw|AV2x_a^j}`^$Rd5qMlI6Fm@Hfw5!{KObxL zi$InA4p}aX6l!`TBSKMn5=kkPyn-B1CTNVp14Is}q6LLjqcMWUNZLUVEJ3l9X(Bum zL@3k_eINi8^e56PVLw zCNain1`NqlBp`$yUT{1o^W~Zn+W3%d1(}B!y=t;13Vum5bSiBUF_)8ukVe6XnOwBV zXyzG0pvVYXzF&fG4k&vPRbwSUL$8;>< zHLqJxUMsQ)XCJyPJw?6#@eGX*l!_3vkK%{I6xpQ)fM3jTL#6-IT==udE1;rp>isMH z|42P7D_;t{ZV@h=II2ws*Or)2QSg7)>v6p!p2>i2)~cz5Wyl2XUP02IVNu#lQX&H| z28cW+P_z@e-sHr1#+k+&X(vyZnGlOI=`mKsm?onw%}g0HI0!Cy-+eg+*CGn9$2T-Md#*l8IIuj zTWc}~w;?gM*6ywAj*YR;8I~TiB(7!L^&dNLytsIF=gDu+&Obl;#JcYv&R%g~L@{!J zja@2rw51GxdB?k}XXVvzi;rJxT=wbW0}UL$WUJI&{O5l+{*!;Zka#}9-$D*#A1{Lq$n>AGqB%XXN zc6)ZNeg3w_Ydg-?T&W+sp|0f4JKGw!+$Q$A6SkOcFF4e=<Y~KczwJf`lq7ZJP*sh0-~MjjbKJ*6Ge_aNpYQGMNJ<*oq@MZpO7h(gp5Hb4(7mW} z56=Wgzn8G?*cXrHN*6B2Oc;2;$qfjk&q!}bn1^0-bsMv8&K-8qc>Pi}Rp9u<5V!Ga zV*UA_IzKwz)G&7|#}_yzKTPbHHc+~lbo6-swBY!ZBZ=ELbuE2RaJJddx}hoMR=ZL< z>J(_;R&#H3paZH$>{&goFmpq*xh~-#n0T&c)aLDEZr7fe2kO=-$bBj|4daDA>i_@% literal 0 HcmV?d00001 diff --git a/assets/packs/Momentum/Icons/RFID/RFIDDolphinSend_97x61.png b/assets/packs/Momentum/Icons/RFID/RFIDDolphinSend_97x61.png new file mode 100644 index 0000000000000000000000000000000000000000..fd2ead2e3d727a71a29170aa6c77739c28a63f56 GIT binary patch literal 2378 zcmcImdu$VR94{;&<27(R#zZ}?fNbn~k6y36+X`&G;Wkz_Mv;L6UhjTwFKc_XciXxl zNCt*MCQLF70-2b>UPD24)Ra3CsVJi{Z9U@#aS3d$qQ4E){J4m5!LW7F%sKEKcR z^ZR^1_q#4Dp8EQLzNvi;2E%~-JiY|(1-RM!^n}0ExcUg(C5G~5sRl#8WPR&lShFt0 zVCcPGc9m+S&O%O90wzIH+`tqGgdo~r$eI`l31S)05I6A3K|A_;^lKE6B|AFP;v}3Q z2k^;x)nPEbda6sTE)!V^oj3u>ig1u105k!K1pGmji`dalUJf4XmvI#7glJ`UG+Qr- zlsbzMhY|(|V83nge zuGvwjv}+21P&aK*joAbZ!y`fnCryMtr8v;(d^R)?=tiqrZUw9o)B8kX)m0S&cnMII z@~{YUD?m^)#*2k~il(SOG1@`5kwc% z33DI`n(%yJMUp*L&jWQjx%{B22|*F$^L7-9GRd;U+1!*Qxfv026IOuHHk!p)o8-nM zfg~vgSQrm$?$iax^NLumJE4#7imjxGkfYm8&PD={n*}UJnk6d+JT!^1g4KdafR!u& zFbptT<7`D?89q6J|8Z7bl>`~x6zMh#5{p?qq<}FZ&0qq_m@$SX7{Wus@KZF3JgVzZ z{7TrVNYGuVRuVan5)KG3KXO3u0z4G->S>1Ta29#83R_nddp@RvcVb6=8R-liCy091 z?Wm|X8AxdCvit=1o=;~NZKV%D(q}Zk3#KX_tx^bsY%g^BKf{GTO}r{pJX`wnHU58; z9S2|+J_3nz~1lfm^RrmHFVzx(y5-|@_3!!+yNRAV;e2;Oc(COkQ!3~eJRzzQ&! zK*S`9c4Ib?abqIuwu%!mI~-<*nI;K_ zr_GOz?3_uLApF_NJCTnqvvC{X+N~dUzck=U_iG0Pp=DwCQVFEYU1u=#EY9b%U6FG) z)=t^BnCPFo^P%&~fb>zwvIQLpOT8=8zi7LByM4;J_QU5+Ri91z@spz+`_OWNn^ai7 zYj|_*sgjI=B@ag?7hO8P`e^jc8x4YXH_DXG9+pI=I*xxoCIRo){3X+PY{-aftCU-7 z78;}d+Y_!-zLwgw_}(u+_i9*Lm6qb1e*fhBv30crR&JkFI(TmgyESOQGgUMlcwqCj3X6cS_SM$C2e0L&M=rm}XO@v8Cf(`aHN*KiEp4H~6}g@a z&#Cu57#w}0RdcrQ*ibj*Li3LDGiyKe zy|wj~W&XqCTm|&l`7Iqijq}n*HAMp{t@(+c{w3K;+q8w3EDe)>IH~Qc-FW?AnQvs9 zr;cCE7oDXNYEpKruFL$aXomklt@F(C4&>0d>VYS+GI9p)OKYoL{?mp!F01~d-o|4` zcABq}o6zLOT{JoS@ORfHBqrTtw$Gb;yO>{aV`^Rfh4d|(cGdg-7{LYWGB&gi9QJ2Y yL(%#~>9&3RLy5-Zz^w`?d0c9<+tU$UGq29t36l% literal 0 HcmV?d00001 From 1a40fae003d1f37ee0ec4fedfac0d38b78b98535 Mon Sep 17 00:00:00 2001 From: gornekich Date: Tue, 16 Apr 2024 06:55:24 +0100 Subject: [PATCH 108/129] [FL-3750] Mf Desfire multiple file rights support (#3576) * mf desfire: remove unused type * mf desfire: continue reading after failed get free mem cmd * mf desfire: fix processing read master key settings command * mf desfire: don't read applications if they are auth protected * mf desfire: handle multiple rights * mf desfire: fix PVS warnings * mf desfire: fix print format * mf desfire: fix logs * mf classic: add send frame functions to poller * unit tests: add test from mfc crypto frame exchange * mf classic: add documentation * mf classic: fix incorrect name * target: fix api version --- applications/debug/unit_tests/nfc/nfc_test.c | 127 ++++++++++++++- .../protocol_support/mf_desfire/mf_desfire.c | 8 +- .../mf_desfire/mf_desfire_render.c | 94 +++++++---- .../scenes/nfc_scene_mf_desfire_more_info.c | 2 +- .../protocols/mf_classic/mf_classic_poller.h | 83 ++++++++++ .../mf_classic/mf_classic_poller_i.c | 74 +++++++++ lib/nfc/protocols/mf_desfire/mf_desfire.h | 11 +- lib/nfc/protocols/mf_desfire/mf_desfire_i.c | 147 +++++++++++------- lib/nfc/protocols/mf_desfire/mf_desfire_i.h | 46 ++++++ .../protocols/mf_desfire/mf_desfire_poller.c | 22 ++- .../mf_desfire/mf_desfire_poller_i.c | 65 ++++++-- .../mf_desfire/mf_desfire_poller_i.h | 2 + targets/f18/api_symbols.csv | 2 +- targets/f7/api_symbols.csv | 6 +- 14 files changed, 576 insertions(+), 113 deletions(-) diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c index 4b6503b72..c6304d53c 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/nfc/nfc_test.c @@ -7,10 +7,13 @@ #include #include #include +#include #include #include #include #include +#include +#include #include #include @@ -22,6 +25,23 @@ #define NFC_TEST_NFC_DEV_PATH EXT_PATH("unit_tests/nfc/nfc_device_test.nfc") #define NFC_APP_MF_CLASSIC_DICT_UNIT_TEST_PATH EXT_PATH("unit_tests/mf_dict.nfc") +#define NFC_TEST_FLAG_WORKER_DONE (1) + +typedef enum { + NfcTestMfClassicSendFrameTestStateAuth, + NfcTestMfClassicSendFrameTestStateReadBlock, + + NfcTestMfClassicSendFrameTestStateFail, + NfcTestMfClassicSendFrameTestStateSuccess, +} NfcTestMfClassicSendFrameTestState; + +typedef struct { + NfcTestMfClassicSendFrameTestState state; + BitBuffer* tx_buf; + BitBuffer* rx_buf; + FuriThreadId thread_id; +} NfcTestMfClassicSendFrameTest; + typedef struct { Storage* storage; } NfcTest; @@ -435,6 +455,109 @@ static void mf_classic_value_block(void) { nfc_free(poller); } +NfcCommand mf_classic_poller_send_frame_callback(NfcGenericEventEx event, void* context) { + furi_check(event.poller); + furi_check(event.parent_event_data); + furi_check(context); + + NfcCommand command = NfcCommandContinue; + MfClassicPoller* instance = event.poller; + NfcTestMfClassicSendFrameTest* frame_test = context; + Iso14443_3aPollerEvent* iso3_event = event.parent_event_data; + + MfClassicError error = MfClassicErrorNone; + if(iso3_event->type == Iso14443_3aPollerEventTypeReady) { + if(frame_test->state == NfcTestMfClassicSendFrameTestStateAuth) { + MfClassicKey key = { + .data = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + }; + error = mf_classic_poller_auth(instance, 0, &key, MfClassicKeyTypeA, NULL); + frame_test->state = (error == MfClassicErrorNone) ? + NfcTestMfClassicSendFrameTestStateReadBlock : + NfcTestMfClassicSendFrameTestStateFail; + } else if(frame_test->state == NfcTestMfClassicSendFrameTestStateReadBlock) { + do { + const uint8_t read_block_cmd[] = { + 0x30, + 0x01, + 0x8b, + 0xb9, + }; + bit_buffer_copy_bytes(frame_test->tx_buf, read_block_cmd, sizeof(read_block_cmd)); + + error = mf_classic_poller_send_encrypted_frame( + instance, frame_test->tx_buf, frame_test->rx_buf, 200000); + if(error != MfClassicErrorNone) break; + if(bit_buffer_get_size_bytes(frame_test->rx_buf) != 18) { + error = MfClassicErrorProtocol; + break; + } + + const uint8_t* rx_data = bit_buffer_get_data(frame_test->rx_buf); + const uint8_t rx_data_ref[16] = {0}; + if(memcmp(rx_data, rx_data_ref, sizeof(rx_data_ref)) != 0) { + error = MfClassicErrorProtocol; + break; + } + } while(false); + + frame_test->state = (error == MfClassicErrorNone) ? + NfcTestMfClassicSendFrameTestStateSuccess : + NfcTestMfClassicSendFrameTestStateFail; + } else if(frame_test->state == NfcTestMfClassicSendFrameTestStateSuccess) { + command = NfcCommandStop; + } else if(frame_test->state == NfcTestMfClassicSendFrameTestStateFail) { + command = NfcCommandStop; + } + } else { + frame_test->state = NfcTestMfClassicSendFrameTestStateFail; + command = NfcCommandStop; + } + + if(command == NfcCommandStop) { + furi_thread_flags_set(frame_test->thread_id, NFC_TEST_FLAG_WORKER_DONE); + } + + return command; +} + +MU_TEST(mf_classic_send_frame_test) { + Nfc* poller = nfc_alloc(); + Nfc* listener = nfc_alloc(); + + NfcDevice* nfc_device = nfc_device_alloc(); + nfc_data_generator_fill_data(NfcDataGeneratorTypeMfClassic4k_7b, nfc_device); + NfcListener* mfc_listener = nfc_listener_alloc( + listener, NfcProtocolMfClassic, nfc_device_get_data(nfc_device, NfcProtocolMfClassic)); + nfc_listener_start(mfc_listener, NULL, NULL); + + NfcPoller* mfc_poller = nfc_poller_alloc(poller, NfcProtocolMfClassic); + NfcTestMfClassicSendFrameTest context = { + .state = NfcTestMfClassicSendFrameTestStateAuth, + .thread_id = furi_thread_get_current_id(), + .tx_buf = bit_buffer_alloc(32), + .rx_buf = bit_buffer_alloc(32), + }; + nfc_poller_start_ex(mfc_poller, mf_classic_poller_send_frame_callback, &context); + + uint32_t flag = + furi_thread_flags_wait(NFC_TEST_FLAG_WORKER_DONE, FuriFlagWaitAny, FuriWaitForever); + mu_assert(flag == NFC_TEST_FLAG_WORKER_DONE, "Wrong thread flag"); + nfc_poller_stop(mfc_poller); + nfc_poller_free(mfc_poller); + + mu_assert( + context.state == NfcTestMfClassicSendFrameTestStateSuccess, "Wrong test state at the end"); + + bit_buffer_free(context.tx_buf); + bit_buffer_free(context.rx_buf); + nfc_listener_stop(mfc_listener); + nfc_listener_free(mfc_listener); + nfc_device_free(nfc_device); + nfc_free(listener); + nfc_free(poller); +} + MU_TEST(mf_classic_dict_test) { Storage* storage = furi_record_open(RECORD_STORAGE); if(storage_common_stat(storage, NFC_APP_MF_CLASSIC_DICT_UNIT_TEST_PATH, NULL) == FSE_OK) { @@ -538,11 +661,11 @@ MU_TEST_SUITE(nfc) { MU_RUN_TEST(mf_classic_1k_7b_file_test); MU_RUN_TEST(mf_classic_4k_4b_file_test); MU_RUN_TEST(mf_classic_4k_7b_file_test); - MU_RUN_TEST(mf_classic_reader); + MU_RUN_TEST(mf_classic_reader); MU_RUN_TEST(mf_classic_write); MU_RUN_TEST(mf_classic_value_block); - + MU_RUN_TEST(mf_classic_send_frame_test); MU_RUN_TEST(mf_classic_dict_test); nfc_test_free(); diff --git a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c index ef51d98e0..deba1bca2 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c +++ b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c @@ -34,6 +34,8 @@ static void nfc_scene_more_info_on_enter_mf_desfire(NfcApp* instance) { static NfcCommand nfc_scene_read_poller_callback_mf_desfire(NfcGenericEvent event, void* context) { furi_assert(event.protocol == NfcProtocolMfDesfire); + NfcCommand command = NfcCommandContinue; + NfcApp* instance = context; const MfDesfirePollerEvent* mf_desfire_event = event.event_data; @@ -41,10 +43,12 @@ static NfcCommand nfc_scene_read_poller_callback_mf_desfire(NfcGenericEvent even nfc_device_set_data( instance->nfc_device, NfcProtocolMfDesfire, nfc_poller_get_data(instance->poller)); view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); - return NfcCommandStop; + command = NfcCommandStop; + } else if(mf_desfire_event->type == MfDesfirePollerEventTypeReadFailed) { + command = NfcCommandReset; } - return NfcCommandContinue; + return command; } static void nfc_scene_read_on_enter_mf_desfire(NfcApp* instance) { diff --git a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c index 94b333f55..f8eacd51a 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c +++ b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c @@ -11,24 +11,29 @@ void nfc_render_mf_desfire_info( const uint32_t bytes_total = 1UL << (data->version.sw_storage >> 1); const uint32_t bytes_free = data->free_memory.is_present ? data->free_memory.bytes_free : 0; + if(data->master_key_settings.is_free_directory_list) { + const uint32_t app_count = simple_array_get_count(data->applications); + uint32_t file_count = 0; + + for(uint32_t i = 0; i < app_count; ++i) { + const MfDesfireApplication* app = simple_array_cget(data->applications, i); + if(app->key_settings.is_free_directory_list) { + file_count += simple_array_get_count(app->file_ids); + } + } + + furi_string_cat_printf(str, "\n%lu Application%s", app_count, app_count != 1 ? "s" : ""); + furi_string_cat_printf(str, ", %lu File%s", file_count, file_count != 1 ? "s" : ""); + } else { + furi_string_cat_printf(str, "\nAuth required to read apps!"); + } + furi_string_cat_printf(str, "\n%lu", bytes_total); if(data->version.sw_storage & 1) { furi_string_push_back(str, '+'); } - - furi_string_cat_printf(str, " bytes, %lu bytes free\n", bytes_free); - - const uint32_t app_count = simple_array_get_count(data->applications); - uint32_t file_count = 0; - - for(uint32_t i = 0; i < app_count; ++i) { - const MfDesfireApplication* app = simple_array_cget(data->applications, i); - file_count += simple_array_get_count(app->file_ids); - } - - furi_string_cat_printf(str, "%lu Application%s", app_count, app_count != 1 ? "s" : ""); - furi_string_cat_printf(str, ", %lu File%s", file_count, file_count != 1 ? "s" : ""); + furi_string_cat_printf(str, " bytes, %lu bytes free", bytes_free); if(format_type != NfcProtocolFormatTypeFull) return; @@ -101,17 +106,29 @@ void nfc_render_mf_desfire_free_memory(const MfDesfireFreeMemory* data, FuriStri } void nfc_render_mf_desfire_key_settings(const MfDesfireKeySettings* data, FuriString* str) { - furi_string_cat_printf(str, "changeKeyID %d\n", data->change_key_id); - furi_string_cat_printf(str, "configChangeable %d\n", data->is_config_changeable); - furi_string_cat_printf(str, "freeCreateDelete %d\n", data->is_free_create_delete); - furi_string_cat_printf(str, "freeDirectoryList %d\n", data->is_free_directory_list); - furi_string_cat_printf(str, "masterChangeable %d\n", data->is_master_key_changeable); + if(data->is_free_directory_list) { + furi_string_cat_printf(str, "changeKeyID %d\n", data->change_key_id); + furi_string_cat_printf(str, "configChangeable %d\n", data->is_config_changeable); + furi_string_cat_printf(str, "freeCreateDelete %d\n", data->is_free_create_delete); + furi_string_cat_printf(str, "freeDirectoryList %d\n", data->is_free_directory_list); + furi_string_cat_printf(str, "masterChangeable %d\n", data->is_master_key_changeable); + } else { + furi_string_cat_printf(str, "changeKeyID ??\n"); + furi_string_cat_printf(str, "configChangeable ??\n"); + furi_string_cat_printf(str, "freeCreateDelete ??\n"); + furi_string_cat_printf(str, "freeDirectoryList 0\n"); + furi_string_cat_printf(str, "masterChangeable ??\n"); + } if(data->flags) { furi_string_cat_printf(str, "flags %d\n", data->flags); } - furi_string_cat_printf(str, "maxKeys %d\n", data->max_keys); + if(data->is_free_directory_list) { + furi_string_cat_printf(str, "maxKeys %d\n", data->max_keys); + } else { + furi_string_cat_printf(str, "maxKeys ??\n"); + } } void nfc_render_mf_desfire_key_version( @@ -123,14 +140,16 @@ void nfc_render_mf_desfire_key_version( void nfc_render_mf_desfire_application_id(const MfDesfireApplicationId* data, FuriString* str) { const uint8_t* app_id = data->data; - furi_string_cat_printf(str, "Application %02x%02x%02x\n", app_id[0], app_id[1], app_id[2]); + furi_string_cat_printf(str, "Application %02x%02x%02x\n", app_id[2], app_id[1], app_id[0]); } void nfc_render_mf_desfire_application(const MfDesfireApplication* data, FuriString* str) { nfc_render_mf_desfire_key_settings(&data->key_settings, str); - for(uint32_t i = 0; i < simple_array_get_count(data->key_versions); ++i) { - nfc_render_mf_desfire_key_version(simple_array_cget(data->key_versions, i), i, str); + if(data->key_settings.is_free_directory_list) { + for(uint32_t i = 0; i < simple_array_get_count(data->key_versions); ++i) { + nfc_render_mf_desfire_key_version(simple_array_cget(data->key_versions, i), i, str); + } } } @@ -179,13 +198,16 @@ void nfc_render_mf_desfire_file_settings_data( } furi_string_cat_printf(str, "%s %s\n", type, comm); - furi_string_cat_printf( - str, - "r %d w %d rw %d c %d\n", - settings->access_rights >> 12 & 0xF, - settings->access_rights >> 8 & 0xF, - settings->access_rights >> 4 & 0xF, - settings->access_rights & 0xF); + + for(size_t i = 0; i < settings->access_rights_len; i++) { + furi_string_cat_printf( + str, + "r %d w %d rw %d c %d\n", + settings->access_rights[i] >> 12 & 0xF, + settings->access_rights[i] >> 8 & 0xF, + settings->access_rights[i] >> 4 & 0xF, + settings->access_rights[i] & 0xF); + } uint32_t record_count = 1; uint32_t record_size = 0; @@ -217,6 +239,20 @@ void nfc_render_mf_desfire_file_settings_data( break; } + bool is_auth_required = true; + for(size_t i = 0; i < settings->access_rights_len; i++) { + uint8_t read_rights = (settings->access_rights[i] >> 12) & 0x0f; + uint8_t read_write_rights = (settings->access_rights[i] >> 4) & 0x0f; + if((read_rights == 0x0e) || (read_write_rights == 0x0e)) { + is_auth_required = false; + break; + } + } + if(is_auth_required) { + furi_string_cat_printf(str, "Auth required to read file data\n"); + return; + } + if(simple_array_get_count(data->data) == 0) { return; } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_desfire_more_info.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_more_info.c index 76834e3f4..3e111c723 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_desfire_more_info.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_desfire_more_info.c @@ -35,7 +35,7 @@ void nfc_scene_mf_desfire_more_info_on_enter(void* context) { for(uint32_t i = 0; i < simple_array_get_count(data->application_ids); ++i) { const MfDesfireApplicationId* app_id = simple_array_cget(data->application_ids, i); furi_string_printf( - label, "App %02x%02x%02x", app_id->data[0], app_id->data[1], app_id->data[2]); + label, "App %02x%02x%02x", app_id->data[2], app_id->data[1], app_id->data[0]); submenu_add_item( submenu, furi_string_get_cstr(label), diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.h b/lib/nfc/protocols/mf_classic/mf_classic_poller.h index 19e525701..518d029d0 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.h @@ -315,6 +315,89 @@ MfClassicError mf_classic_poller_value_cmd( */ MfClassicError mf_classic_poller_value_transfer(MfClassicPoller* instance, uint8_t block_num); +/** + * @brief Transmit and receive Iso14443_3a standard frames in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_send_standard_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc); + +/** + * @brief Transmit and receive Iso14443_3a frames in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_send_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc); + +/** + * @brief Transmit and receive Iso14443_3a frames with custom parity bits in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * Custom parity bits must be set in the tx_buffer. The rx_buffer will contain + * the received data with the parity bits. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_send_custom_parity_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc); + +/** + * @brief Transmit and receive Mifare Classic encrypted frames with custom parity bits in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the plain data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with decyphered received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_send_encrypted_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c index 2b01e74ee..949ef8e66 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c @@ -465,3 +465,77 @@ MfClassicError mf_classic_poller_value_transfer(MfClassicPoller* instance, uint8 return ret; } + +MfClassicError mf_classic_poller_send_standard_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc) { + furi_check(instance); + furi_check(tx_buffer); + furi_check(rx_buffer); + + Iso14443_3aError error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, tx_buffer, rx_buffer, fwt_fc); + + return mf_classic_process_error(error); +} + +MfClassicError mf_classic_poller_send_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc) { + furi_check(instance); + furi_check(tx_buffer); + furi_check(rx_buffer); + + Iso14443_3aError error = + iso14443_3a_poller_txrx(instance->iso14443_3a_poller, tx_buffer, rx_buffer, fwt_fc); + + return mf_classic_process_error(error); +} + +MfClassicError mf_classic_poller_send_custom_parity_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc) { + furi_check(instance); + furi_check(tx_buffer); + furi_check(rx_buffer); + + Iso14443_3aError error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, tx_buffer, rx_buffer, fwt_fc); + + return mf_classic_process_error(error); +} + +MfClassicError mf_classic_poller_send_encrypted_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc) { + furi_check(instance); + furi_check(tx_buffer); + furi_check(rx_buffer); + + MfClassicError ret = MfClassicErrorNone; + do { + crypto1_encrypt(instance->crypto, NULL, tx_buffer, instance->tx_encrypted_buffer); + + Iso14443_3aError error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, + instance->tx_encrypted_buffer, + instance->rx_encrypted_buffer, + fwt_fc); + if(error != Iso14443_3aErrorNone) { + ret = mf_classic_process_error(error); + break; + } + + crypto1_decrypt(instance->crypto, instance->rx_encrypted_buffer, rx_buffer); + } while(false); + + return ret; +} diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire.h b/lib/nfc/protocols/mf_desfire/mf_desfire.h index 4c3075386..2818e9a24 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire.h @@ -21,8 +21,6 @@ extern "C" { #define MF_DESFIRE_CMD_GET_VALUE (0x6C) #define MF_DESFIRE_CMD_READ_RECORDS (0xBB) -#define MF_DESFIRE_FLAG_HAS_NEXT (0xAF) - #define MF_DESFIRE_MAX_KEYS (14) #define MF_DESFIRE_MAX_FILES (32) @@ -71,11 +69,6 @@ typedef struct { typedef uint8_t MfDesfireKeyVersion; -typedef struct { - MfDesfireKeySettings key_settings; - SimpleArray* key_versions; -} MfDesfireKeyConfiguration; - typedef enum { MfDesfireFileTypeStandard = 0, MfDesfireFileTypeBackup = 1, @@ -96,7 +89,8 @@ typedef uint16_t MfDesfireFileAccessRights; typedef struct { MfDesfireFileType type; MfDesfireFileCommunicationSettings comm; - MfDesfireFileAccessRights access_rights; + MfDesfireFileAccessRights access_rights[MF_DESFIRE_MAX_KEYS]; + uint8_t access_rights_len; union { struct { uint32_t size; @@ -136,6 +130,7 @@ typedef enum { MfDesfireErrorNotPresent, MfDesfireErrorProtocol, MfDesfireErrorTimeout, + MfDesfireErrorAuthentication, } MfDesfireError; typedef struct { diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c index 646803e75..bfbbadadf 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c @@ -1,5 +1,7 @@ #include "mf_desfire_i.h" +#define TAG "MfDesfire" + #define BITS_IN_BYTE (8U) #define MF_DESFIRE_FFF_VERSION_KEY \ @@ -175,59 +177,88 @@ bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer }; } MfDesfireFileSettingsLayout; + MfDesfireFileSettings file_settings_temp = {}; do { const size_t data_size = bit_buffer_get_size_bytes(buf); + const uint8_t* data_ptr = bit_buffer_get_data(buf); const size_t min_data_size = sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsData); - const size_t max_data_size = - sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsValue); - if(data_size < min_data_size) break; - if(data_size <= max_data_size) { - MfDesfireFileSettingsLayout layout; - bit_buffer_write_bytes(buf, &layout, sizeof(MfDesfireFileSettingsLayout)); - - data->type = layout.header.type; - data->comm = layout.header.comm; - data->access_rights = layout.header.access_rights; - - if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) { - if(data_size != min_data_size) break; - - data->data.size = layout.data.size; - } else if(data->type == MfDesfireFileTypeValue) { - if(data_size != - sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsValue)) - break; - - data->value.lo_limit = layout.value.lo_limit; - data->value.hi_limit = layout.value.hi_limit; - data->value.limited_credit_value = layout.value.limited_credit_value; - data->value.limited_credit_enabled = layout.value.limited_credit_enabled; - - } else if( - data->type == MfDesfireFileTypeLinearRecord || - data->type == MfDesfireFileTypeCyclicRecord) { - if(data_size != - sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsRecord)) - break; - - data->record.size = layout.record.size; - data->record.max = layout.record.max; - data->record.cur = layout.record.cur; - - } else { - break; - } - } else { - // TODO FL-3750: process HID Desfire command response here - // Set default fields for now - data->type = 0; - data->comm = 0; - data->access_rights = 0; - data->data.size = 0; + if(data_size < min_data_size) { + FURI_LOG_E( + TAG, "File settings size %zu less than minimum %zu", data_size, min_data_size); + break; } + size_t bytes_processed = sizeof(MfDesfireFileSettingsHeader); + MfDesfireFileSettingsLayout layout = {}; + memcpy(&layout.header, data_ptr, sizeof(MfDesfireFileSettingsHeader)); + bool has_additional_access_rights = (layout.header.comm & 0x80) != 0; + + file_settings_temp.type = layout.header.type; + file_settings_temp.comm = layout.header.comm & 0x03; + file_settings_temp.access_rights_len = 1; + file_settings_temp.access_rights[0] = layout.header.access_rights; + + if(file_settings_temp.type == MfDesfireFileTypeStandard || + file_settings_temp.type == MfDesfireFileTypeBackup) { + memcpy( + &layout.data, + &data_ptr[sizeof(MfDesfireFileSettingsHeader)], + sizeof(MfDesfireFileSettingsData)); + file_settings_temp.data.size = layout.data.size; + bytes_processed += sizeof(MfDesfireFileSettingsData); + } else if(file_settings_temp.type == MfDesfireFileTypeValue) { + memcpy( + &layout.value, + &data_ptr[sizeof(MfDesfireFileSettingsHeader)], + sizeof(MfDesfireFileSettingsValue)); + file_settings_temp.value.lo_limit = layout.value.lo_limit; + file_settings_temp.value.hi_limit = layout.value.hi_limit; + file_settings_temp.value.limited_credit_value = layout.value.limited_credit_value; + file_settings_temp.value.limited_credit_enabled = layout.value.limited_credit_enabled; + bytes_processed += sizeof(MfDesfireFileSettingsValue); + } else if( + file_settings_temp.type == MfDesfireFileTypeLinearRecord || + file_settings_temp.type == MfDesfireFileTypeCyclicRecord) { + memcpy( + &layout.record, + &data_ptr[sizeof(MfDesfireFileSettingsHeader)], + sizeof(MfDesfireFileSettingsRecord)); + file_settings_temp.record.size = layout.record.size; + file_settings_temp.record.max = layout.record.max; + file_settings_temp.record.cur = layout.record.cur; + bytes_processed += sizeof(MfDesfireFileSettingsRecord); + } else { + FURI_LOG_W(TAG, "Unknown file type: %02x", file_settings_temp.type); + break; + } + + if(has_additional_access_rights) { + uint8_t additional_access_rights_len = bit_buffer_get_byte(buf, bytes_processed); + FURI_LOG_D(TAG, "Has additional rights: %d", additional_access_rights_len); + if(data_size != bytes_processed + + additional_access_rights_len * sizeof(MfDesfireFileAccessRights) + + 1) { + FURI_LOG_W(TAG, "Unexpected command length: %zu", data_size); + for(size_t i = 0; i < bit_buffer_get_size_bytes(buf); i++) { + printf("%02X ", bit_buffer_get_byte(buf, i)); + } + printf("\r\n"); + break; + } + if(additional_access_rights_len > + MF_DESFIRE_MAX_KEYS * sizeof(MfDesfireFileAccessRights)) + break; + + memcpy( + &file_settings_temp.access_rights[1], + &data_ptr[bytes_processed], + additional_access_rights_len * sizeof(MfDesfireFileAccessRights)); + file_settings_temp.access_rights_len += additional_access_rights_len; + } + + *data = file_settings_temp; parsed = true; } while(false); @@ -392,18 +423,19 @@ bool mf_desfire_file_settings_load( break; furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_ACCESS_RIGHTS_KEY); - if(!flipper_format_read_hex( - ff, - furi_string_get_cstr(key), - (uint8_t*)&data->access_rights, - sizeof(MfDesfireFileAccessRights))) + uint32_t access_rights_len = 0; + if(!flipper_format_get_value_count(ff, furi_string_get_cstr(key), &access_rights_len)) break; + if((access_rights_len == 0) || ((access_rights_len % 2) != 0)) break; + if(!flipper_format_read_hex( + ff, furi_string_get_cstr(key), (uint8_t*)&data->access_rights, access_rights_len)) + break; + data->access_rights_len = access_rights_len / sizeof(MfDesfireFileAccessRights); if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) { furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_SIZE_KEY); if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->data.size, 1)) break; - } else if(data->type == MfDesfireFileTypeValue) { furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_HI_LIMIT_KEY); if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->value.hi_limit, 1)) @@ -641,8 +673,8 @@ bool mf_desfire_file_settings_save( if(!flipper_format_write_hex( ff, furi_string_get_cstr(key), - (const uint8_t*)&data->access_rights, - sizeof(MfDesfireFileAccessRights))) + (const uint8_t*)data->access_rights, + data->access_rights_len * sizeof(MfDesfireFileAccessRights))) break; if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) { @@ -737,8 +769,11 @@ bool mf_desfire_application_save( if(i != key_version_count) break; const uint32_t file_count = simple_array_get_count(data->file_ids); - if(!mf_desfire_file_ids_save(simple_array_get_data(data->file_ids), file_count, prefix, ff)) - break; + if(file_count > 0) { + if(!mf_desfire_file_ids_save( + simple_array_get_data(data->file_ids), file_count, prefix, ff)) + break; + } for(i = 0; i < file_count; ++i) { const MfDesfireFileId* file_id = simple_array_cget(data->file_ids, i); diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_i.h b/lib/nfc/protocols/mf_desfire/mf_desfire_i.h index 05381096d..21250baac 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_i.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_i.h @@ -5,6 +5,52 @@ #define MF_DESFIRE_FFF_PICC_PREFIX "PICC" #define MF_DESFIRE_FFF_APP_PREFIX "Application" +// Successful operation +#define MF_DESFIRE_STATUS_OPERATION_OK (0x00) +// No changes done to backup files, CommitTransaction / AbortTransaction not necessary +#define MF_DESFIRE_STATUS_NO_CHANGES (0x0C) +// Insufficient NV-Memory to complete command +#define MF_DESFIRE_STATUS_OUT_OF_EEPROM_ERROR (0x0E) +// Command code not supported +#define MF_DESFIRE_STATUS_ILLEGAL_COMMAND_CODE (0x1C) +// CRC or MAC does not match data Padding bytes not valid +#define MF_DESFIRE_STATUS_INTEGRITY_ERROR (0x1E) +// Invalid key number specified +#define MF_DESFIRE_STATUS_NO_SUCH_KEY (0x40) +// Length of command string invalid +#define MF_DESFIRE_STATUS_LENGTH_ERROR (0x7E) +// Current configuration / status does not allow the requested command +#define MF_DESFIRE_STATUS_PERMISSION_DENIED (0x9D) +// Value of the parameter(s) invalid +#define MF_DESFIRE_STATUS_PARAMETER_ERROR (0x9E) +// Requested AID not present on PICC +#define MF_DESFIRE_STATUS_APPLICATION_NOT_FOUND (0xA0) +// Unrecoverable error within application, application will be disabled +#define MF_DESFIRE_STATUS_APPL_INTEGRITY_ERROR (0xA1) +// Current authentication status does not allow the requested command +#define MF_DESFIRE_STATUS_AUTHENTICATION_ERROR (0xAE) +// Additional data frame is expected to be sent +#define MF_DESFIRE_STATUS_ADDITIONAL_FRAME (0xAF) +// Attempt to read/write data from/to beyond the file's/record's limits +// Attempt to exceed the limits of a value file. +#define MF_DESFIRE_STATUS_BOUNDARY_ERROR (0xBE) +// Unrecoverable error within PICC, PICC will be disabled +#define MF_DESFIRE_STATUS_PICC_INTEGRITY_ERROR (0xC1) +// Previous Command was not fully completed. Not all Frames were requested or provided by the PCD +#define MF_DESFIRE_STATUS_COMMAND_ABORTED (0xCA) +// PICC was disabled by an unrecoverable error +#define MF_DESFIRE_STATUS_PICC_DISABLED_ERROR (0xCD) +// Number of Applications limited to 28, no additional CreateApplication possible +#define MF_DESFIRE_STATUS_COUNT_ERROR (0xCE) +// Creation of file/application failed because file/application with same number already exists +#define MF_DESFIRE_STATUS_DUBLICATE_ERROR (0xDE) +// Could not complete NV-write operation due to loss of power, internal backup/rollback mechanism activated +#define MF_DESFIRE_STATUS_EEPROM_ERROR (0xEE) +// Specified file number does not exist +#define MF_DESFIRE_STATUS_FILE_NOT_FOUND (0xF0) +// Unrecoverable error within file, file will be disabled +#define MF_DESFIRE_STATUS_FILE_INTEGRITY_ERROR (0xF1) + // SimpleArray configurations extern const SimpleArrayConfig mf_desfire_key_version_array_config; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c index c9d8bbab6..fa8a7ae9b 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c @@ -75,17 +75,23 @@ static NfcCommand mf_desfire_poller_handler_read_version(MfDesfirePoller* instan } static NfcCommand mf_desfire_poller_handler_read_free_memory(MfDesfirePoller* instance) { + NfcCommand command = NfcCommandContinue; + instance->error = mf_desfire_poller_read_free_memory(instance, &instance->data->free_memory); if(instance->error == MfDesfireErrorNone) { FURI_LOG_D(TAG, "Read free memory success"); instance->state = MfDesfirePollerStateReadMasterKeySettings; + } else if(instance->error == MfDesfireErrorNotPresent) { + FURI_LOG_D(TAG, "Read free memoty is unsupported"); + instance->state = MfDesfirePollerStateReadMasterKeySettings; + command = NfcCommandReset; } else { FURI_LOG_E(TAG, "Failed to read free memory"); iso14443_4a_poller_halt(instance->iso14443_4a_poller); instance->state = MfDesfirePollerStateReadFailed; } - return NfcCommandContinue; + return command; } static NfcCommand mf_desfire_poller_handler_read_master_key_settings(MfDesfirePoller* instance) { @@ -94,6 +100,11 @@ static NfcCommand mf_desfire_poller_handler_read_master_key_settings(MfDesfirePo if(instance->error == MfDesfireErrorNone) { FURI_LOG_D(TAG, "Read master key settings success"); instance->state = MfDesfirePollerStateReadMasterKeyVersion; + } else if(instance->error == MfDesfireErrorAuthentication) { + FURI_LOG_D(TAG, "Auth is required to read master key settings and app ids"); + instance->data->master_key_settings.is_free_directory_list = false; + instance->data->master_key_settings.max_keys = 1; + instance->state = MfDesfirePollerStateReadMasterKeyVersion; } else { FURI_LOG_E(TAG, "Failed to read master key settings"); iso14443_4a_poller_halt(instance->iso14443_4a_poller); @@ -110,7 +121,11 @@ static NfcCommand mf_desfire_poller_handler_read_master_key_version(MfDesfirePol instance->data->master_key_settings.max_keys); if(instance->error == MfDesfireErrorNone) { FURI_LOG_D(TAG, "Read master key version success"); - instance->state = MfDesfirePollerStateReadApplicationIds; + if(instance->data->master_key_settings.is_free_directory_list) { + instance->state = MfDesfirePollerStateReadApplicationIds; + } else { + instance->state = MfDesfirePollerStateReadSuccess; + } } else { FURI_LOG_E(TAG, "Failed to read master key version"); iso14443_4a_poller_halt(instance->iso14443_4a_poller); @@ -126,6 +141,9 @@ static NfcCommand mf_desfire_poller_handler_read_application_ids(MfDesfirePoller if(instance->error == MfDesfireErrorNone) { FURI_LOG_D(TAG, "Read application ids success"); instance->state = MfDesfirePollerStateReadApplications; + } else if(instance->error == MfDesfireErrorAuthentication) { + FURI_LOG_D(TAG, "Read application ids impossible without authentication"); + instance->state = MfDesfirePollerStateReadSuccess; } else { FURI_LOG_E(TAG, "Failed to read application ids"); iso14443_4a_poller_halt(instance->iso14443_4a_poller); diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c index 790f11715..2b8631849 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c @@ -19,6 +19,17 @@ MfDesfireError mf_desfire_process_error(Iso14443_4aError error) { } } +MfDesfireError mf_desfire_process_status_code(uint8_t status_code) { + switch(status_code) { + case MF_DESFIRE_STATUS_OPERATION_OK: + return MfDesfireErrorNone; + case MF_DESFIRE_STATUS_AUTHENTICATION_ERROR: + return MfDesfireErrorAuthentication; + default: + return MfDesfireErrorProtocol; + } +} + MfDesfireError mf_desfire_send_chunks( MfDesfirePoller* instance, const BitBuffer* tx_buffer, @@ -42,7 +53,7 @@ MfDesfireError mf_desfire_send_chunks( } bit_buffer_reset(instance->tx_buffer); - bit_buffer_append_byte(instance->tx_buffer, MF_DESFIRE_FLAG_HAS_NEXT); + bit_buffer_append_byte(instance->tx_buffer, MF_DESFIRE_STATUS_ADDITIONAL_FRAME); if(bit_buffer_get_size_bytes(instance->rx_buffer) > sizeof(uint8_t)) { bit_buffer_copy_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t)); @@ -50,7 +61,8 @@ MfDesfireError mf_desfire_send_chunks( bit_buffer_reset(rx_buffer); } - while(bit_buffer_starts_with_byte(instance->rx_buffer, MF_DESFIRE_FLAG_HAS_NEXT)) { + while( + bit_buffer_starts_with_byte(instance->rx_buffer, MF_DESFIRE_STATUS_ADDITIONAL_FRAME)) { Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block( instance->iso14443_4a_poller, instance->tx_buffer, instance->rx_buffer); @@ -71,6 +83,11 @@ MfDesfireError mf_desfire_send_chunks( } } while(false); + if(error == MfDesfireErrorNone) { + uint8_t err_code = bit_buffer_get_byte(instance->rx_buffer, 0); + error = mf_desfire_process_status_code(err_code); + } + return error; } @@ -110,7 +127,7 @@ MfDesfireError if(error != MfDesfireErrorNone) break; if(!mf_desfire_free_memory_parse(data, instance->result_buffer)) { - error = MfDesfireErrorProtocol; + error = MfDesfireErrorNotPresent; } } while(false); @@ -414,13 +431,25 @@ MfDesfireError mf_desfire_poller_read_file_data_multi( simple_array_init(data, file_id_count); } - for(uint32_t i = 0; i < file_id_count; ++i) { + for(size_t i = 0; i < file_id_count; ++i) { const MfDesfireFileId file_id = *(const MfDesfireFileId*)simple_array_cget(file_ids, i); const MfDesfireFileSettings* file_settings_cur = simple_array_cget(file_settings, i); const MfDesfireFileType file_type = file_settings_cur->type; MfDesfireFileData* file_data = simple_array_get(data, i); + bool can_read_data = false; + for(size_t j = 0; j < file_settings_cur->access_rights_len; j++) { + uint8_t read_access = (file_settings_cur->access_rights[j] >> 12) & 0x0f; + uint8_t read_write_access = (file_settings_cur->access_rights[j] >> 4) & 0x0f; + can_read_data = (read_access == 0x0e) || (read_write_access == 0x0e); + if(can_read_data) break; + } + if(!can_read_data) { + FURI_LOG_D(TAG, "Can't read file %zu data without authentication", i); + continue; + } + if(file_type == MfDesfireFileTypeStandard || file_type == MfDesfireFileTypeBackup) { error = mf_desfire_poller_read_file_data( instance, file_id, 0, file_settings_cur->data.size, file_data); @@ -432,8 +461,6 @@ MfDesfireError mf_desfire_poller_read_file_data_multi( error = mf_desfire_poller_read_file_records( instance, file_id, 0, file_settings_cur->data.size, file_data); } - - if(error != MfDesfireErrorNone) break; } return error; @@ -448,22 +475,36 @@ MfDesfireError do { error = mf_desfire_poller_read_key_settings(instance, &data->key_settings); + if(error == MfDesfireErrorAuthentication) { + FURI_LOG_D(TAG, "Auth is required to read master key settings and app ids"); + data->key_settings.is_free_directory_list = false; + error = MfDesfireErrorNone; + break; + } if(error != MfDesfireErrorNone) break; error = mf_desfire_poller_read_key_versions( instance, data->key_versions, data->key_settings.max_keys); - if(error != MfDesfireErrorNone) break; + if(error != MfDesfireErrorNone) { + FURI_LOG_E(TAG, "Failed to read key version: %d", error); + break; + } error = mf_desfire_poller_read_file_ids(instance, data->file_ids); - if(error != MfDesfireErrorNone) break; + if(error != MfDesfireErrorNone) { + FURI_LOG_E(TAG, "Failed to read file ids: %d", error); + break; + } error = mf_desfire_poller_read_file_settings_multi( instance, data->file_ids, data->file_settings); - if(error != MfDesfireErrorNone) break; + if(error != MfDesfireErrorNone) { + FURI_LOG_E(TAG, "Failed to read file settings: %d", error); + break; + } error = mf_desfire_poller_read_file_data_multi( instance, data->file_ids, data->file_settings, data->file_data); - if(error != MfDesfireErrorNone) break; } while(false); @@ -484,11 +525,13 @@ MfDesfireError mf_desfire_poller_read_applications( simple_array_init(data, app_id_count); } - for(uint32_t i = 0; i < app_id_count; ++i) { + for(size_t i = 0; i < app_id_count; ++i) { do { + FURI_LOG_D(TAG, "Selecting app %zu", i); error = mf_desfire_poller_select_application(instance, simple_array_cget(app_ids, i)); if(error != MfDesfireErrorNone) break; + FURI_LOG_D(TAG, "Reading app %zu", i); MfDesfireApplication* current_app = simple_array_get(data, i); error = mf_desfire_poller_read_application(instance, current_app); diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h index 1c80af36f..19e38bebb 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h @@ -47,6 +47,8 @@ struct MfDesfirePoller { MfDesfireError mf_desfire_process_error(Iso14443_4aError error); +MfDesfireError mf_desfire_process_status_code(uint8_t status_code); + const MfDesfireData* mf_desfire_poller_get_data(MfDesfirePoller* instance); #ifdef __cplusplus diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 6e597e7d0..703bcc50c 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.7,, +Version,+,60.8,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 09b8b9ee8..c0edf9ffa 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.7,, +Version,+,60.8,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -2435,6 +2435,10 @@ Function,+,mf_classic_poller_get_nt,MfClassicError,"MfClassicPoller*, uint8_t, M Function,+,mf_classic_poller_get_nt_nested,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKeyType, MfClassicNt*" Function,+,mf_classic_poller_halt,MfClassicError,MfClassicPoller* Function,+,mf_classic_poller_read_block,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicBlock*" +Function,+,mf_classic_poller_send_custom_parity_frame,MfClassicError,"MfClassicPoller*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,mf_classic_poller_send_encrypted_frame,MfClassicError,"MfClassicPoller*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,mf_classic_poller_send_frame,MfClassicError,"MfClassicPoller*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,mf_classic_poller_send_standard_frame,MfClassicError,"MfClassicPoller*, const BitBuffer*, BitBuffer*, uint32_t" Function,+,mf_classic_poller_sync_auth,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*" Function,+,mf_classic_poller_sync_change_value,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, int32_t, int32_t*" Function,+,mf_classic_poller_sync_collect_nt,MfClassicError,"Nfc*, uint8_t, MfClassicKeyType, MfClassicNt*" From 16654d3c905862dbbd81fc70997e014fc32e0e5a Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Wed, 17 Apr 2024 00:25:55 +0100 Subject: [PATCH 109/129] Allow empty prefix in name generator --- lib/toolbox/name_generator.c | 72 +++++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 26 deletions(-) diff --git a/lib/toolbox/name_generator.c b/lib/toolbox/name_generator.c index 09e963b9e..ea6213d98 100644 --- a/lib/toolbox/name_generator.c +++ b/lib/toolbox/name_generator.c @@ -74,22 +74,29 @@ void name_generator_make_random_prefixed( uint8_t name_generator_left_i = rand() % COUNT_OF(name_generator_left); uint8_t name_generator_right_i = rand() % COUNT_OF(name_generator_right); - if(prefix_after) { - snprintf( - name, - max_name_size, - "%s-%s%s%s", - name_generator_left[name_generator_left_i], - name_generator_right[name_generator_right_i], - prefix ? "_" : "", - prefix ? prefix : ""); + if(prefix) { + if(prefix_after) { + snprintf( + name, + max_name_size, + "%s-%s_%s", + name_generator_left[name_generator_left_i], + name_generator_right[name_generator_right_i], + prefix); + } else { + snprintf( + name, + max_name_size, + "%s_%s-%s", + prefix, + name_generator_left[name_generator_left_i], + name_generator_right[name_generator_right_i]); + } } else { snprintf( name, max_name_size, - "%s%s%s-%s", - prefix ? prefix : "", - prefix ? "_" : "", + "%s-%s", name_generator_left[name_generator_left_i], name_generator_right[name_generator_right_i]); } @@ -119,24 +126,37 @@ void name_generator_make_detailed_datetime( furi_hal_rtc_get_datetime(&dateTime); } - if(prefix_after) { - snprintf( - name, - max_name_size, - "%.4d-%.2d-%.2d_%.2d,%.2d,%.2d_%s", - dateTime.year, - dateTime.month, - dateTime.day, - dateTime.hour, - dateTime.minute, - dateTime.second, - prefix); + if(prefix) { + if(prefix_after) { + snprintf( + name, + max_name_size, + "%.4d-%.2d-%.2d_%.2d,%.2d,%.2d_%s", + dateTime.year, + dateTime.month, + dateTime.day, + dateTime.hour, + dateTime.minute, + dateTime.second, + prefix); + } else { + snprintf( + name, + max_name_size, + "%s_%.4d-%.2d-%.2d_%.2d,%.2d,%.2d", + prefix, + dateTime.year, + dateTime.month, + dateTime.day, + dateTime.hour, + dateTime.minute, + dateTime.second); + } } else { snprintf( name, max_name_size, - "%s_%.4d-%.2d-%.2d_%.2d,%.2d,%.2d", - prefix, + "%.4d-%.2d-%.2d_%.2d,%.2d,%.2d", dateTime.year, dateTime.month, dateTime.day, From 3b490d5d1f3bf6c95555d65de509243efc22dc4f Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Wed, 17 Apr 2024 01:21:41 +0100 Subject: [PATCH 110/129] BLE: Allow bonding with GapPairingNone --- applications/main/bad_kb/bad_kb_app.c | 1 + targets/f7/ble_glue/gap.c | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/applications/main/bad_kb/bad_kb_app.c b/applications/main/bad_kb/bad_kb_app.c index 551eb24a2..3b71a19be 100644 --- a/applications/main/bad_kb/bad_kb_app.c +++ b/applications/main/bad_kb/bad_kb_app.c @@ -152,6 +152,7 @@ int32_t bad_kb_conn_apply(BadKbApp* app) { BadKbConfig* cfg = app->set_bt_id ? &app->id_config : &app->config; memcpy(&app->cur_ble_cfg, &cfg->ble, sizeof(cfg->ble)); app->cur_ble_cfg.bonding = app->bt_remember; + // TODO: Allow GapPairingNone with BT Remember ON if(app->bt_remember) { app->cur_ble_cfg.pairing = GapPairingPinCodeVerifyYesNo; } else { diff --git a/targets/f7/ble_glue/gap.c b/targets/f7/ble_glue/gap.c index 1d535664e..de8b6f44c 100644 --- a/targets/f7/ble_glue/gap.c +++ b/targets/f7/ble_glue/gap.c @@ -372,7 +372,6 @@ static void gap_init_svc(Gap* gap) { // Set default PHY hci_le_set_default_phy(ALL_PHYS_PREFERENCE, TX_2M_PREFERRED, RX_2M_PREFERRED); // Set I/O capability - bool bonding_mode = gap->config->bonding_mode; uint8_t cfg_mitm_protection = CFG_MITM_PROTECTION; uint8_t cfg_used_fixed_pin = CFG_USED_FIXED_PIN; bool keypress_supported = false; @@ -383,7 +382,6 @@ static void gap_init_svc(Gap* gap) { keypress_supported = true; } else if(gap->config->pairing_method == GapPairingNone) { // "Just works" pairing method (iOS accepts it, it seems Android and Linux don't) - bonding_mode = false; cfg_mitm_protection = MITM_PROTECTION_NOT_REQUIRED; cfg_used_fixed_pin = USE_FIXED_PIN_FOR_PAIRING_ALLOWED; // If "just works" isn't supported, we want the numeric comparaison method @@ -392,7 +390,7 @@ static void gap_init_svc(Gap* gap) { } // Setup authentication aci_gap_set_authentication_requirement( - bonding_mode, + gap->config->bonding_mode, cfg_mitm_protection, CFG_SC_SUPPORT, keypress_supported, From 053040e3080cf68553810b5c57eeb83880b31b1e Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Wed, 17 Apr 2024 01:22:43 +0100 Subject: [PATCH 111/129] BadKB: Fix mac address for remember on/off --- applications/main/bad_kb/bad_kb_app.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/bad_kb/bad_kb_app.c b/applications/main/bad_kb/bad_kb_app.c index 3b71a19be..5b2e86b2e 100644 --- a/applications/main/bad_kb/bad_kb_app.c +++ b/applications/main/bad_kb/bad_kb_app.c @@ -155,9 +155,9 @@ int32_t bad_kb_conn_apply(BadKbApp* app) { // TODO: Allow GapPairingNone with BT Remember ON if(app->bt_remember) { app->cur_ble_cfg.pairing = GapPairingPinCodeVerifyYesNo; + memcpy(app->cur_ble_cfg.mac, BAD_KB_BOUND_MAC, sizeof(BAD_KB_BOUND_MAC)); } else { app->cur_ble_cfg.pairing = GapPairingNone; - memcpy(app->cur_ble_cfg.mac, BAD_KB_BOUND_MAC, sizeof(BAD_KB_BOUND_MAC)); } // Set profile From a19598c44daff27ae91203156210757b1386a7a8 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Wed, 17 Apr 2024 02:45:43 +0100 Subject: [PATCH 112/129] BadKB: Improve BT Remember handling --- applications/main/bad_kb/bad_kb_app.c | 27 ++++++++++--------- applications/main/bad_kb/bad_kb_app_i.h | 1 - .../main/bad_kb/helpers/ducky_script.c | 3 +++ .../main/bad_kb/scenes/bad_kb_scene_config.c | 20 +++++++++----- 4 files changed, 31 insertions(+), 20 deletions(-) diff --git a/applications/main/bad_kb/bad_kb_app.c b/applications/main/bad_kb/bad_kb_app.c index 5b2e86b2e..be9fc020f 100644 --- a/applications/main/bad_kb/bad_kb_app.c +++ b/applications/main/bad_kb/bad_kb_app.c @@ -47,8 +47,8 @@ void bad_kb_load_settings(BadKbApp* app) { flipper_format_rewind(file); } - if(!flipper_format_read_bool(file, "Bt_Remember", &app->bt_remember, 1)) { - app->bt_remember = false; + if(!flipper_format_read_bool(file, "Bt_Remember", &cfg->ble.bonding, 1)) { + cfg->ble.bonding = false; flipper_format_rewind(file); } @@ -117,7 +117,7 @@ static void bad_kb_save_settings(BadKbApp* app) { if(flipper_format_file_open_always(file, BAD_KB_SETTINGS_PATH)) { flipper_format_write_string(file, "Keyboard_Layout", app->keyboard_layout); flipper_format_write_bool(file, "Is_Bt", &app->is_bt, 1); - flipper_format_write_bool(file, "Bt_Remember", &app->bt_remember, 1); + flipper_format_write_bool(file, "Bt_Remember", &cfg->ble.bonding, 1); flipper_format_write_string_cstr(file, "Bt_Name", cfg->ble.name); flipper_format_write_hex(file, "Bt_Mac", (uint8_t*)&cfg->ble.mac, sizeof(cfg->ble.mac)); flipper_format_write_string_cstr(file, "Usb_Manuf", cfg->usb.manuf); @@ -143,23 +143,24 @@ void bad_kb_app_show_loading_popup(BadKbApp* app, bool show) { int32_t bad_kb_conn_apply(BadKbApp* app) { if(app->is_bt) { - bt_timeout = bt_hid_delays[LevelRssi39_0]; - bt_disconnect(app->bt); - furi_delay_ms(200); - bt_keys_storage_set_storage_path(app->bt, BAD_KB_KEYS_PATH); - - // Setup new config + // Setup profile config BadKbConfig* cfg = app->set_bt_id ? &app->id_config : &app->config; memcpy(&app->cur_ble_cfg, &cfg->ble, sizeof(cfg->ble)); - app->cur_ble_cfg.bonding = app->bt_remember; - // TODO: Allow GapPairingNone with BT Remember ON - if(app->bt_remember) { + if(app->cur_ble_cfg.bonding) { app->cur_ble_cfg.pairing = GapPairingPinCodeVerifyYesNo; + // Hardcode mac for remember mode + // Change in config copy to preserve user choice for non-remember mode memcpy(app->cur_ble_cfg.mac, BAD_KB_BOUND_MAC, sizeof(BAD_KB_BOUND_MAC)); } else { app->cur_ble_cfg.pairing = GapPairingNone; } + // Prepare for new profile + bt_timeout = bt_hid_delays[LevelRssi39_0]; + bt_disconnect(app->bt); + furi_delay_ms(200); + bt_keys_storage_set_storage_path(app->bt, BAD_KB_KEYS_PATH); + // Set profile app->ble_hid = bt_profile_start(app->bt, ble_profile_hid, &app->cur_ble_cfg); furi_check(app->ble_hid); @@ -247,7 +248,7 @@ void bad_kb_config_refresh(BadKbApp* app) { bad_kb_conn_reset(app); } else { BleProfileHidParams* cur = &app->cur_ble_cfg; - apply = apply || cfg->ble.bonding != app->bt_remember; + apply = apply || cfg->ble.bonding != cur->bonding; apply = apply || strncmp(cfg->ble.name, cur->name, sizeof(cfg->ble.name)); apply = apply || memcmp(cfg->ble.mac, cur->mac, sizeof(cfg->ble.mac)); } diff --git a/applications/main/bad_kb/bad_kb_app_i.h b/applications/main/bad_kb/bad_kb_app_i.h index e009f0e60..a7d3844ac 100644 --- a/applications/main/bad_kb/bad_kb_app_i.h +++ b/applications/main/bad_kb/bad_kb_app_i.h @@ -72,7 +72,6 @@ struct BadKbApp { Bt* bt; bool is_bt; - bool bt_remember; BadKbConfig config; // User options BadKbConfig id_config; // ID and BT_ID values diff --git a/applications/main/bad_kb/helpers/ducky_script.c b/applications/main/bad_kb/helpers/ducky_script.c index 4e7e48e22..70aec93af 100644 --- a/applications/main/bad_kb/helpers/ducky_script.c +++ b/applications/main/bad_kb/helpers/ducky_script.c @@ -344,6 +344,9 @@ static bool ducky_set_bt_id(BadKbScript* bad_kb, const char* line) { strlcpy(cfg->ble.name, line + mac_len, sizeof(cfg->ble.name)); FURI_LOG_D(WORKER_TAG, "set bt id: %s", line); + + // Can't set bonding via BT_ID, sync with user choice instead + cfg->ble.bonding = bad_kb->app->config.ble.bonding; return true; } diff --git a/applications/main/bad_kb/scenes/bad_kb_scene_config.c b/applications/main/bad_kb/scenes/bad_kb_scene_config.c index cc7cefdc0..d12863a7c 100644 --- a/applications/main/bad_kb/scenes/bad_kb_scene_config.c +++ b/applications/main/bad_kb/scenes/bad_kb_scene_config.c @@ -29,8 +29,14 @@ void bad_kb_scene_config_connection_callback(VariableItem* item) { void bad_kb_scene_config_bt_remember_callback(VariableItem* item) { BadKbApp* bad_kb = variable_item_get_context(item); - bad_kb->bt_remember = variable_item_get_current_value_index(item); - variable_item_set_current_value_text(item, bad_kb->bt_remember ? "ON" : "OFF"); + bool value = variable_item_get_current_value_index(item); + // Set user config and remember + bad_kb->config.ble.bonding = value; + // Apply to ID config so its temporarily overridden (currently can't set bonding with BT_ID anyway) + if(bad_kb->set_bt_id) { + bad_kb->id_config.ble.bonding = value; + } + variable_item_set_current_value_text(item, value ? "ON" : "OFF"); view_dispatcher_send_custom_event(bad_kb->view_dispatcher, VarItemListIndexBtRemember); } @@ -52,20 +58,22 @@ void bad_kb_scene_config_on_enter(void* context) { variable_item_set_current_value_text(item, bad_kb->is_bt ? "BT" : "USB"); if(bad_kb->is_bt) { + BadKbConfig* cfg = bad_kb->set_bt_id ? &bad_kb->id_config : &bad_kb->config; + item = variable_item_list_add( var_item_list, "BT Remember", 2, bad_kb_scene_config_bt_remember_callback, bad_kb); - variable_item_set_current_value_index(item, bad_kb->bt_remember); - variable_item_set_current_value_text(item, bad_kb->bt_remember ? "ON" : "OFF"); + variable_item_set_current_value_index(item, cfg->ble.bonding); + variable_item_set_current_value_text(item, cfg->ble.bonding ? "ON" : "OFF"); item = variable_item_list_add(var_item_list, "BT Device Name", 0, NULL, bad_kb); item = variable_item_list_add(var_item_list, "BT MAC Address", 0, NULL, bad_kb); - if(bad_kb->bt_remember) { + if(cfg->ble.bonding) { variable_item_set_locked(item, true, "Remember\nmust be Off!"); } item = variable_item_list_add(var_item_list, "Randomize BT MAC", 0, NULL, bad_kb); - if(bad_kb->bt_remember) { + if(cfg->ble.bonding) { variable_item_set_locked(item, true, "Remember\nmust be Off!"); } } else { From 5798446a09de4371b22634e85659ae09f0ca6141 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Wed, 17 Apr 2024 02:47:23 +0100 Subject: [PATCH 113/129] BadKB: Choose BT Pairing security mode (YesNo, PIN Type, Pin Y/N) --- applications/main/bad_kb/bad_kb_app.c | 14 +++++++-- .../main/bad_kb/helpers/ducky_script.c | 3 +- .../main/bad_kb/scenes/bad_kb_scene_config.c | 31 +++++++++++++++++++ targets/f7/ble_glue/gap.h | 1 + 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/applications/main/bad_kb/bad_kb_app.c b/applications/main/bad_kb/bad_kb_app.c index be9fc020f..21e160264 100644 --- a/applications/main/bad_kb/bad_kb_app.c +++ b/applications/main/bad_kb/bad_kb_app.c @@ -36,6 +36,7 @@ void bad_kb_load_settings(BadKbApp* app) { FlipperFormat* file = flipper_format_file_alloc(storage); if(flipper_format_file_open_existing(file, BAD_KB_SETTINGS_PATH)) { FuriString* tmp_str = furi_string_alloc(); + uint32_t tmp_uint = 0; if(!flipper_format_read_string(file, "Keyboard_Layout", app->keyboard_layout)) { furi_string_reset(app->keyboard_layout); @@ -52,6 +53,12 @@ void bad_kb_load_settings(BadKbApp* app) { flipper_format_rewind(file); } + if(!flipper_format_read_uint32(file, "Bt_Pairing", &tmp_uint, 1)) { + tmp_uint = GapPairingNone; + flipper_format_rewind(file); + } + cfg->ble.pairing = tmp_uint; + if(flipper_format_read_string(file, "Bt_Name", tmp_str)) { strlcpy(cfg->ble.name, furi_string_get_cstr(tmp_str), sizeof(cfg->ble.name)); } else { @@ -115,9 +122,12 @@ static void bad_kb_save_settings(BadKbApp* app) { Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* file = flipper_format_file_alloc(storage); if(flipper_format_file_open_always(file, BAD_KB_SETTINGS_PATH)) { + uint32_t tmp_uint = 0; flipper_format_write_string(file, "Keyboard_Layout", app->keyboard_layout); flipper_format_write_bool(file, "Is_Bt", &app->is_bt, 1); flipper_format_write_bool(file, "Bt_Remember", &cfg->ble.bonding, 1); + tmp_uint = cfg->ble.pairing; + flipper_format_write_uint32(file, "Bt_Pairing", &tmp_uint, 1); flipper_format_write_string_cstr(file, "Bt_Name", cfg->ble.name); flipper_format_write_hex(file, "Bt_Mac", (uint8_t*)&cfg->ble.mac, sizeof(cfg->ble.mac)); flipper_format_write_string_cstr(file, "Usb_Manuf", cfg->usb.manuf); @@ -147,12 +157,9 @@ int32_t bad_kb_conn_apply(BadKbApp* app) { BadKbConfig* cfg = app->set_bt_id ? &app->id_config : &app->config; memcpy(&app->cur_ble_cfg, &cfg->ble, sizeof(cfg->ble)); if(app->cur_ble_cfg.bonding) { - app->cur_ble_cfg.pairing = GapPairingPinCodeVerifyYesNo; // Hardcode mac for remember mode // Change in config copy to preserve user choice for non-remember mode memcpy(app->cur_ble_cfg.mac, BAD_KB_BOUND_MAC, sizeof(BAD_KB_BOUND_MAC)); - } else { - app->cur_ble_cfg.pairing = GapPairingNone; } // Prepare for new profile @@ -249,6 +256,7 @@ void bad_kb_config_refresh(BadKbApp* app) { } else { BleProfileHidParams* cur = &app->cur_ble_cfg; apply = apply || cfg->ble.bonding != cur->bonding; + apply = apply || cfg->ble.pairing != cur->pairing; apply = apply || strncmp(cfg->ble.name, cur->name, sizeof(cfg->ble.name)); apply = apply || memcmp(cfg->ble.mac, cur->mac, sizeof(cfg->ble.mac)); } diff --git a/applications/main/bad_kb/helpers/ducky_script.c b/applications/main/bad_kb/helpers/ducky_script.c index 70aec93af..925bce5d4 100644 --- a/applications/main/bad_kb/helpers/ducky_script.c +++ b/applications/main/bad_kb/helpers/ducky_script.c @@ -345,8 +345,9 @@ static bool ducky_set_bt_id(BadKbScript* bad_kb, const char* line) { strlcpy(cfg->ble.name, line + mac_len, sizeof(cfg->ble.name)); FURI_LOG_D(WORKER_TAG, "set bt id: %s", line); - // Can't set bonding via BT_ID, sync with user choice instead + // Can't set bonding and pairing via BT_ID, sync with user choice instead cfg->ble.bonding = bad_kb->app->config.ble.bonding; + cfg->ble.pairing = bad_kb->app->config.ble.pairing; return true; } diff --git a/applications/main/bad_kb/scenes/bad_kb_scene_config.c b/applications/main/bad_kb/scenes/bad_kb_scene_config.c index d12863a7c..52e12af9c 100644 --- a/applications/main/bad_kb/scenes/bad_kb_scene_config.c +++ b/applications/main/bad_kb/scenes/bad_kb_scene_config.c @@ -8,6 +8,7 @@ enum VarItemListIndex { enum VarItemListIndexBt { VarItemListIndexBtRemember = VarItemListIndexConnection + 1, + VarItemListIndexBtPairing, VarItemListIndexBtDeviceName, VarItemListIndexBtMacAddress, VarItemListIndexBtRandomizeMac, @@ -40,6 +41,24 @@ void bad_kb_scene_config_bt_remember_callback(VariableItem* item) { view_dispatcher_send_custom_event(bad_kb->view_dispatcher, VarItemListIndexBtRemember); } +const char* const bt_pairing_names[GapPairingCount] = { + "YesNo", + "PIN Type", + "PIN Y/N", +}; +void bad_kb_scene_config_bt_pairing_callback(VariableItem* item) { + BadKbApp* bad_kb = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + // Set user config and remember + bad_kb->config.ble.pairing = index; + // Apply to ID config so its temporarily overridden (currently can't set pairing with BT_ID anyway) + if(bad_kb->set_bt_id) { + bad_kb->id_config.ble.pairing = index; + } + variable_item_set_current_value_text(item, bt_pairing_names[index]); + view_dispatcher_send_custom_event(bad_kb->view_dispatcher, VarItemListIndexBtPairing); +} + void bad_kb_scene_config_var_item_list_callback(void* context, uint32_t index) { BadKbApp* bad_kb = context; view_dispatcher_send_custom_event(bad_kb->view_dispatcher, index); @@ -65,6 +84,15 @@ void bad_kb_scene_config_on_enter(void* context) { variable_item_set_current_value_index(item, cfg->ble.bonding); variable_item_set_current_value_text(item, cfg->ble.bonding ? "ON" : "OFF"); + item = variable_item_list_add( + var_item_list, + "BT Pairing", + GapPairingCount, + bad_kb_scene_config_bt_pairing_callback, + bad_kb); + variable_item_set_current_value_index(item, cfg->ble.pairing); + variable_item_set_current_value_text(item, bt_pairing_names[cfg->ble.pairing]); + item = variable_item_list_add(var_item_list, "BT Device Name", 0, NULL, bad_kb); item = variable_item_list_add(var_item_list, "BT MAC Address", 0, NULL, bad_kb); @@ -117,6 +145,9 @@ bool bad_kb_scene_config_on_event(void* context, SceneManagerEvent event) { case VarItemListIndexBtRemember: bad_kb_config_refresh(bad_kb); break; + case VarItemListIndexBtPairing: + bad_kb_config_refresh(bad_kb); + break; case VarItemListIndexBtDeviceName: scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneConfigBtName); break; diff --git a/targets/f7/ble_glue/gap.h b/targets/f7/ble_glue/gap.h index 8ee4b3d91..4376570ad 100644 --- a/targets/f7/ble_glue/gap.h +++ b/targets/f7/ble_glue/gap.h @@ -52,6 +52,7 @@ typedef enum { GapPairingNone, GapPairingPinCodeShow, GapPairingPinCodeVerifyYesNo, + GapPairingCount, } GapPairing; typedef struct { From 30414e864be63217f91fe5570cbebc81677838a4 Mon Sep 17 00:00:00 2001 From: Zachary Weiss Date: Wed, 17 Apr 2024 00:19:03 -0400 Subject: [PATCH 114/129] Impl basic favorite ability for WAVs Namely just for the sake of favorite-ing the cart lock/unlock. Requires changes to WAV player ext fap, as to be PR'd from the Momentum-Apps repo. --- applications/main/archive/helpers/archive_browser.h | 1 + applications/main/archive/helpers/archive_files.h | 1 + applications/main/archive/scenes/archive_scene_browser.c | 2 ++ applications/main/archive/views/archive_browser_view.c | 1 + 4 files changed, 5 insertions(+) diff --git a/applications/main/archive/helpers/archive_browser.h b/applications/main/archive/helpers/archive_browser.h index 6966dc8de..be5c6a4d0 100644 --- a/applications/main/archive/helpers/archive_browser.h +++ b/applications/main/archive/helpers/archive_browser.h @@ -33,6 +33,7 @@ static const char* known_ext[] = { [ArchiveFileTypeSubghzRemote] = ".txt", [ArchiveFileTypeInfraredRemote] = ".txt", [ArchiveFileTypeBadKb] = ".txt", + [ArchiveFileTypeWAV] = ".wav", [ArchiveFileTypeU2f] = "?", [ArchiveFileTypeApplication] = ".fap", [ArchiveFileTypeJS] = ".js", diff --git a/applications/main/archive/helpers/archive_files.h b/applications/main/archive/helpers/archive_files.h index 61232f943..2872105ef 100644 --- a/applications/main/archive/helpers/archive_files.h +++ b/applications/main/archive/helpers/archive_files.h @@ -19,6 +19,7 @@ typedef enum { ArchiveFileTypeSubghzRemote, ArchiveFileTypeInfraredRemote, ArchiveFileTypeBadKb, + ArchiveFileTypeWAV, ArchiveFileTypeU2f, ArchiveFileTypeApplication, ArchiveFileTypeJS, diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index 3f37990d1..6942144aa 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -32,6 +32,8 @@ const char* archive_get_flipper_app_name(ArchiveFileTypeEnum file_type) { return EXT_PATH("apps/Infrared/ir_remote.fap"); case ArchiveFileTypeBadKb: return "Bad KB"; + case ArchiveFileTypeWAV: + return EXT_PATH("apps/Media/wav_player.fap"); case ArchiveFileTypeU2f: return "U2F"; case ArchiveFileTypeUpdateManifest: diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index c29dec947..8894496e8 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -34,6 +34,7 @@ static const Icon* ArchiveItemIcons[] = { [ArchiveFileTypeSubghzRemote] = &I_subrem_10px, [ArchiveFileTypeInfraredRemote] = &I_ir_scope_10px, [ArchiveFileTypeBadKb] = &I_badkb_10px, + [ArchiveFileTypeWAV] = &I_music_10px, [ArchiveFileTypeU2f] = &I_u2f_10px, [ArchiveFileTypeApplication] = &I_Apps_10px, [ArchiveFileTypeJS] = &I_js_script_10px, From e4bba06e6e13754e6217f2ed2e2a5c40269cd029 Mon Sep 17 00:00:00 2001 From: Zachary Weiss Date: Wed, 17 Apr 2024 00:27:09 -0400 Subject: [PATCH 115/129] Update WAV fap for compatibility --- applications/external | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/external b/applications/external index f6adf0695..f07702622 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit f6adf0695636a0c4d048eec8756f66284636c789 +Subproject commit f07702622b868ff1b1dddf49ed03c48f06bb3815 From 938c051eae655bce5d0d964dd634e73aceecaed5 Mon Sep 17 00:00:00 2001 From: Kuronons <110337784+Kuronons@users.noreply.github.com> Date: Wed, 17 Apr 2024 06:47:45 +0200 Subject: [PATCH 116/129] Delete assets/dolphin/internal/L1_NoSd_128x49/frame_1.png --- .../dolphin/internal/L1_NoSd_128x49/frame_1.png | Bin 1410 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 assets/dolphin/internal/L1_NoSd_128x49/frame_1.png diff --git a/assets/dolphin/internal/L1_NoSd_128x49/frame_1.png b/assets/dolphin/internal/L1_NoSd_128x49/frame_1.png deleted file mode 100644 index b58936d817d4967a344cef335547992207d239a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1410 zcmaJ>eM}o=7=K$n1RP43b_>X=C zon=d$_{UuQ182}Mr&%!Bh+?8i5Ty%Hzm zJL^kJ>QX#Hh(lArQt>|3;8Pxa1(-+c%r=Q*D=KEM2lr+?VjbZK=F<`obK7Y`q9_V; zb9N{TjOd67(hiKF7@DNf>n3P7&p3FR!?F*KJYrZ~LXPom{-&%)Bzf>2)6{vAOeT}| zq{FTyx=EVjIFe#Wh9Qs!VGO9IkRnuL>ncbV#t#iCq3EWfsaOV95Vd~OgQK8Vvx)2B z@EW>mWQ&H%N2Uawr0o z(eS$y3c4Vn&#y`Saj2RhzXwMrc3F{m8A>b*Ws%^Vl1RuR>m)caMiU$>$}BCr7&q
(kt?QPyye|O- zQ%giOtuIH2NRMV}MvtarjKfZ2)q53H){;g|?xN6Dt!D0r3FRP^n-W?aTd6g#tP_V! zIT;Sd7(#>&H$gEhO1~DmQ6Xdp?P4W2<7Dxyy?j#*S80oOP%e5dB%XCSVonDK3C77G zAdYnrB3nxlG%LhpD7tGINx(63g_tN%GDD)9NVQ7Xm0iTxOv8|C9g~sPx(=$S)e@+u zn6T|E>h`XX-y02nFf&qI{ytU|xKcRw&%sGd+S+jL^)&*ICrvK!XwO&=Cm_74xG8XkQ0Qo-j-_v{}1 zX!5YN=DU}{6PNMy&0zE0Q2AbB+O^tdrR zP>3xaKRfYzcsuA0?n}?U`$X|5m{OL_rHV^0+_xDVB!7JDtK=w{zN_JyMQCLwE8C+4;=mv&OgyK From 4d087bf6979483e11ce6435b3b3b8449ba84fdbf Mon Sep 17 00:00:00 2001 From: Kuronons <110337784+Kuronons@users.noreply.github.com> Date: Wed, 17 Apr 2024 06:47:56 +0200 Subject: [PATCH 117/129] Delete assets/dolphin/internal/L1_NoSd_128x49/frame_2.png --- .../dolphin/internal/L1_NoSd_128x49/frame_2.png | Bin 1416 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 assets/dolphin/internal/L1_NoSd_128x49/frame_2.png diff --git a/assets/dolphin/internal/L1_NoSd_128x49/frame_2.png b/assets/dolphin/internal/L1_NoSd_128x49/frame_2.png deleted file mode 100644 index 5b474fff262306ecccbdc2a0ace273bd26e55219..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1416 zcmaJ>ZA=?w9KQmaP*#Iu#5snZH%*x1dcEuQP zJpcdi|ML9)_o~06)nwdcL=eQ}ZQ}xPPQgJI6~gbe;q%pSGH8L;Mr3?$^G{GH>ht(K z2r_ZF^ftc+%4_}Y!RB;24VOD-=3jtQiPH9zh9LLOWQL)ldV2~%3f~F`yY+5gJ1fc& zGcU;kFvlYb)JBlHhPc9u13*Uw&=-!nup5&XF*Gc>ur8a=;!~PHf4D8Fg3e?|P)rVp zjD$7Rn~ZgF79vD|&ZF_jU{qt{E-VYo!gXewz|gFUKH$PU8INeU&yP0ADnK1(5*IBN z3+iCZK;kLE8sfzol(JAHLBiL8lMa@$vLu7%9t?U!(K=NMu>r0n=MhRStY6m^mLOuW zm^o%O%W5A%G7LjlD1xGJsDW#VsLsdnsJ3MpBnQI*O;p2*9+snM29_7(A>D<+pqI0W zC_di`dQ{654VI6H^9n(lEkq=eNinOf=>hP6EURj3!Gr>c0MO(iRRmCdOWqik@$QZ? ziZB}X2{jBah#%x+aVP?!x|efd@Wd>IB~}6=O#?~58Jj5Jl0e&VMhKBOLkkj3N_NUY z+VVK7*|%6pr>&7`wmTagB-z}=a8#p*@>m&;Avvb0DR1kIYC0bkLEd*5`p(&MceQ1k zRKV-98kFV1JR$u3vMy`=vVu}pGl^Drg`<)j)3)X>3SQN6<{VJN&jP7Ml_O}j)@*o{ zI3$aWVnB$(1z>gH7K(=HJAnfhLb8%}T69o08q3*Bchqp1wr~gLqU1wjX}dLKvoZju zYzzcqXge;@P76-bd`JRt^*#=&*394wfa zQF_#_Pb29|^G~O5*6dA;Y#M&@_~-tch*`P%?wMOf)33dA{gK^o6dE3gBkp)**`rm+ zU-i@U((Hj>>(QxyYmsyXGWxyAeXMl%4~uKvYrY%1*7DMco%7yQ`Pkb5V|#4cgxY!Bg19KQpSlFB|~DLUW}BUEU8BJAeQ0I zH$Mv|`6w!oGCmR()zIS#avN z3mrAJu|JZJR}o{AoGsO0+)>*zb?W}#FZg$*<_@){_SDtPI!Cu{HtgH_#q}Rc-Ok4z bbQeGLd>3K=>hGz{9~SX8cW~p4y)XX Date: Wed, 17 Apr 2024 06:48:07 +0200 Subject: [PATCH 118/129] Delete assets/dolphin/internal/L1_NoSd_128x49/frame_3.png --- .../dolphin/internal/L1_NoSd_128x49/frame_3.png | Bin 1415 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 assets/dolphin/internal/L1_NoSd_128x49/frame_3.png diff --git a/assets/dolphin/internal/L1_NoSd_128x49/frame_3.png b/assets/dolphin/internal/L1_NoSd_128x49/frame_3.png deleted file mode 100644 index 952f968fb673a5de24fefb834e6c8655cc29a6d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1415 zcmaJ>YfKbZ6rP1$3M|m1!Io$rG}Q*Aw$-#D_0`righz~p)@o8>+W3Ipg*$r8aLI}KTXh5}7)?H=RmGbV2$K1c9BeUK0(G3I zMr^#K3cwbRM4>f;l$OP#yx0j0Q~({a;=%5m8OBgq@?g!502zpSL0IBz1$&4WOHi7mDT0Ex3#VKhZRaQ!O1ts70?|6EqI)`L5ufM^1`+O3HIny*VcLmD3+ zC}{|@;Tkj<9uVKfoi2ZLX1k}Po&5E%wY0?s-_0ha{EfwMx0!dXU;7)o-| zF3OR{S;@cJ?W^!QNxRcs>7uAAZzan(p{ly7%IozxDyXz?t)d&eB7$^m8OBce(vS7! zyc*yQRST+WSGpk@!>Xa`VKs`M7%b&4J;LEq+d>VjC7Mo&W1RMo!_ESn zcCZi#@_`GCo5U%G4@p39xoMHdP-3w$VWLE`i5_XHrG{P9!j4T246!^hNojfNfC5KN zgEO`B?W1*Y+PBsEDuVv+#}Bt2{07bQ&8```z3cjcU(Xf&aA?B|-UH{|)0g~c_7fIi z&(zS3fp@RWzjN%={9mp2{w5uHpV99hy=7{f9Nfa+%}K0k7_Ys$o-j+3R%G;-MDeU8 zF_BrfO9I5&ry0M*q{7G7o0m z8rv{7{^2uynsp|N%xqdWD=f$dN%X`hA&UN3szID` Date: Wed, 17 Apr 2024 06:48:16 +0200 Subject: [PATCH 119/129] Delete assets/dolphin/internal/L1_NoSd_128x49/frame_4.png --- .../dolphin/internal/L1_NoSd_128x49/frame_4.png | Bin 1401 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 assets/dolphin/internal/L1_NoSd_128x49/frame_4.png diff --git a/assets/dolphin/internal/L1_NoSd_128x49/frame_4.png b/assets/dolphin/internal/L1_NoSd_128x49/frame_4.png deleted file mode 100644 index 2bb43b306f3bf9025ba68190fb4903cc5a132133..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1401 zcmaJ>Yitx%6rQCoyR}lQZ9;)C3{na>JCB`x%-FTvcDHn~yKR@PZG+m*<8C``cV;p( zWtXD3L4)Fp20@~KB!)&brN&sJf=M4~B8rwqNYsc1B_Sb}EK#FIO6uLVTlvv2nLGEM zd(ZjK<9=rjHHCH*TFa~mf)oboePK8!;h?g!;P=YG&nw_$F~U1)kdf(i*P)Qz{`vWNxNMocxF1gWT7A2LAooqChoz)qXA(iO-j*Y6W?Qg<6V;fF zmsA1R5-|;GBS>XcLgU3=V4?!(k`)h@Iy;7;vgE;99YHdv)q-xhen1Di20{^WpjTui ztm@H1Yh{9i2r*#tXd>397+k`GEdX2gx0D$KwUNp7fF&t zU91gAJT2Ixy!bFmlQcz8@O9yoi=*uv#iC0O20fx^r7lIeu&-{(Ba}Q?w`pn|LB!*6 zTik9_^)7;9S(YGaf~Ik(fg5`jlTYA^v1t)x3C0HuQI|DSRuwc2%L{71>A_&oi`m4q zU~m~-F_wx3%SR-5ji78K5sRf$T+lYmF!(=~6}64XUJVdoV5t4N2%!3=j4>?Z-5sSB zVKiK$F2f7r`+Ta{9|MXR@OdzJVv}TvlYq!DKoW4)Aqu!8FbX+kj%2ydr{(ZyEYtvUS|mmaEkP zZ>oAkRr@l8XzEr?)#z3=l(yR_w4zm3BsFeq&0G|`s>RHGK$rIbsZLj8=t8YId4)J6 z(m}HzO5*~syKs_bVES(0f`yRml#>x%w1dHx?4>(uxJX;L19Q4qVeJ0>kHcO6h*tLd<( zwq33E!)|X5_`H$&FK)cidt?f|=lL6JnZL(ob06&JEglWu>V5OlD_@Rx74$UZH!XN{*U z?{9so?qtgH*FPy_F7L7>VXYW(w_JIBPjOM!spDr~Qpy7-I?fDDAw9#fi^elZuI2X6 zL-_C3$EON92gmcZC)L`v`Bvms3%YFrxpvc?-4G9*OAcEaQ`@J1Iel;YXPfJURo~w~ z^>WXeqlK27>$|A<#*LPecgAkEA9I~cA~X5HWY$B-cv~pnR(^Q$=~bU*b#mJ;z4gtH zU#+h`KRNx=wX)JJgL%p5Q2B=0@3eI@N8Wwz!edprGfy-oFF(s1DjhsC{K?P*Ipzze za$jtn82#w<@ww{#jYo@4{BgCkb9VK4I+QATEje*)U)P^GXskVZ@Y~h-|JvdQ0=L(v O|8@a?$Tw2cdEh_m_R@*~ From 0188b022e5a8bd71728f997bab6bccc8a7ad4e7d Mon Sep 17 00:00:00 2001 From: Kuronons <110337784+Kuronons@users.noreply.github.com> Date: Wed, 17 Apr 2024 06:48:26 +0200 Subject: [PATCH 120/129] Delete assets/dolphin/internal/L1_NoSd_128x49/frame_5.png --- .../dolphin/internal/L1_NoSd_128x49/frame_5.png | Bin 1412 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 assets/dolphin/internal/L1_NoSd_128x49/frame_5.png diff --git a/assets/dolphin/internal/L1_NoSd_128x49/frame_5.png b/assets/dolphin/internal/L1_NoSd_128x49/frame_5.png deleted file mode 100644 index d7f8c6402a27c5a2eb565d12b595a3a438ed94ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1412 zcmaJ>eM}o=7{5Y6#+TX5+!*L?H~(N}*Xv!cJ?_>DwAVqE0>LhTBeU1Lw{R)FYwnIw z(71*v(P3uHf;h|sKcbjL5cULiX=$*GC0X_l#L@>h-#L%EmjY+*Ks$^|><` zoJRwKp=tqD?amUSxl1)vy-SUul+8k-m94TOslEEX>_x$=TF;yZntT*U2Q@W{rfbc~ zo5Uem?Gy{b6dnRL2Def)Ouq^+un>}sbkHJ0*=a0eFWphYb=txmm`f}h5=T31VY`h5 zIAv!c5KBAo5M5=(Nm>X?AjDKrqJW`9nwT(9B1NJ*NVQJb^e$vUiVb+e1H{KK5(m=OgYNm#>cY!8O>bKA%qGXf&B1f0zTbUp ztB~hVa^vWndH-Z^dvT2ek>^jwO@V>4!&`z2hYL7k%KxKjrmbiPTIL)~ymq7b;;y7| zw&L4*UlxT@2>>MiwkDa!Npt}*?G%*eCWOC z?n^7NduEN+iG5>DBL@;A&zK)K zy!~bWnZLK?86y+<&$sT8iOY@uEPTTv#?hsm7q1HKb1NNJ3U)Sp(p&c=BAxXjSoO2r V#OII6-|b2L_ Date: Wed, 17 Apr 2024 06:49:36 +0200 Subject: [PATCH 121/129] Replacing frame_0 & meta --- .../internal/L1_NoSd_128x49/frame_0.png | Bin 1411 -> 4374 bytes .../dolphin/internal/L1_NoSd_128x49/meta.txt | 17 ++++------------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/assets/dolphin/internal/L1_NoSd_128x49/frame_0.png b/assets/dolphin/internal/L1_NoSd_128x49/frame_0.png index 9a48d15f7a61949a492799c5098957738b9fe837..c804c6a152883af706448ceccc36aa9678a97525 100644 GIT binary patch literal 4374 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 CGO#uP delta 782 zcmbQH)XY6WB9Midfq~&c+a3)d#aJBV?!>U}oXkrghb7(7*O7r?V?XzwL{=c5Ex;$l z_5c6>KxXKn%XL7Cv%n*=n1O-s5C}7hYIrp;Ffh-bXywnu%Qdmf#@t-bP&e7Yz(B#= zQZF?n(J09{EiqX~!N|bKP~Q-U%ykXTt&EJV3@sI)K*^4mOJU-7CsrV(pfK5zal&K{ zCe_JLm?9?sVd9uv!mFU7q)?Gt;OlGUnO9trn3tUD>0+w{6w%AfOtErtGj%jDvoM}q z!z(d)8?zAy$T$#~9Kx!nn_^&UWSN>~q??p#Y_4lyWMZgmX_0EKYnhZ{Y-nbZY;I&~ zqNGq<5|o-|l``3oS+U;4%s9=|*fLeu$kY<3*wVyIH_61pK-bVDF)bxE$=t#yIZ;VL zA8Lz@eo;!Al}l=Ia#3bMNoIZ?SR5i6^+%_13LRG9aQ$iidn&mUGosD5@_BJ_%>vO&8ZpX}G2#;jmW3$l3icRKq z%*yLCHin#%bSV3?qUeGqL%;lFcQ0?L_8Eo?$|gk~I4ipLhnzzcLnYIMX|`-3zq$P5 p?;N{xiE*w! Date: Thu, 18 Apr 2024 00:17:40 +0900 Subject: [PATCH 122/129] [FL-2969] FuriHal: add ADC API (#3583) * Examples: remove unused context * FuriHal: add simple ADC API * Examples: add ADC example app * FuriHal: add extended configuration options for ADC API * FuriHal: add ADC clock configuration, fix calibration routine for single ended mode, new optimized parameters, documentation. * FuriHal: add FuriHalAdcChannelTEMPSENSOR sampling time note * FuriHal: update FuriHalAdcChannelVBAT description. * FuriHal: use insomnia while ADC is acquired. * Examples: cleanup example_adc a little bit --- .../examples/example_adc/application.fam | 9 + .../examples/example_adc/example_adc.c | 176 +++++++++++ .../example_custom_font/example_custom_font.c | 2 +- .../examples/example_images/example_images.c | 2 +- targets/f18/api_symbols.csv | 13 +- targets/f18/furi_hal/furi_hal.c | 1 + targets/f18/furi_hal/furi_hal_resources.c | 168 +++++++++-- targets/f18/furi_hal/furi_hal_resources.h | 5 +- targets/f7/api_symbols.csv | 13 +- targets/f7/furi_hal/furi_hal.c | 1 + targets/f7/furi_hal/furi_hal_adc.c | 281 ++++++++++++++++++ targets/f7/furi_hal/furi_hal_clock.c | 1 + targets/f7/furi_hal/furi_hal_resources.c | 90 +++++- targets/f7/furi_hal/furi_hal_resources.h | 14 +- targets/furi_hal_include/furi_hal.h | 1 + targets/furi_hal_include/furi_hal_adc.h | 229 ++++++++++++++ 16 files changed, 949 insertions(+), 57 deletions(-) create mode 100644 applications/examples/example_adc/application.fam create mode 100644 applications/examples/example_adc/example_adc.c create mode 100644 targets/f7/furi_hal/furi_hal_adc.c create mode 100644 targets/furi_hal_include/furi_hal_adc.h diff --git a/applications/examples/example_adc/application.fam b/applications/examples/example_adc/application.fam new file mode 100644 index 000000000..68aa2ac4d --- /dev/null +++ b/applications/examples/example_adc/application.fam @@ -0,0 +1,9 @@ +App( + appid="example_adc", + name="Example: ADC", + apptype=FlipperAppType.EXTERNAL, + entry_point="example_adc_main", + requires=["gui"], + stack_size=1 * 1024, + fap_category="Examples", +) diff --git a/applications/examples/example_adc/example_adc.c b/applications/examples/example_adc/example_adc.c new file mode 100644 index 000000000..53f8d5c12 --- /dev/null +++ b/applications/examples/example_adc/example_adc.c @@ -0,0 +1,176 @@ +/** + * @file example_adc.c + * @brief ADC example. + */ +#include +#include + +#include +#include +#include + +const uint8_t font[] = + "`\2\3\2\3\4\1\2\4\5\11\0\376\6\376\7\377\1M\2\263\3\370 \6\315\364\371\6!\12\315" + "\364\201\260\35\312Q\0\42\11\315tJI\316\13\0#\14\315\264\223dP*\203R'\1$\15\315\264" + "\262A\311\266D\251l\71\0%\15\315\264\7%\61)J\42\345 \0&\14\315\264\263$\13\223\266$" + "\7\1'\10\315\364\201\60\347\10(\10\315\364\32[\313\0)\11\315\64\322b[\35\2*\12\315\264\263" + "(\222j\71\15+\11\315\364I\331\226\23\1,\10\315\364\271\205Y\10-\10\315\364\31t\26\0.\10" + "\315\364\71\346(\0/\14\315\364\221\60\13\263\60\13C\0\60\13\315\264\245Jb)E:\12\61\12\315" + "\364\201Ll\333A\0\62\12\315\264\245bV\33r\20\63\13\315\264\245Z\232D\221\216\2\64\14\315\364" + "\201LJ\242!\313v\20\65\14\315t\207$\134\223(\322Q\0\66\13\315\264\245p\252D\221\216\2\67" + "\12\315t\207\60+\326a\0\70\13\315\264\245\222T\211\42\35\5\71\13\315\264\245J\24\215\221\216\2:" + "\11\315\364i\71!G\1;\12\315\364I\71!\314B\0<\11\315\364\341\254Z\7\1=\12\315\364)" + "C<\344$\0>\11\315\364\301\264V\207\1\77\12\315\264\245Z\35\312a\0@\14\315\264\245J\242$" + "J\272\203\0A\15\315\264\245J\224\14I\224D\71\10B\13\315t\247\312T\211\222\35\5C\12\315\264" + "\245JX\212t\24D\15\315t\247J\224DI\224\354(\0E\14\315t\207$\234\302p\310A\0F" + "\12\315t\207$\234\302:\1G\14\315\264\245J\230(Q\244\243\0H\17\315t\243$J\206$J\242" + "$\312A\0I\11\315\264\267\260m\7\1J\12\315\364\221\260%\212t\24K\14\315t\243\244\244iI" + "T\7\1L\11\315t\303\216C\16\2M\17\315t\243dH\206$J\242$\312A\0N\16\315t\243" + "D\251(Q\22%Q\16\2O\15\315\264\245J\224DI\24\351(\0P\12\315t\247J\224LaN" + "Q\15\315\264\245J\224DI\42\251\61\0R\14\315t\247J\224L\225(\7\1S\13\315\264\245\222\232" + "D\221\216\2T\10\315\264\267\260;\12U\16\315t\243$J\242$J\242HG\1V\15\315t\243$" + "J\242$Jj\71\14W\17\315t\243$J\242dH\206$\312A\0X\15\315t\243$\212\64\251\22" + "\345 \0Y\13\315t\243$Jja\35\6Z\12\315t\207\60k\34r\20[\10\315\264\264\260G\31" + "\134\12\315\264\303\64L\303\64\14]\10\315t\304\276\351\0^\11\315\364\201,\311\271\1_\7\315\364y" + "\35\4`\10\315t\322\234'\0a\14\315\364IK\224$R\222\203\0b\13\315t\303p\252D\311\216" + "\2c\12\315\364IR%\335A\0d\14\315\364\221\60Z\242$\212v\20e\12\315\364I\322\220\244;" + "\10f\12\315\364\221,\333\302:\12g\14\315\364IK\224D\321\30I\0h\14\315t\303p\252DI" + "\224\203\0i\12\315\364\201\34\21k;\10j\12\315\364\201\34\21\273e\0k\13\315t\303J\244%Q" + "\35\4l\10\315\264\305n;\10m\14\315\364)CRQ\22\245\216\1n\13\315\364)%\245\224D\71" + "\10o\12\315\364IR%\212t\24p\13\315\364)S%J\246\60\4q\13\315\364IK\224D\321X" + "\1r\11\315\364)%\245\230\23s\12\315\364I\313\232\354(\0t\13\315\364\201\60\333\302\64\7\1u" + "\15\315\364)Q\22%\211\224\344 \0v\13\315\364)Q\22%\265\34\6w\13\315\364)\25%Q\272" + "\203\0x\12\315\364)Q\244Iu\20y\15\315\364)Q\22%Q\64F\22\0z\12\315\364)CV" + "\33r\20{\12\315\364\212\265\64\254&\0|\7\315\264\302~\7}\12\315t\322\260\232\205\265\14~\11" + "\315\364II;\13\0\177\6\315\364\371\6\0\0\0\4\377\377\0"; + +#define FONT_HEIGHT (8u) + +typedef float (*ValueConverter)(FuriHalAdcHandle* handle, uint16_t value); + +typedef struct { + const GpioPinRecord* pin; + float value; + ValueConverter converter; + const char* suffix; +} DataItem; + +typedef struct { + size_t count; + DataItem* items; +} Data; + +const GpioPinRecord item_vref = {.name = "VREF", .channel = FuriHalAdcChannelVREFINT}; +const GpioPinRecord item_temp = {.name = "TEMP", .channel = FuriHalAdcChannelTEMPSENSOR}; +const GpioPinRecord item_vbat = {.name = "VBAT", .channel = FuriHalAdcChannelVBAT}; + +static void app_draw_callback(Canvas* canvas, void* ctx) { + furi_assert(ctx); + Data* data = ctx; + + canvas_set_custom_u8g2_font(canvas, font); + char buffer[64]; + int32_t x = 0, y = FONT_HEIGHT; + for(size_t i = 0; i < data->count; i++) { + if(i == canvas_height(canvas) / FONT_HEIGHT) { + x = 64; + y = FONT_HEIGHT; + } + + snprintf( + buffer, + sizeof(buffer), + "%4s: %4.0f%s\n", + data->items[i].pin->name, + (double)data->items[i].value, + data->items[i].suffix); + canvas_draw_str(canvas, x, y, buffer); + y += FONT_HEIGHT; + } +} + +static void app_input_callback(InputEvent* input_event, void* ctx) { + furi_assert(ctx); + FuriMessageQueue* event_queue = ctx; + furi_message_queue_put(event_queue, input_event, FuriWaitForever); +} + +int32_t example_adc_main(void* p) { + UNUSED(p); + + // Data + Data data = {}; + for(size_t i = 0; i < gpio_pins_count; i++) { + if(gpio_pins[i].channel != FuriHalAdcChannelNone) { + data.count++; + } + } + data.count += 3; // Special channels + data.items = malloc(data.count * sizeof(DataItem)); + size_t item_pos = 0; + for(size_t i = 0; i < gpio_pins_count; i++) { + if(gpio_pins[i].channel != FuriHalAdcChannelNone) { + furi_hal_gpio_init(gpio_pins[i].pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + data.items[item_pos].pin = &gpio_pins[i]; + data.items[item_pos].converter = furi_hal_adc_convert_to_voltage; + data.items[item_pos].suffix = "mV"; + item_pos++; + } + } + data.items[item_pos].pin = &item_vref; + data.items[item_pos].converter = furi_hal_adc_convert_vref; + data.items[item_pos].suffix = "mV"; + item_pos++; + data.items[item_pos].pin = &item_temp; + data.items[item_pos].converter = furi_hal_adc_convert_temp; + data.items[item_pos].suffix = "C"; + item_pos++; + data.items[item_pos].pin = &item_vbat; + data.items[item_pos].converter = furi_hal_adc_convert_vbat; + data.items[item_pos].suffix = "mV"; + item_pos++; + furi_assert(item_pos == data.count); + + // Alloc message queue + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + + // Configure view port + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, app_draw_callback, &data); + view_port_input_callback_set(view_port, app_input_callback, event_queue); + + // Register view port in GUI + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + // Initialize ADC + FuriHalAdcHandle* adc_handle = furi_hal_adc_acquire(); + furi_hal_adc_configure(adc_handle); + + // Process events + InputEvent event; + bool running = true; + while(running) { + if(furi_message_queue_get(event_queue, &event, 100) == FuriStatusOk) { + if(event.type == InputTypePress && event.key == InputKeyBack) { + running = false; + } + } else { + for(size_t i = 0; i < data.count; i++) { + data.items[i].value = data.items[i].converter( + adc_handle, furi_hal_adc_read(adc_handle, data.items[i].pin->channel)); + } + view_port_update(view_port); + } + } + + furi_hal_adc_release(adc_handle); + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + view_port_free(view_port); + furi_message_queue_free(event_queue); + furi_record_close(RECORD_GUI); + free(data.items); + + return 0; +} diff --git a/applications/examples/example_custom_font/example_custom_font.c b/applications/examples/example_custom_font/example_custom_font.c index 405db46e3..a175838e3 100644 --- a/applications/examples/example_custom_font/example_custom_font.c +++ b/applications/examples/example_custom_font/example_custom_font.c @@ -94,7 +94,7 @@ int32_t example_custom_font_main(void* p) { // Configure view port ViewPort* view_port = view_port_alloc(); - view_port_draw_callback_set(view_port, app_draw_callback, view_port); + view_port_draw_callback_set(view_port, app_draw_callback, NULL); view_port_input_callback_set(view_port, app_input_callback, event_queue); // Register view port in GUI diff --git a/applications/examples/example_images/example_images.c b/applications/examples/example_images/example_images.c index ba9a354f6..60269a81f 100644 --- a/applications/examples/example_images/example_images.c +++ b/applications/examples/example_images/example_images.c @@ -39,7 +39,7 @@ int32_t example_images_main(void* p) { // Configure view port ViewPort* view_port = view_port_alloc(); - view_port_draw_callback_set(view_port, app_draw_callback, view_port); + view_port_draw_callback_set(view_port, app_draw_callback, NULL); view_port_input_callback_set(view_port, app_input_callback, event_queue); // Register view port in GUI diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 703bcc50c..43500aa1c 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.8,, +Version,+,61.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -199,6 +199,7 @@ Header,+,targets/f7/platform_specific/cxx_virtual_stub.h,, Header,+,targets/f7/platform_specific/intrinsic_export.h,, Header,+,targets/f7/platform_specific/math_wrapper.h,, Header,+,targets/furi_hal_include/furi_hal.h,, +Header,+,targets/furi_hal_include/furi_hal_adc.h,, Header,+,targets/furi_hal_include/furi_hal_bt.h,, Header,+,targets/furi_hal_include/furi_hal_cortex.h,, Header,+,targets/furi_hal_include/furi_hal_crypto.h,, @@ -1086,6 +1087,16 @@ Function,+,furi_event_flag_get,uint32_t,FuriEventFlag* Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t" Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t" Function,+,furi_get_tick,uint32_t, +Function,+,furi_hal_adc_acquire,FuriHalAdcHandle*, +Function,+,furi_hal_adc_configure,void,FuriHalAdcHandle* +Function,+,furi_hal_adc_configure_ex,void,"FuriHalAdcHandle*, FuriHalAdcScale, FuriHalAdcClock, FuriHalAdcOversample, FuriHalAdcSamplingTime" +Function,+,furi_hal_adc_convert_temp,float,"FuriHalAdcHandle*, uint16_t" +Function,+,furi_hal_adc_convert_to_voltage,float,"FuriHalAdcHandle*, uint16_t" +Function,+,furi_hal_adc_convert_vbat,float,"FuriHalAdcHandle*, uint16_t" +Function,+,furi_hal_adc_convert_vref,float,"FuriHalAdcHandle*, uint16_t" +Function,+,furi_hal_adc_init,void, +Function,+,furi_hal_adc_read,uint16_t,"FuriHalAdcHandle*, FuriHalAdcChannel" +Function,+,furi_hal_adc_release,void,FuriHalAdcHandle* Function,+,furi_hal_bt_change_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*" Function,+,furi_hal_bt_check_profile_type,_Bool,"FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*" Function,+,furi_hal_bt_clear_white_list,_Bool, diff --git a/targets/f18/furi_hal/furi_hal.c b/targets/f18/furi_hal/furi_hal.c index 6e0a254c1..6cfc939b8 100644 --- a/targets/f18/furi_hal/furi_hal.c +++ b/targets/f18/furi_hal/furi_hal.c @@ -31,6 +31,7 @@ void furi_hal_deinit_early(void) { void furi_hal_init(void) { furi_hal_mpu_init(); + furi_hal_adc_init(); furi_hal_clock_init(); furi_hal_random_init(); furi_hal_serial_control_init(); diff --git a/targets/f18/furi_hal/furi_hal_resources.c b/targets/f18/furi_hal/furi_hal_resources.c index 9935d4124..45ca3e6c4 100644 --- a/targets/f18/furi_hal/furi_hal_resources.c +++ b/targets/f18/furi_hal/furi_hal_resources.c @@ -68,49 +68,161 @@ const GpioPin gpio_usb_dp = {.port = GPIOA, .pin = LL_GPIO_PIN_12}; const GpioPinRecord gpio_pins[] = { // 5V: 1 - {.pin = &gpio_ext_pa7, .name = "PA7", .number = 2, .debug = false}, - {.pin = &gpio_ext_pa6, .name = "PA6", .number = 3, .debug = false}, - {.pin = &gpio_ext_pa4, .name = "PA4", .number = 4, .debug = false}, - {.pin = &gpio_ext_pb3, .name = "PB3", .number = 5, .debug = false}, - {.pin = &gpio_ext_pb2, .name = "PB2", .number = 6, .debug = false}, - {.pin = &gpio_ext_pc3, .name = "PC3", .number = 7, .debug = false}, + {.pin = &gpio_ext_pa7, + .name = "PA7", + .channel = FuriHalAdcChannel12, + .number = 2, + .debug = false}, + {.pin = &gpio_ext_pa6, + .name = "PA6", + .channel = FuriHalAdcChannel11, + .number = 3, + .debug = false}, + {.pin = &gpio_ext_pa4, + .name = "PA4", + .channel = FuriHalAdcChannel9, + .number = 4, + .debug = false}, + {.pin = &gpio_ext_pb3, + .name = "PB3", + .channel = FuriHalAdcChannelNone, + .number = 5, + .debug = false}, + {.pin = &gpio_ext_pb2, + .name = "PB2", + .channel = FuriHalAdcChannelNone, + .number = 6, + .debug = false}, + {.pin = &gpio_ext_pc3, + .name = "PC3", + .channel = FuriHalAdcChannel4, + .number = 7, + .debug = false}, // GND: 8 // Space // 3v3: 9 - {.pin = &gpio_swclk, .name = "PA14", .number = 10, .debug = true}, + {.pin = &gpio_swclk, + .name = "PA14", + .channel = FuriHalAdcChannelNone, + .number = 10, + .debug = true}, // GND: 11 - {.pin = &gpio_swdio, .name = "PA13", .number = 12, .debug = true}, - {.pin = &gpio_usart_tx, .name = "PB6", .number = 13, .debug = true}, - {.pin = &gpio_usart_rx, .name = "PB7", .number = 14, .debug = true}, - {.pin = &gpio_ext_pc1, .name = "PC1", .number = 15, .debug = false}, - {.pin = &gpio_ext_pc0, .name = "PC0", .number = 16, .debug = false}, - {.pin = &gpio_ibutton, .name = "PB14", .number = 17, .debug = true}, + {.pin = &gpio_swdio, + .name = "PA13", + .channel = FuriHalAdcChannelNone, + .number = 12, + .debug = true}, + {.pin = &gpio_usart_tx, + .name = "PB6", + .channel = FuriHalAdcChannelNone, + .number = 13, + .debug = true}, + {.pin = &gpio_usart_rx, + .name = "PB7", + .channel = FuriHalAdcChannelNone, + .number = 14, + .debug = true}, + {.pin = &gpio_ext_pc1, + .name = "PC1", + .channel = FuriHalAdcChannel2, + .number = 15, + .debug = false}, + {.pin = &gpio_ext_pc0, + .name = "PC0", + .channel = FuriHalAdcChannel1, + .number = 16, + .debug = false}, + {.pin = &gpio_ibutton, + .name = "PB14", + .channel = FuriHalAdcChannelNone, + .number = 17, + .debug = true}, // GND: 18 // 2nd column // 5V: 19 - {.pin = &gpio_ext_pc5, .name = "PC5", .number = 20, .debug = false}, - {.pin = &gpio_ext_pc4, .name = "PC4", .number = 21, .debug = false}, - {.pin = &gpio_ext_pa5, .name = "PA5", .number = 22, .debug = false}, - {.pin = &gpio_ext_pb9, .name = "PB9", .number = 23, .debug = false}, - {.pin = &gpio_ext_pa0, .name = "PA0", .number = 24, .debug = false}, - {.pin = &gpio_ext_pa1, .name = "PA1", .number = 25, .debug = false}, + {.pin = &gpio_ext_pc5, + .name = "PC5", + .channel = FuriHalAdcChannel14, + .number = 20, + .debug = false}, + {.pin = &gpio_ext_pc4, + .name = "PC4", + .channel = FuriHalAdcChannel13, + .number = 21, + .debug = false}, + {.pin = &gpio_ext_pa5, + .name = "PA5", + .channel = FuriHalAdcChannel10, + .number = 22, + .debug = false}, + {.pin = &gpio_ext_pb9, + .name = "PB9", + .channel = FuriHalAdcChannelNone, + .number = 23, + .debug = false}, + {.pin = &gpio_ext_pa0, + .name = "PA0", + .channel = FuriHalAdcChannel5, + .number = 24, + .debug = false}, + {.pin = &gpio_ext_pa1, + .name = "PA1", + .channel = FuriHalAdcChannel6, + .number = 25, + .debug = false}, // KEY: 26 // Space // 3v3: 27 - {.pin = &gpio_ext_pa15, .name = "PA15", .number = 28, .debug = false}, + {.pin = &gpio_ext_pa15, + .name = "PA15", + .channel = FuriHalAdcChannelNone, + .number = 28, + .debug = false}, // GND: 29 - {.pin = &gpio_ext_pe4, .name = "PE4", .number = 30, .debug = false}, - {.pin = &gpio_ext_pa2, .name = "PA2", .number = 31, .debug = false}, - {.pin = &gpio_ext_pb4, .name = "PB4", .number = 32, .debug = false}, - {.pin = &gpio_ext_pb5, .name = "PB5", .number = 33, .debug = false}, - {.pin = &gpio_ext_pd0, .name = "PD0", .number = 34, .debug = false}, - {.pin = &gpio_ext_pb13, .name = "PB13", .number = 35, .debug = false}, + {.pin = &gpio_ext_pe4, + .name = "PE4", + .channel = FuriHalAdcChannelNone, + .number = 30, + .debug = false}, + {.pin = &gpio_ext_pa2, + .name = "PA2", + .channel = FuriHalAdcChannel7, + .number = 31, + .debug = false}, + {.pin = &gpio_ext_pb4, + .name = "PB4", + .channel = FuriHalAdcChannelNone, + .number = 32, + .debug = false}, + {.pin = &gpio_ext_pb5, + .name = "PB5", + .channel = FuriHalAdcChannelNone, + .number = 33, + .debug = false}, + {.pin = &gpio_ext_pd0, + .name = "PD0", + .channel = FuriHalAdcChannelNone, + .number = 34, + .debug = false}, + {.pin = &gpio_ext_pb13, + .name = "PB13", + .channel = FuriHalAdcChannelNone, + .number = 35, + .debug = false}, // GND: 36 /* Dangerous pins, may damage hardware */ - {.pin = &gpio_usart_rx, .name = "PB7", .number = 0, .debug = true}, - {.pin = &gpio_speaker, .name = "PB8", .number = 0, .debug = true}, + {.pin = &gpio_usart_rx, + .name = "PB7", + .channel = FuriHalAdcChannelNone, + .number = 0, + .debug = true}, + {.pin = &gpio_speaker, + .name = "PB8", + .channel = FuriHalAdcChannelNone, + .number = 0, + .debug = true}, }; const size_t gpio_pins_count = COUNT_OF(gpio_pins); diff --git a/targets/f18/furi_hal/furi_hal_resources.h b/targets/f18/furi_hal/furi_hal_resources.h index 3d45ad885..8f6173eb9 100644 --- a/targets/f18/furi_hal/furi_hal_resources.h +++ b/targets/f18/furi_hal/furi_hal_resources.h @@ -1,9 +1,7 @@ #pragma once #include - -#include -#include +#include #ifdef __cplusplus extern "C" { @@ -41,6 +39,7 @@ typedef struct { typedef struct { const GpioPin* pin; const char* name; + const FuriHalAdcChannel channel; const uint8_t number; const bool debug; } GpioPinRecord; diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index c0edf9ffa..dbaadc4a1 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.8,, +Version,+,61.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -268,6 +268,7 @@ Header,+,targets/f7/platform_specific/cxx_virtual_stub.h,, Header,+,targets/f7/platform_specific/intrinsic_export.h,, Header,+,targets/f7/platform_specific/math_wrapper.h,, Header,+,targets/furi_hal_include/furi_hal.h,, +Header,+,targets/furi_hal_include/furi_hal_adc.h,, Header,+,targets/furi_hal_include/furi_hal_bt.h,, Header,+,targets/furi_hal_include/furi_hal_cortex.h,, Header,+,targets/furi_hal_include/furi_hal_crypto.h,, @@ -1183,6 +1184,16 @@ Function,+,furi_event_flag_get,uint32_t,FuriEventFlag* Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t" Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t" Function,+,furi_get_tick,uint32_t, +Function,+,furi_hal_adc_acquire,FuriHalAdcHandle*, +Function,+,furi_hal_adc_configure,void,FuriHalAdcHandle* +Function,+,furi_hal_adc_configure_ex,void,"FuriHalAdcHandle*, FuriHalAdcScale, FuriHalAdcClock, FuriHalAdcOversample, FuriHalAdcSamplingTime" +Function,+,furi_hal_adc_convert_temp,float,"FuriHalAdcHandle*, uint16_t" +Function,+,furi_hal_adc_convert_to_voltage,float,"FuriHalAdcHandle*, uint16_t" +Function,+,furi_hal_adc_convert_vbat,float,"FuriHalAdcHandle*, uint16_t" +Function,+,furi_hal_adc_convert_vref,float,"FuriHalAdcHandle*, uint16_t" +Function,+,furi_hal_adc_init,void, +Function,+,furi_hal_adc_read,uint16_t,"FuriHalAdcHandle*, FuriHalAdcChannel" +Function,+,furi_hal_adc_release,void,FuriHalAdcHandle* Function,+,furi_hal_bt_change_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*" Function,+,furi_hal_bt_check_profile_type,_Bool,"FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*" Function,+,furi_hal_bt_clear_white_list,_Bool, diff --git a/targets/f7/furi_hal/furi_hal.c b/targets/f7/furi_hal/furi_hal.c index 1fe8b241b..7f3959bc3 100644 --- a/targets/f7/furi_hal/furi_hal.c +++ b/targets/f7/furi_hal/furi_hal.c @@ -31,6 +31,7 @@ void furi_hal_deinit_early(void) { void furi_hal_init(void) { furi_hal_mpu_init(); + furi_hal_adc_init(); furi_hal_clock_init(); furi_hal_random_init(); furi_hal_serial_control_init(); diff --git a/targets/f7/furi_hal/furi_hal_adc.c b/targets/f7/furi_hal/furi_hal_adc.c new file mode 100644 index 000000000..7a15f2e54 --- /dev/null +++ b/targets/f7/furi_hal/furi_hal_adc.c @@ -0,0 +1,281 @@ +#include +#include +#include +#include + +#include + +#include +#include + +struct FuriHalAdcHandle { + ADC_TypeDef* adc; + FuriMutex* mutex; + uint32_t full_scale; +}; + +static const uint32_t furi_hal_adc_clock[] = { + [FuriHalAdcClockSync16] = LL_ADC_CLOCK_SYNC_PCLK_DIV4, + [FuriHalAdcClockSync32] = LL_ADC_CLOCK_SYNC_PCLK_DIV2, + [FuriHalAdcClockSync64] = LL_ADC_CLOCK_SYNC_PCLK_DIV1, +}; + +static const uint8_t furi_hal_adc_clock_div[] = { + [FuriHalAdcClockSync16] = 4, + [FuriHalAdcClockSync32] = 2, + [FuriHalAdcClockSync64] = 1, +}; + +static const uint32_t furi_hal_adc_oversample_ratio[] = { + [FuriHalAdcOversample2] = LL_ADC_OVS_RATIO_2, + [FuriHalAdcOversample4] = LL_ADC_OVS_RATIO_4, + [FuriHalAdcOversample8] = LL_ADC_OVS_RATIO_8, + [FuriHalAdcOversample16] = LL_ADC_OVS_RATIO_16, + [FuriHalAdcOversample32] = LL_ADC_OVS_RATIO_32, + [FuriHalAdcOversample64] = LL_ADC_OVS_RATIO_64, + [FuriHalAdcOversample128] = LL_ADC_OVS_RATIO_128, + [FuriHalAdcOversample256] = LL_ADC_OVS_RATIO_256, +}; + +static const uint32_t furi_hal_adc_oversample_shift[] = { + [FuriHalAdcOversample2] = LL_ADC_OVS_SHIFT_RIGHT_1, + [FuriHalAdcOversample4] = LL_ADC_OVS_SHIFT_RIGHT_2, + [FuriHalAdcOversample8] = LL_ADC_OVS_SHIFT_RIGHT_3, + [FuriHalAdcOversample16] = LL_ADC_OVS_SHIFT_RIGHT_4, + [FuriHalAdcOversample32] = LL_ADC_OVS_SHIFT_RIGHT_5, + [FuriHalAdcOversample64] = LL_ADC_OVS_SHIFT_RIGHT_6, + [FuriHalAdcOversample128] = LL_ADC_OVS_SHIFT_RIGHT_7, + [FuriHalAdcOversample256] = LL_ADC_OVS_SHIFT_RIGHT_8, +}; + +static const uint32_t furi_hal_adc_sampling_time[] = { + [FuriHalAdcSamplingtime2_5] = LL_ADC_SAMPLINGTIME_2CYCLES_5, + [FuriHalAdcSamplingtime6_5] = LL_ADC_SAMPLINGTIME_6CYCLES_5, + [FuriHalAdcSamplingtime12_5] = LL_ADC_SAMPLINGTIME_12CYCLES_5, + [FuriHalAdcSamplingtime24_5] = LL_ADC_SAMPLINGTIME_24CYCLES_5, + [FuriHalAdcSamplingtime47_5] = LL_ADC_SAMPLINGTIME_47CYCLES_5, + [FuriHalAdcSamplingtime92_5] = LL_ADC_SAMPLINGTIME_92CYCLES_5, + [FuriHalAdcSamplingtime247_5] = LL_ADC_SAMPLINGTIME_247CYCLES_5, + [FuriHalAdcSamplingtime640_5] = LL_ADC_SAMPLINGTIME_640CYCLES_5, +}; + +static const uint32_t furi_hal_adc_channel_map[] = { + [FuriHalAdcChannel0] = LL_ADC_CHANNEL_0, + [FuriHalAdcChannel1] = LL_ADC_CHANNEL_1, + [FuriHalAdcChannel2] = LL_ADC_CHANNEL_2, + [FuriHalAdcChannel3] = LL_ADC_CHANNEL_3, + [FuriHalAdcChannel4] = LL_ADC_CHANNEL_4, + [FuriHalAdcChannel5] = LL_ADC_CHANNEL_5, + [FuriHalAdcChannel6] = LL_ADC_CHANNEL_6, + [FuriHalAdcChannel7] = LL_ADC_CHANNEL_7, + [FuriHalAdcChannel8] = LL_ADC_CHANNEL_8, + [FuriHalAdcChannel9] = LL_ADC_CHANNEL_9, + [FuriHalAdcChannel10] = LL_ADC_CHANNEL_10, + [FuriHalAdcChannel11] = LL_ADC_CHANNEL_11, + [FuriHalAdcChannel12] = LL_ADC_CHANNEL_12, + [FuriHalAdcChannel13] = LL_ADC_CHANNEL_13, + [FuriHalAdcChannel14] = LL_ADC_CHANNEL_14, + [FuriHalAdcChannel15] = LL_ADC_CHANNEL_15, + [FuriHalAdcChannel16] = LL_ADC_CHANNEL_16, + [FuriHalAdcChannel17] = LL_ADC_CHANNEL_17, + [FuriHalAdcChannel18] = LL_ADC_CHANNEL_18, + [FuriHalAdcChannelVREFINT] = LL_ADC_CHANNEL_VREFINT, + [FuriHalAdcChannelTEMPSENSOR] = LL_ADC_CHANNEL_TEMPSENSOR, + [FuriHalAdcChannelVBAT] = LL_ADC_CHANNEL_VBAT, +}; + +static FuriHalAdcHandle* furi_hal_adc_handle = NULL; + +void furi_hal_adc_init(void) { + furi_hal_adc_handle = malloc(sizeof(FuriHalAdcHandle)); + furi_hal_adc_handle->adc = ADC1; + furi_hal_adc_handle->mutex = furi_mutex_alloc(FuriMutexTypeNormal); +} + +FuriHalAdcHandle* furi_hal_adc_acquire(void) { + furi_check(furi_mutex_acquire(furi_hal_adc_handle->mutex, FuriWaitForever) == FuriStatusOk); + + furi_hal_power_insomnia_enter(); + + furi_hal_bus_enable(FuriHalBusADC); + + return furi_hal_adc_handle; +} + +void furi_hal_adc_release(FuriHalAdcHandle* handle) { + furi_check(handle); + + if(furi_hal_bus_is_enabled(FuriHalBusADC)) furi_hal_bus_disable(FuriHalBusADC); + + LL_VREFBUF_Disable(); + LL_VREFBUF_EnableHIZ(); + + furi_hal_power_insomnia_exit(); + + furi_check(furi_mutex_release(furi_hal_adc_handle->mutex) == FuriStatusOk); +} + +void furi_hal_adc_configure(FuriHalAdcHandle* handle) { + furi_hal_adc_configure_ex( + handle, + FuriHalAdcScale2048, + FuriHalAdcClockSync64, + FuriHalAdcOversample64, + FuriHalAdcSamplingtime247_5); +} + +void furi_hal_adc_configure_ex( + FuriHalAdcHandle* handle, + FuriHalAdcScale scale, + FuriHalAdcClock clock, + FuriHalAdcOversample oversample, + FuriHalAdcSamplingTime sampling_time) { + furi_check(handle); + furi_check(scale == FuriHalAdcScale2048 || scale == FuriHalAdcScale2500); + furi_check(clock <= FuriHalAdcClockSync64); + furi_check(oversample <= FuriHalAdcOversampleNone); + furi_check(sampling_time <= FuriHalAdcSamplingtime640_5); + + FuriHalCortexTimer timer; + + if(furi_hal_bus_is_enabled(FuriHalBusADC)) furi_hal_bus_disable(FuriHalBusADC); + + uint32_t trim_value = 0; + switch(scale) { + case FuriHalAdcScale2048: + LL_VREFBUF_SetVoltageScaling(LL_VREFBUF_VOLTAGE_SCALE0); + trim_value = LL_VREFBUF_SC0_GetCalibration() & 0x3FU; + handle->full_scale = 2048; + break; + case FuriHalAdcScale2500: + LL_VREFBUF_SetVoltageScaling(LL_VREFBUF_VOLTAGE_SCALE1); + trim_value = LL_VREFBUF_SC1_GetCalibration() & 0x3FU; + handle->full_scale = 2500; + break; + default: + furi_crash(); + } + LL_VREFBUF_SetTrimming(trim_value); + LL_VREFBUF_Enable(); + LL_VREFBUF_DisableHIZ(); + + timer = furi_hal_cortex_timer_get(500000); // 500ms to stabilize VREF + while(!LL_VREFBUF_IsVREFReady()) { + furi_check(!furi_hal_cortex_timer_is_expired(timer), "VREF fail"); + }; + + furi_hal_bus_enable(FuriHalBusADC); + + // ADC Common config + LL_ADC_CommonInitTypeDef ADC_CommonInitStruct = {0}; + ADC_CommonInitStruct.CommonClock = furi_hal_adc_clock[clock]; + furi_check( + LL_ADC_CommonInit(__LL_ADC_COMMON_INSTANCE(handle->adc), &ADC_CommonInitStruct) == + SUCCESS); + LL_ADC_SetCommonPathInternalCh( + __LL_ADC_COMMON_INSTANCE(handle->adc), + LL_ADC_PATH_INTERNAL_VREFINT | LL_ADC_PATH_INTERNAL_TEMPSENSOR | + LL_ADC_PATH_INTERNAL_VBAT); + + // ADC config part 1 + LL_ADC_InitTypeDef ADC_InitStruct = {0}; + ADC_InitStruct.Resolution = LL_ADC_RESOLUTION_12B; //-V1048 + ADC_InitStruct.DataAlignment = LL_ADC_DATA_ALIGN_RIGHT; + ADC_InitStruct.LowPowerMode = LL_ADC_LP_MODE_NONE; + furi_check(LL_ADC_Init(handle->adc, &ADC_InitStruct) == SUCCESS); + + // ADC config part 2: groups parameters + LL_ADC_REG_InitTypeDef ADC_REG_InitStruct = {0}; + ADC_REG_InitStruct.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE; //-V1048 + ADC_REG_InitStruct.SequencerLength = LL_ADC_REG_SEQ_SCAN_DISABLE; + ADC_REG_InitStruct.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE; + ADC_REG_InitStruct.ContinuousMode = LL_ADC_REG_CONV_SINGLE; + ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_NONE; + ADC_REG_InitStruct.Overrun = LL_ADC_REG_OVR_DATA_OVERWRITTEN; + furi_check(LL_ADC_REG_Init(handle->adc, &ADC_REG_InitStruct) == SUCCESS); + + // ADC config part 3: sequencer and channels + if(oversample == FuriHalAdcOversampleNone) { + LL_ADC_SetOverSamplingScope(handle->adc, LL_ADC_OVS_DISABLE); + } else { + LL_ADC_SetOverSamplingScope(handle->adc, LL_ADC_OVS_GRP_REGULAR_CONTINUED); + LL_ADC_ConfigOverSamplingRatioShift( + handle->adc, + furi_hal_adc_oversample_ratio[oversample], + furi_hal_adc_oversample_shift[oversample]); + } + + for(FuriHalAdcChannel channel = FuriHalAdcChannel0; channel < FuriHalAdcChannelNone; + channel++) { + // 47.5 cycles on 64MHz is first meaningful value for internal sources sampling + LL_ADC_SetChannelSamplingTime( + handle->adc, + furi_hal_adc_channel_map[channel], + furi_hal_adc_sampling_time[sampling_time]); + LL_ADC_SetChannelSingleDiff( + handle->adc, furi_hal_adc_channel_map[channel], LL_ADC_SINGLE_ENDED); + } + + // Disable ADC deep power down (enabled by default after reset state) + LL_ADC_DisableDeepPowerDown(handle->adc); + + // Enable ADC internal voltage regulator + LL_ADC_EnableInternalRegulator(handle->adc); + // Delay for ADC internal voltage regulator stabilization. + timer = furi_hal_cortex_timer_get(LL_ADC_DELAY_INTERNAL_REGUL_STAB_US); + while(!furi_hal_cortex_timer_is_expired(timer)) + ; + + // Run ADC self calibration + LL_ADC_StartCalibration(handle->adc, LL_ADC_SINGLE_ENDED); + // Poll for ADC effectively calibrated + while(LL_ADC_IsCalibrationOnGoing(handle->adc) != 0) + ; + // Delay between ADC end of calibration and ADC enable + size_t end = + DWT->CYCCNT + (LL_ADC_DELAY_CALIB_ENABLE_ADC_CYCLES * furi_hal_adc_clock_div[clock]); + while(DWT->CYCCNT < end) + ; + + // Enable ADC + LL_ADC_ClearFlag_ADRDY(handle->adc); + LL_ADC_Enable(handle->adc); + while(LL_ADC_IsActiveFlag_ADRDY(handle->adc) == 0) + ; +} + +uint16_t furi_hal_adc_read(FuriHalAdcHandle* handle, FuriHalAdcChannel channel) { + furi_check(handle); + furi_check(channel <= FuriHalAdcChannelVBAT); + furi_check(LL_ADC_IsEnabled(handle->adc) == 1); + furi_check(LL_ADC_IsDisableOngoing(handle->adc) == 0); + furi_check(LL_ADC_REG_IsConversionOngoing(handle->adc) == 0); + + LL_ADC_REG_SetSequencerRanks( + handle->adc, LL_ADC_REG_RANK_1, furi_hal_adc_channel_map[channel]); + + LL_ADC_REG_StartConversion(handle->adc); + + while(LL_ADC_IsActiveFlag_EOC(handle->adc) == 0) + ; + uint16_t value = LL_ADC_REG_ReadConversionData12(handle->adc); + + return value; +} + +float furi_hal_adc_convert_to_voltage(FuriHalAdcHandle* handle, uint16_t value) { + return (float)__LL_ADC_CALC_DATA_TO_VOLTAGE(handle->full_scale, value, LL_ADC_RESOLUTION_12B); +} + +float furi_hal_adc_convert_vref(FuriHalAdcHandle* handle, uint16_t value) { + UNUSED(handle); + return (float)__LL_ADC_CALC_VREFANALOG_VOLTAGE(value, LL_ADC_RESOLUTION_12B); +} + +float furi_hal_adc_convert_temp(FuriHalAdcHandle* handle, uint16_t value) { + return (float)__LL_ADC_CALC_TEMPERATURE(handle->full_scale, value, LL_ADC_RESOLUTION_12B); +} + +float furi_hal_adc_convert_vbat(FuriHalAdcHandle* handle, uint16_t value) { + return furi_hal_adc_convert_to_voltage(handle, value) * 3; +} diff --git a/targets/f7/furi_hal/furi_hal_clock.c b/targets/f7/furi_hal/furi_hal_clock.c index ad21031fe..9184fa715 100644 --- a/targets/f7/furi_hal/furi_hal_clock.c +++ b/targets/f7/furi_hal/furi_hal_clock.c @@ -121,6 +121,7 @@ void furi_hal_clock_init(void) { LL_RCC_SetSMPSClockSource(LL_RCC_SMPS_CLKSOURCE_HSI); LL_RCC_SetSMPSPrescaler(LL_RCC_SMPS_DIV_1); LL_RCC_SetRFWKPClockSource(LL_RCC_RFWKP_CLKSOURCE_LSE); + LL_RCC_SetADCClockSource(LL_RCC_ADC_CLKSOURCE_SYSCLK); FURI_LOG_I(TAG, "Init OK"); } diff --git a/targets/f7/furi_hal/furi_hal_resources.c b/targets/f7/furi_hal/furi_hal_resources.c index b98d93769..486c24230 100644 --- a/targets/f7/furi_hal/furi_hal_resources.c +++ b/targets/f7/furi_hal/furi_hal_resources.c @@ -70,28 +70,88 @@ const GpioPin gpio_usb_dp = {.port = GPIOA, .pin = LL_GPIO_PIN_12}; const GpioPinRecord gpio_pins[] = { // 5V: 1 - {.pin = &gpio_ext_pa7, .name = "PA7", .number = 2, .debug = false}, - {.pin = &gpio_ext_pa6, .name = "PA6", .number = 3, .debug = false}, - {.pin = &gpio_ext_pa4, .name = "PA4", .number = 4, .debug = false}, - {.pin = &gpio_ext_pb3, .name = "PB3", .number = 5, .debug = false}, - {.pin = &gpio_ext_pb2, .name = "PB2", .number = 6, .debug = false}, - {.pin = &gpio_ext_pc3, .name = "PC3", .number = 7, .debug = false}, + {.pin = &gpio_ext_pa7, + .name = "PA7", + .channel = FuriHalAdcChannel12, + .number = 2, + .debug = false}, + {.pin = &gpio_ext_pa6, + .name = "PA6", + .channel = FuriHalAdcChannel11, + .number = 3, + .debug = false}, + {.pin = &gpio_ext_pa4, + .name = "PA4", + .channel = FuriHalAdcChannel9, + .number = 4, + .debug = false}, + {.pin = &gpio_ext_pb3, + .name = "PB3", + .channel = FuriHalAdcChannelNone, + .number = 5, + .debug = false}, + {.pin = &gpio_ext_pb2, + .name = "PB2", + .channel = FuriHalAdcChannelNone, + .number = 6, + .debug = false}, + {.pin = &gpio_ext_pc3, + .name = "PC3", + .channel = FuriHalAdcChannel4, + .number = 7, + .debug = false}, // GND: 8 // Space // 3v3: 9 - {.pin = &gpio_swclk, .name = "PA14", .number = 10, .debug = true}, + {.pin = &gpio_swclk, + .name = "PA14", + .channel = FuriHalAdcChannelNone, + .number = 10, + .debug = true}, // GND: 11 - {.pin = &gpio_swdio, .name = "PA13", .number = 12, .debug = true}, - {.pin = &gpio_usart_tx, .name = "PB6", .number = 13, .debug = true}, - {.pin = &gpio_usart_rx, .name = "PB7", .number = 14, .debug = true}, - {.pin = &gpio_ext_pc1, .name = "PC1", .number = 15, .debug = false}, - {.pin = &gpio_ext_pc0, .name = "PC0", .number = 16, .debug = false}, - {.pin = &gpio_ibutton, .name = "PB14", .number = 17, .debug = true}, + {.pin = &gpio_swdio, + .name = "PA13", + .channel = FuriHalAdcChannelNone, + .number = 12, + .debug = true}, + {.pin = &gpio_usart_tx, + .name = "PB6", + .channel = FuriHalAdcChannelNone, + .number = 13, + .debug = true}, + {.pin = &gpio_usart_rx, + .name = "PB7", + .channel = FuriHalAdcChannelNone, + .number = 14, + .debug = true}, + {.pin = &gpio_ext_pc1, + .name = "PC1", + .channel = FuriHalAdcChannel2, + .number = 15, + .debug = false}, + {.pin = &gpio_ext_pc0, + .name = "PC0", + .channel = FuriHalAdcChannel1, + .number = 16, + .debug = false}, + {.pin = &gpio_ibutton, + .name = "PB14", + .channel = FuriHalAdcChannelNone, + .number = 17, + .debug = true}, // GND: 18 /* Dangerous pins, may damage hardware */ - {.pin = &gpio_speaker, .name = "PB8", .debug = true}, - {.pin = &gpio_infrared_tx, .name = "PB9", .debug = true}, + {.pin = &gpio_speaker, + .name = "PB8", + .channel = FuriHalAdcChannelNone, + .number = 0, + .debug = true}, + {.pin = &gpio_infrared_tx, + .name = "PB9", + .channel = FuriHalAdcChannelNone, + .number = 0, + .debug = true}, }; const size_t gpio_pins_count = COUNT_OF(gpio_pins); diff --git a/targets/f7/furi_hal/furi_hal_resources.h b/targets/f7/furi_hal/furi_hal_resources.h index bb01b9bcd..99768654d 100644 --- a/targets/f7/furi_hal/furi_hal_resources.h +++ b/targets/f7/furi_hal/furi_hal_resources.h @@ -1,9 +1,7 @@ #pragma once #include - -#include -#include +#include #ifdef __cplusplus extern "C" { @@ -41,6 +39,7 @@ typedef struct { typedef struct { const GpioPin* pin; const char* name; + const FuriHalAdcChannel channel; const uint8_t number; const bool debug; } GpioPinRecord; @@ -220,10 +219,11 @@ void furi_hal_resources_deinit_early(void); void furi_hal_resources_init(void); -/** - * Get a corresponding external connector pin number for a gpio - * @param gpio GpioPin - * @return pin number or -1 if gpio is not on the external connector +/** Get a corresponding external connector pin number for a gpio + * + * @param gpio GpioPin + * + * @return pin number or -1 if gpio is not on the external connector */ int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio); diff --git a/targets/furi_hal_include/furi_hal.h b/targets/furi_hal_include/furi_hal.h index 3eaf06da6..719df0275 100644 --- a/targets/furi_hal_include/furi_hal.h +++ b/targets/furi_hal_include/furi_hal.h @@ -12,6 +12,7 @@ struct STOP_EXTERNING_ME {}; #include #include +#include #include #include #include diff --git a/targets/furi_hal_include/furi_hal_adc.h b/targets/furi_hal_include/furi_hal_adc.h new file mode 100644 index 000000000..ecbdad2ca --- /dev/null +++ b/targets/furi_hal_include/furi_hal_adc.h @@ -0,0 +1,229 @@ +/** + * @file furi_hal_adc.h + * @brief ADC HAL API + * + * For the sake of simplicity this API implements only small subset + * of what ADC is actually capable of. Feel free to visit Reference + * Manual for STM32WB series and implement any other modes by your + * self. + * + * Couple things to keep in mind: + * + * - ADC resolution is 12 bits, but effective number of bits is ~10 at the best + * and further depends on how you use and configure it. + * - Analog domain is fed from SMPS which is quite noisy. + * - Because of that we use internal on-chip voltage reference for ADC. + * - It's capable of producing 2 voltages: 2.5V and 2.048V. This is the scale + * for your signal. + * - Only single ended mode is available. But you can implement differential one + * by using low level controls directly. + * - No DMA or interrupt API available at this point. But can be implemented + * with low level controls. + * + * + * How to use: + * + * - furi_hal_gpio_init - Configure your pins in `GpioModeAnalog` + * - furi_hal_adc_acquire - acquire ADC handle to work with + * - furi_hal_adc_configure - configure ADC block + * - furi_hal_adc_read - read value + * - furi_hal_adc_release - release ADC handle + */ + +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct FuriHalAdcHandle FuriHalAdcHandle; + +typedef enum { + FuriHalAdcScale2048, /**< 2.048V scale */ + FuriHalAdcScale2500, /**< 2.5V scale */ +} FuriHalAdcScale; + +typedef enum { + FuriHalAdcClockSync16, /**< 16MHZ, synchronous */ + FuriHalAdcClockSync32, /**< 32MHZ, synchronous */ + FuriHalAdcClockSync64, /**< 64MHz, synchronous */ +} FuriHalAdcClock; + +typedef enum { + FuriHalAdcOversample2, /**< ADC will take 2 samples per each value */ + FuriHalAdcOversample4, /**< ADC will take 4 samples per each value */ + FuriHalAdcOversample8, /**< ADC will take 8 samples per each value */ + FuriHalAdcOversample16, /**< ADC will take 16 samples per each value */ + FuriHalAdcOversample32, /**< ADC will take 32 samples per each value */ + FuriHalAdcOversample64, /**< ADC will take 64 samples per each value */ + FuriHalAdcOversample128, /**< ADC will take 128 samples per each value */ + FuriHalAdcOversample256, /**< ADC will take 256 samples per each value */ + FuriHalAdcOversampleNone, /**< disable oversampling */ +} FuriHalAdcOversample; + +typedef enum { + FuriHalAdcSamplingtime2_5, /**< Sampling time 2.5 ADC clock */ + FuriHalAdcSamplingtime6_5, /**< Sampling time 6.5 ADC clock */ + FuriHalAdcSamplingtime12_5, /**< Sampling time 12.5 ADC clock */ + FuriHalAdcSamplingtime24_5, /**< Sampling time 24.5 ADC clock */ + FuriHalAdcSamplingtime47_5, /**< Sampling time 47.5 ADC clock */ + FuriHalAdcSamplingtime92_5, /**< Sampling time 92.5 ADC clock */ + FuriHalAdcSamplingtime247_5, /**< Sampling time 247.5 ADC clock */ + FuriHalAdcSamplingtime640_5, /**< Sampling time 640.5 ADC clock */ +} FuriHalAdcSamplingTime; + +typedef enum { + /* Channels 0 - 5 are fast channels */ + FuriHalAdcChannel0, /**< Internal channel, see `FuriHalAdcChannelVREFINT`. */ + FuriHalAdcChannel1, /**< Channel 1p */ + FuriHalAdcChannel2, /**< Channel 2p or 1n */ + FuriHalAdcChannel3, /**< Channel 3p or 2n */ + FuriHalAdcChannel4, /**< Channel 4p or 3n */ + FuriHalAdcChannel5, /**< Channel 5p or 4n */ + /* Channels 6 - 18 are slow channels */ + FuriHalAdcChannel6, /**< Channel 6p or 5n */ + FuriHalAdcChannel7, /**< Channel 7p or 6n */ + FuriHalAdcChannel8, /**< Channel 8p or 7n */ + FuriHalAdcChannel9, /**< Channel 9p or 8n */ + FuriHalAdcChannel10, /**< Channel 10p or 9n */ + FuriHalAdcChannel11, /**< Channel 11p or 10n */ + FuriHalAdcChannel12, /**< Channel 12p or 11n */ + FuriHalAdcChannel13, /**< Channel 13p or 12n */ + FuriHalAdcChannel14, /**< Channel 14p or 13n */ + FuriHalAdcChannel15, /**< Channel 15p or 14n */ + FuriHalAdcChannel16, /**< Channel 16p or 15n */ + FuriHalAdcChannel17, /**< Internal channel, see `FuriHalAdcChannelTEMPSENSOR`. */ + FuriHalAdcChannel18, /**< Internal channel, see `FuriHalAdcChannelVBAT`. */ + /* Special Channels: combines one of the 0-18 channel and additional internal peripherals */ + FuriHalAdcChannelVREFINT, /**< Special channel for VREFINT, used for calibration and self test */ + FuriHalAdcChannelTEMPSENSOR, /**< Special channel for on-die temperature sensor, requires at least 5us of sampling time */ + FuriHalAdcChannelVBAT, /**< Special channel for VBAT/3 voltage, requires at least 12us of sampling time */ + /* Special value to indicate that pin is not connected to ADC */ + FuriHalAdcChannelNone, /**< No channel */ +} FuriHalAdcChannel; + +/** Initialize ADC subsystem */ +void furi_hal_adc_init(void); + +/** Acquire ADC handle + * + * Enables appropriate power and clocking domains + * + * @return FuriHalAdcHandle pointer + */ +FuriHalAdcHandle* furi_hal_adc_acquire(void); + +/** Release ADC handle + * + * @param handle The ADC handle + */ +void furi_hal_adc_release(FuriHalAdcHandle* handle); + +/** Configure with default parameters and enable ADC + * + * Parameters used: + * - FuriHalAdcScale2048 - 2.048V VREF Scale. Your signal should be in 0 - + * 2.048V range. + * - FuriHalAdcClockSync64 - Clocked from sysclk bus at 64MHz in synchronous + * mode. Fast, no delay on data bus access. + * - FuriHalAdcOversample64 - Going to acquire and average 64 samples. For + * circuits with slowly or not changing signal. Total time per one read: + * (1/64)*(12.5+247.5)*64 = 260us. The best results you'll get if your signal + * will stay on the same level all this time. + * - FuriHalAdcSamplingtime247_5 - Sampling(transfer from source to internal + * sampling capacitor) time is 247.5 ADC clocks: (1/64)*247.5 = 3.8671875us. + * For relatively high impedance circuits. + * + * Also keep your measurement circuit impedance under 10KOhm or oversampling + * results will be compromised. Verify your signal with oscilloscope(you may + * need fast oscilloscope: 200MHz bandwidth, 125MS/s), ensure that signal is not + * distorted by sampling. + * + * Those parameters were optimized for 0 - 2.048 voltage measurement with ~0.1% + * precision. You can get more, but it will require some magic. + * + * @param handle The ADC handle + */ +void furi_hal_adc_configure(FuriHalAdcHandle* handle); + +/** Configure with extended parameters and enable ADC + * + * General advice is to start with default parameters, figure out what exactly + * is not working for you and then tune it. Also in some cases changing + * circuit(adding caps, lowering impedance, adding opamp) may be better than changing + * parameters. + * + * @warning In general ADC is a world of magic: make sure that you understand + * how your circuit and ADC works. Then carefully read STM32WB + * series reference manual. Setting incorrect parameters leads to + * very poor results. Also internal channels require special + * settings. + * + * @param handle The ADC handle + * @param[in] scale The ADC voltage scale + * @param[in] clock The ADC clock + * @param[in] oversample The ADC oversample mode + * @param[in] sampling_time The ADC sampling time + */ +void furi_hal_adc_configure_ex( + FuriHalAdcHandle* handle, + FuriHalAdcScale scale, + FuriHalAdcClock clock, + FuriHalAdcOversample oversample, + FuriHalAdcSamplingTime sampling_time); + +/** Read single ADC value + * + * @param handle The ADC handle + * @param[in] channel The channel to sample + * + * @return value, 12 bits + */ +uint16_t furi_hal_adc_read(FuriHalAdcHandle* handle, FuriHalAdcChannel channel); + +/** Convert sampled value to voltage + * + * @param handle The ADC handle + * @param[in] value The value acquired with `furi_hal_adc_read` + * + * @return Voltage in mV + */ +float furi_hal_adc_convert_to_voltage(FuriHalAdcHandle* handle, uint16_t value); + +/** Convert sampled VREFINT value to voltage + * + * @param handle The ADC handle + * @param[in] value The value acquired with `furi_hal_adc_read` for + * `FuriHalAdcChannelVREFINT` channel + * + * @return Voltage in mV + */ +float furi_hal_adc_convert_vref(FuriHalAdcHandle* handle, uint16_t value); + +/** Convert sampled TEMPSENSOR value to temperature + * + * @param handle The ADC handle + * @param[in] value The value acquired with `furi_hal_adc_read` for + * `FuriHalAdcChannelTEMPSENSOR` channel + * + * @return temperature in degree C + */ +float furi_hal_adc_convert_temp(FuriHalAdcHandle* handle, uint16_t value); + +/** Convert sampled VBAT value to voltage + * + * @param handle The ADC handle + * @param[in] value The value acquired with `furi_hal_adc_read` for + * `FuriHalAdcChannelVBAT` channel + * + * @return Voltage in mV + */ +float furi_hal_adc_convert_vbat(FuriHalAdcHandle* handle, uint16_t value); + +#ifdef __cplusplus +} +#endif From 8c69bcf32078041b6693ccc0049d5e8695d7cf33 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Wed, 17 Apr 2024 19:37:09 +0100 Subject: [PATCH 123/129] Sync WAV Player --- applications/external | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/external b/applications/external index f07702622..68b1a9af7 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit f07702622b868ff1b1dddf49ed03c48f06bb3815 +Subproject commit 68b1a9af7c8ff1cdb3b9bb269fab014810376f22 From 4883383b208a7214350b6f35ccd44c5324f735d4 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 18 Apr 2024 08:05:37 +0900 Subject: [PATCH 124/129] [FL-3679] iButton new UI (#3471) * iButton new UI * UI final touches * Satisfy PVS Co-authored-by: Aleksandr Kutuzov --- .../ibutton/scenes/ibutton_scene_config.h | 1 + .../scenes/ibutton_scene_delete_confirm.c | 22 +++++-- .../ibutton/scenes/ibutton_scene_emulate.c | 17 ++--- .../main/ibutton/scenes/ibutton_scene_info.c | 16 +++-- .../main/ibutton/scenes/ibutton_scene_read.c | 6 +- .../ibutton/scenes/ibutton_scene_read_error.c | 7 +-- .../scenes/ibutton_scene_read_exit_confirm.c | 59 ++++++++++++++++++ .../scenes/ibutton_scene_read_key_menu.c | 22 +++---- .../scenes/ibutton_scene_read_success.c | 8 +-- .../scenes/ibutton_scene_saved_key_menu.c | 8 ++- .../main/ibutton/scenes/ibutton_scene_write.c | 19 +++--- assets/icons/Dolphin/DolphinWait_59x54.png | Bin 0 -> 1539 bytes lib/ibutton/ibutton_protocols.c | 11 ++++ lib/ibutton/ibutton_protocols.h | 11 ++++ lib/ibutton/protocols/dallas/dallas_common.c | 34 +++++----- lib/ibutton/protocols/dallas/dallas_common.h | 2 + .../protocols/dallas/protocol_dallas_base.h | 1 + .../protocols/dallas/protocol_ds1971.c | 16 ++++- .../protocols/dallas/protocol_ds1990.c | 10 +++ .../protocols/dallas/protocol_ds1992.c | 16 ++++- .../protocols/dallas/protocol_ds1996.c | 16 ++++- .../protocols/dallas/protocol_ds_generic.c | 10 ++- .../protocols/dallas/protocol_group_dallas.c | 19 ++++++ lib/ibutton/protocols/misc/protocol_cyfral.c | 9 +++ lib/ibutton/protocols/misc/protocol_metakom.c | 8 ++- lib/ibutton/protocols/protocol_group_base.h | 1 + lib/toolbox/protocols/protocol.h | 1 + targets/f18/api_symbols.csv | 2 +- targets/f7/api_symbols.csv | 3 +- 29 files changed, 280 insertions(+), 75 deletions(-) create mode 100644 applications/main/ibutton/scenes/ibutton_scene_read_exit_confirm.c create mode 100644 assets/icons/Dolphin/DolphinWait_59x54.png diff --git a/applications/main/ibutton/scenes/ibutton_scene_config.h b/applications/main/ibutton/scenes/ibutton_scene_config.h index 79f6791b3..f3fd0fff7 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_config.h +++ b/applications/main/ibutton/scenes/ibutton_scene_config.h @@ -17,5 +17,6 @@ ADD_SCENE(ibutton, delete_confirm, DeleteConfirm) ADD_SCENE(ibutton, delete_success, DeleteSuccess) ADD_SCENE(ibutton, retry_confirm, RetryConfirm) ADD_SCENE(ibutton, exit_confirm, ExitConfirm) +ADD_SCENE(ibutton, read_exit_confirm, ReadExitConfirm) ADD_SCENE(ibutton, view_data, ViewData) ADD_SCENE(ibutton, rpc, Rpc) diff --git a/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c b/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c index b293af952..a7eba672a 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c +++ b/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c @@ -7,23 +7,33 @@ void ibutton_scene_delete_confirm_on_enter(void* context) { Widget* widget = ibutton->widget; FuriString* tmp = furi_string_alloc(); + FuriString* uid = furi_string_alloc(); widget_add_button_element(widget, GuiButtonTypeLeft, "Back", ibutton_widget_callback, context); widget_add_button_element( widget, GuiButtonTypeRight, "Delete", ibutton_widget_callback, context); - furi_string_printf(tmp, "\e#Delete %s?\e#", ibutton->key_name); + furi_string_printf(tmp, "\e#Delete %s?\e#\n", ibutton->key_name); + + ibutton_protocols_render_uid(ibutton->protocols, key, uid); + + furi_string_cat_printf( + uid, + "\n%s %s", + ibutton_protocols_get_manufacturer(ibutton->protocols, ibutton_key_get_protocol_id(key)), + ibutton_protocols_get_name(ibutton->protocols, ibutton_key_get_protocol_id(key))); + + furi_string_cat(tmp, uid); + widget_add_text_box_element( - widget, 0, 0, 128, 23, AlignCenter, AlignCenter, furi_string_get_cstr(tmp), false); + widget, 0, 0, 128, 64, AlignCenter, AlignTop, furi_string_get_cstr(tmp), false); furi_string_reset(tmp); - ibutton_protocols_render_brief_data(ibutton->protocols, key, tmp); - - widget_add_string_multiline_element( - widget, 128 / 2, 24, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(tmp)); + furi_string_reset(uid); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); furi_string_free(tmp); + furi_string_free(uid); } bool ibutton_scene_delete_confirm_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/ibutton/scenes/ibutton_scene_emulate.c b/applications/main/ibutton/scenes/ibutton_scene_emulate.c index 713b8331c..070f22910 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_emulate.c +++ b/applications/main/ibutton/scenes/ibutton_scene_emulate.c @@ -21,17 +21,20 @@ void ibutton_scene_emulate_on_enter(void* context) { widget_add_icon_element(widget, 3, 10, &I_iButtonKey_49x44); - furi_string_printf( - tmp, - "%s\n[%s]", - furi_string_empty(ibutton->file_path) ? "Unsaved Key" : ibutton->key_name, - ibutton_protocols_get_name(ibutton->protocols, ibutton_key_get_protocol_id(key))); + if(furi_string_empty(ibutton->file_path)) { + furi_string_printf( + tmp, + "Unsaved\n%s", + ibutton_protocols_get_name(ibutton->protocols, ibutton_key_get_protocol_id(key))); + } else { + furi_string_printf(tmp, "%s", ibutton->key_name); + } widget_add_text_box_element( - widget, 52, 38, 75, 26, AlignCenter, AlignCenter, furi_string_get_cstr(tmp), true); + widget, 52, 23, 75, 26, AlignCenter, AlignTop, furi_string_get_cstr(tmp), false); widget_add_string_multiline_element( - widget, 88, 10, AlignCenter, AlignTop, FontPrimary, "iButton\nemulating"); + widget, 88, 10, AlignCenter, AlignTop, FontPrimary, "Emulating"); ibutton_worker_emulate_set_callback(ibutton->worker, ibutton_scene_emulate_callback, ibutton); ibutton_worker_emulate_start(ibutton->worker, key); diff --git a/applications/main/ibutton/scenes/ibutton_scene_info.c b/applications/main/ibutton/scenes/ibutton_scene_info.c index cf44d6a86..ba72de6ca 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_info.c +++ b/applications/main/ibutton/scenes/ibutton_scene_info.c @@ -8,21 +8,24 @@ void ibutton_scene_info_on_enter(void* context) { const iButtonProtocolId protocol_id = ibutton_key_get_protocol_id(key); FuriString* tmp = furi_string_alloc(); + FuriString* brief_data = furi_string_alloc(); furi_string_printf( tmp, - "\e#%s [%s]\e#", + "Name:%s\n\e#%s %s\e#\n", ibutton->key_name, + ibutton_protocols_get_manufacturer(ibutton->protocols, protocol_id), ibutton_protocols_get_name(ibutton->protocols, protocol_id)); + ibutton_protocols_render_brief_data(ibutton->protocols, key, brief_data); + + furi_string_cat(tmp, brief_data); + widget_add_text_box_element( - widget, 0, 2, 128, 12, AlignLeft, AlignTop, furi_string_get_cstr(tmp), true); + widget, 0, 0, 128, 64, AlignLeft, AlignTop, furi_string_get_cstr(tmp), false); furi_string_reset(tmp); - ibutton_protocols_render_brief_data(ibutton->protocols, key, tmp); - - widget_add_string_multiline_element( - widget, 0, 16, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(tmp)); + furi_string_reset(brief_data); if(ibutton_protocols_get_features(ibutton->protocols, protocol_id) & iButtonProtocolFeatureExtData) { @@ -32,6 +35,7 @@ void ibutton_scene_info_on_enter(void* context) { view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); furi_string_free(tmp); + furi_string_free(brief_data); } bool ibutton_scene_info_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/ibutton/scenes/ibutton_scene_read.c b/applications/main/ibutton/scenes/ibutton_scene_read.c index f360c3ac4..f490488f5 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_read.c +++ b/applications/main/ibutton/scenes/ibutton_scene_read.c @@ -12,9 +12,9 @@ void ibutton_scene_read_on_enter(void* context) { iButtonKey* key = ibutton->key; iButtonWorker* worker = ibutton->worker; - popup_set_header(popup, "iButton", 95, 26, AlignCenter, AlignBottom); - popup_set_text(popup, "Apply key to\nFlipper's back", 95, 30, AlignCenter, AlignTop); - popup_set_icon(popup, 0, 5, &I_DolphinWait_61x59); + popup_set_header(popup, "Reading", 95, 26, AlignCenter, AlignBottom); + popup_set_text(popup, "Connect key\nwith pogo pins", 95, 30, AlignCenter, AlignTop); + popup_set_icon(popup, 0, 10, &I_DolphinWait_59x54); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup); diff --git a/applications/main/ibutton/scenes/ibutton_scene_read_error.c b/applications/main/ibutton/scenes/ibutton_scene_read_error.c index e966384bf..665565752 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_read_error.c +++ b/applications/main/ibutton/scenes/ibutton_scene_read_error.c @@ -14,13 +14,10 @@ void ibutton_scene_read_error_on_enter(void* context) { widget_add_button_element( widget, GuiButtonTypeRight, "More", ibutton_widget_callback, context); - widget_add_string_element( - widget, 128 / 2, 2, AlignCenter, AlignTop, FontPrimary, "Read Error"); - ibutton_protocols_render_error(ibutton->protocols, key, tmp); - widget_add_string_multiline_element( - widget, 128 / 2, 16, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(tmp)); + widget_add_text_box_element( + widget, 0, 0, 128, 48, AlignCenter, AlignTop, furi_string_get_cstr(tmp), false); ibutton_notification_message(ibutton, iButtonNotificationMessageError); ibutton_notification_message(ibutton, iButtonNotificationMessageRedOn); diff --git a/applications/main/ibutton/scenes/ibutton_scene_read_exit_confirm.c b/applications/main/ibutton/scenes/ibutton_scene_read_exit_confirm.c new file mode 100644 index 000000000..4077de9a6 --- /dev/null +++ b/applications/main/ibutton/scenes/ibutton_scene_read_exit_confirm.c @@ -0,0 +1,59 @@ +#include "../ibutton_i.h" + +static void ibutton_scene_read_exit_confirm_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + iButton* ibutton = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(ibutton->view_dispatcher, result); + } +} + +void ibutton_scene_read_exit_confirm_on_enter(void* context) { + iButton* ibutton = context; + Widget* widget = ibutton->widget; + + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Exit", + ibutton_scene_read_exit_confirm_widget_callback, + ibutton); + widget_add_button_element( + widget, + GuiButtonTypeRight, + "Stay", + ibutton_scene_read_exit_confirm_widget_callback, + ibutton); + widget_add_string_element( + widget, 64, 19, AlignCenter, AlignBottom, FontPrimary, "Retry Reading?"); + widget_add_string_element( + widget, 64, 31, AlignCenter, AlignBottom, FontSecondary, "All unsaved data will be lost!"); + + view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); +} + +bool ibutton_scene_read_exit_confirm_on_event(void* context, SceneManagerEvent event) { + iButton* ibutton = context; + SceneManager* scene_manager = ibutton->scene_manager; + bool consumed = false; + + if(event.type == SceneManagerEventTypeBack) { + consumed = true; // Ignore Back button presses + } else if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == GuiButtonTypeLeft) { + scene_manager_search_and_switch_to_previous_scene(scene_manager, iButtonSceneRead); + } else if(event.event == GuiButtonTypeRight) { + scene_manager_previous_scene(scene_manager); + } + } + + return consumed; +} + +void ibutton_scene_read_exit_confirm_on_exit(void* context) { + iButton* ibutton = context; + widget_reset(ibutton->widget); +} diff --git a/applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c b/applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c index 1555f2cc2..890e0a284 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c +++ b/applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c @@ -30,19 +30,10 @@ void ibutton_scene_read_key_menu_on_enter(void* context) { ibutton_scene_read_key_menu_submenu_callback, ibutton); - if(features & iButtonProtocolFeatureExtData) { - submenu_add_item( - submenu, - "View Data", - SubmenuIndexViewData, - ibutton_scene_read_key_menu_submenu_callback, - ibutton); - } - if(features & iButtonProtocolFeatureWriteBlank) { submenu_add_item( submenu, - "Write Blank", + "Write ID", SubmenuIndexWriteBlank, ibutton_scene_read_key_menu_submenu_callback, ibutton); @@ -51,12 +42,21 @@ void ibutton_scene_read_key_menu_on_enter(void* context) { if(features & iButtonProtocolFeatureWriteCopy) { submenu_add_item( submenu, - "Write Copy", + "Full Write on Same Type", SubmenuIndexWriteCopy, ibutton_scene_read_key_menu_submenu_callback, ibutton); } + if(features & iButtonProtocolFeatureExtData) { + submenu_add_item( + submenu, + "Data Info", + SubmenuIndexViewData, + ibutton_scene_read_key_menu_submenu_callback, + ibutton); + } + submenu_set_selected_item( submenu, scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneReadKeyMenu)); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewSubmenu); diff --git a/applications/main/ibutton/scenes/ibutton_scene_read_success.c b/applications/main/ibutton/scenes/ibutton_scene_read_success.c index 2e50bc996..6dd06ca52 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_read_success.c +++ b/applications/main/ibutton/scenes/ibutton_scene_read_success.c @@ -18,9 +18,9 @@ void ibutton_scene_read_success_on_enter(void* context) { furi_string_printf( tmp, - "%s[%s]", - ibutton_protocols_get_name(ibutton->protocols, protocol_id), - ibutton_protocols_get_manufacturer(ibutton->protocols, protocol_id)); + "%s %s", + ibutton_protocols_get_manufacturer(ibutton->protocols, protocol_id), + ibutton_protocols_get_name(ibutton->protocols, protocol_id)); widget_add_string_element( widget, 0, 2, AlignLeft, AlignTop, FontPrimary, furi_string_get_cstr(tmp)); @@ -44,7 +44,7 @@ bool ibutton_scene_read_success_on_event(void* context, SceneManagerEvent event) if(event.type == SceneManagerEventTypeBack) { consumed = true; - scene_manager_next_scene(scene_manager, iButtonSceneExitConfirm); + scene_manager_next_scene(scene_manager, iButtonSceneReadExitConfirm); } else if(event.type == SceneManagerEventTypeCustom) { consumed = true; if(event.event == GuiButtonTypeRight) { diff --git a/applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c b/applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c index fc0cf42e2..1547a647b 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c +++ b/applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c @@ -21,12 +21,16 @@ void ibutton_scene_saved_key_menu_on_enter(void* context) { if(features & iButtonProtocolFeatureWriteBlank) { submenu_add_item( - submenu, "Write Blank", SubmenuIndexWriteBlank, ibutton_submenu_callback, ibutton); + submenu, "Write ID", SubmenuIndexWriteBlank, ibutton_submenu_callback, ibutton); } if(features & iButtonProtocolFeatureWriteCopy) { submenu_add_item( - submenu, "Write Copy", SubmenuIndexWriteCopy, ibutton_submenu_callback, ibutton); + submenu, + "Full Write on Same Type", + SubmenuIndexWriteCopy, + ibutton_submenu_callback, + ibutton); } submenu_add_item(submenu, "Edit", SubmenuIndexEdit, ibutton_submenu_callback, ibutton); diff --git a/applications/main/ibutton/scenes/ibutton_scene_write.c b/applications/main/ibutton/scenes/ibutton_scene_write.c index 63be63506..465b06301 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_write.c +++ b/applications/main/ibutton/scenes/ibutton_scene_write.c @@ -40,25 +40,24 @@ void ibutton_scene_write_on_enter(void* context) { widget_add_icon_element(widget, 3, 10, &I_iButtonKey_49x44); - furi_string_printf( - tmp, - "%s\n[%s]", - ibutton->key_name, - ibutton_protocols_get_name(ibutton->protocols, protocol_id)); + if(furi_string_empty(ibutton->file_path)) { + furi_string_printf( + tmp, "Unsaved\n%s", ibutton_protocols_get_name(ibutton->protocols, protocol_id)); + } else { + furi_string_printf(tmp, "%s", ibutton->key_name); + } widget_add_text_box_element( - widget, 52, 38, 75, 26, AlignCenter, AlignCenter, furi_string_get_cstr(tmp), true); + widget, 52, 23, 75, 26, AlignCenter, AlignTop, furi_string_get_cstr(tmp), false); ibutton_worker_write_set_callback(worker, ibutton_scene_write_callback, ibutton); - furi_string_set(tmp, "iButton\nwriting "); - if(ibutton->write_mode == iButtonWriteModeBlank) { - furi_string_cat(tmp, "Blank"); + furi_string_set(tmp, "Writing ID"); ibutton_worker_write_blank_start(worker, key); } else if(ibutton->write_mode == iButtonWriteModeCopy) { - furi_string_cat(tmp, "Copy"); + furi_string_set(tmp, "Full Writing"); ibutton_worker_write_copy_start(worker, key); } diff --git a/assets/icons/Dolphin/DolphinWait_59x54.png b/assets/icons/Dolphin/DolphinWait_59x54.png new file mode 100644 index 0000000000000000000000000000000000000000..bdf8171b8028801b1f82d682cda4366ce6848bc5 GIT binary patch literal 1539 zcmbVMeM}Q)7_TxvVkTomCL+^Y*#zgVcfEdG@1$sHsg_w_rI~d&$Mx=QX`pxI?w~DG zro}ki=He9N&_Ti`78?x@D=!CanvlS37m0^u7O6DJd#U+#XghA8u=x zLmoyiU#_vDR-X|Rc$Sv&Ah#^bPL~zZ1S4OR2Bii_Nr4xL6cq3lu>u*;%SZK+k{mup zssZ>Mb zk6g0IGcM9*GLPGnc6xb%DEdeQ@%#M>ze>UJZbV5C1cG7+hQSg77J{ru1z=W4ifAwa zf#y9v(ZjJ&SR>`+N<_U}@^&l?ug_|IqL>xNlO?4M2~a*nsX!60Hyqn2S`cmEUpJnL z7VJSEKx{zZN_bkTi7P2Wmh$&(Lt&ug4Vle*q{2`|CXO!g0#>w`^m6G&!FU)F01ZJi zDgwqR+y$$(Din5NDgfg&p&^t`EpV!w5j{`is|~mcSE4!`H={~rMh2n9v2>%!Y*wo= zL%NoTa4oDLQY;N3wmp*VajsOmXK+a)4=9o2?HpGW>4cmDPUM6F&Ie%{1q!8DDcZw^ zPv(cKG#0c8@Sak@n0d|%ji#COJjDeJCkRT(;4r1cl&~6OG_Vus)G&iF1gZlX7e!0q zkMWuRn?6Kx3JLehe|jf!BsFAMd@>Ev=E*z(RvIE+nwHy%w~xzY6CsPqU=Lh4yJDcZ z6Po&U^y9~;h!=VtW^kh+xhHRS#n8kB1;N^zTOU-}jt^9|rp!z^xht{srQ)bM)0q#e zk7XJYs}HTYad7eWrjx~6qgb}NbTISa$Ok|3gAR9UWnYJ8T_dt?-;{SRM=k&TY~QI` zxM$?Z)bgaRydCqi&?B{1Hx=0P`#ZN>SakCW8$*W%^6qrp&s7bbn}^zx8n#bjV(w+T z=B(O(%O4Y~>WFP?FjcLMHs4CX@7yi9^68}**^hB>>!Z(e5-Y0JulH^F)Y;Qlo&y4!5XJy&P2X^f>R%j~fugQDrUhJKFb&o3!JHFXY<^gwY|2tX4{xoBiF>$rUelbShQFVSm%Y{Es8*(jTEf3&OGcKdF+D%@4+0EB A-~a#s literal 0 HcmV?d00001 diff --git a/lib/ibutton/ibutton_protocols.c b/lib/ibutton/ibutton_protocols.c index ce4392475..84fcccd71 100644 --- a/lib/ibutton/ibutton_protocols.c +++ b/lib/ibutton/ibutton_protocols.c @@ -287,6 +287,17 @@ bool ibutton_protocols_load(iButtonProtocols* protocols, iButtonKey* key, const return success; } +void ibutton_protocols_render_uid( + iButtonProtocols* protocols, + const iButtonKey* key, + FuriString* result) { + const iButtonProtocolId id = ibutton_key_get_protocol_id(key); + const iButtonProtocolData* data = ibutton_key_get_protocol_data(key); + + GET_PROTOCOL_GROUP(id); + GROUP_BASE->render_uid(GROUP_DATA, data, PROTOCOL_ID, result); +} + void ibutton_protocols_render_data( iButtonProtocols* protocols, const iButtonKey* key, diff --git a/lib/ibutton/ibutton_protocols.h b/lib/ibutton/ibutton_protocols.h index ec4a9fc72..dd2afbd6e 100644 --- a/lib/ibutton/ibutton_protocols.h +++ b/lib/ibutton/ibutton_protocols.h @@ -133,6 +133,17 @@ bool ibutton_protocols_save( */ bool ibutton_protocols_load(iButtonProtocols* protocols, iButtonKey* key, const char* file_name); +/** + * Format a string containing defice UID + * @param [in] protocols pointer to an iButtonProtocols object + * @param [in] key pointer to the key to be rendered + * @param [out] result pointer to the FuriString instance (must be initialized) + */ +void ibutton_protocols_render_uid( + iButtonProtocols* protocols, + const iButtonKey* key, + FuriString* result); + /** * Format a string containing device full data * @param [in] protocols pointer to an iButtonProtocols object diff --git a/lib/ibutton/protocols/dallas/dallas_common.c b/lib/ibutton/protocols/dallas/dallas_common.c index 6e99a3be2..7ce12f7a0 100644 --- a/lib/ibutton/protocols/dallas/dallas_common.c +++ b/lib/ibutton/protocols/dallas/dallas_common.c @@ -208,15 +208,26 @@ bool dallas_common_is_valid_crc(const DallasCommonRomData* rom_data) { return crc_calculated == crc_received; } +void dallas_common_render_uid(FuriString* result, const DallasCommonRomData* rom_data) { + furi_string_cat_printf(result, "ID: "); + for(size_t i = 0; i < sizeof(DallasCommonRomData); ++i) { + furi_string_cat_printf(result, "%02X ", rom_data->bytes[i]); + } +} + void dallas_common_render_brief_data( FuriString* result, const DallasCommonRomData* rom_data, const uint8_t* mem_data, size_t mem_size, const char* mem_name) { + UNUSED(mem_data); + + furi_string_cat_printf(result, "ID: "); for(size_t i = 0; i < sizeof(rom_data->bytes); ++i) { furi_string_cat_printf(result, "%02X ", rom_data->bytes[i]); } + furi_string_cat_printf(result, "\nFamily Code: %02X\n", rom_data->bytes[0]); const char* size_prefix = ""; size_t mem_size_bits = mem_size * BITS_IN_BYTE; @@ -229,28 +240,23 @@ void dallas_common_render_brief_data( mem_size_bits /= BITS_IN_KBIT; } - furi_string_cat_printf( - result, "\nInternal %s: %zu %sbit\n", mem_name, mem_size_bits, size_prefix); - - for(size_t i = 0; i < DALLAS_COMMON_BRIEF_HEAD_COUNT; ++i) { - furi_string_cat_printf(result, "%02X ", mem_data[i]); - } - - furi_string_cat_printf(result, "[ . . . ]"); - - for(size_t i = mem_size - DALLAS_COMMON_BRIEF_TAIL_COUNT; i < mem_size; ++i) { - furi_string_cat_printf(result, " %02X", mem_data[i]); - } + furi_string_cat_printf(result, "%s: %zu %sbit\n", mem_name, mem_size_bits, size_prefix); } void dallas_common_render_crc_error(FuriString* result, const DallasCommonRomData* rom_data) { - furi_string_set(result, "CRC Error\n"); + furi_string_set(result, "\e#CRC Error\e#\n"); const size_t data_size = sizeof(DallasCommonRomData); for(size_t i = 0; i < data_size; ++i) { - furi_string_cat_printf(result, (i < data_size - 1) ? "%02X " : "%02X", rom_data->bytes[i]); + furi_string_cat_printf( + result, (i < data_size - 1) ? "%02X " : "\e!%02X\e!", rom_data->bytes[i]); } + + furi_string_cat_printf( + result, + "\nExpected CRC: \e!%02X\e!", + maxim_crc8(rom_data->bytes, sizeof(DallasCommonRomData) - 1, MAXIM_CRC8_INIT)); } void dallas_common_apply_edits(DallasCommonRomData* rom_data, uint8_t family_code) { diff --git a/lib/ibutton/protocols/dallas/dallas_common.h b/lib/ibutton/protocols/dallas/dallas_common.h index 6f5ff7cc0..90fec3e28 100644 --- a/lib/ibutton/protocols/dallas/dallas_common.h +++ b/lib/ibutton/protocols/dallas/dallas_common.h @@ -96,6 +96,8 @@ bool dallas_common_load_rom_data( /* Miscellaneous */ bool dallas_common_is_valid_crc(const DallasCommonRomData* rom_data); +void dallas_common_render_uid(FuriString* result, const DallasCommonRomData* rom_data); + void dallas_common_render_brief_data( FuriString* result, const DallasCommonRomData* rom_data, diff --git a/lib/ibutton/protocols/dallas/protocol_dallas_base.h b/lib/ibutton/protocols/dallas/protocol_dallas_base.h index 55e109936..05620329f 100644 --- a/lib/ibutton/protocols/dallas/protocol_dallas_base.h +++ b/lib/ibutton/protocols/dallas/protocol_dallas_base.h @@ -30,6 +30,7 @@ typedef struct { iButtonProtocolDallasEmulateFunc emulate; iButtonProtocolDallasSaveFunc save; iButtonProtocolDallasLoadFunc load; + iButtonProtocolDallasRenderDataFunc render_uid; iButtonProtocolDallasRenderDataFunc render_data; iButtonProtocolDallasRenderDataFunc render_brief_data; iButtonProtocolDallasRenderDataFunc render_error; diff --git a/lib/ibutton/protocols/dallas/protocol_ds1971.c b/lib/ibutton/protocols/dallas/protocol_ds1971.c index b65e64584..d60803fc6 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1971.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1971.c @@ -35,6 +35,7 @@ static bool dallas_ds1971_write_copy(OneWireHost*, iButtonProtocolData*); static void dallas_ds1971_emulate(OneWireSlave*, iButtonProtocolData*); static bool dallas_ds1971_load(FlipperFormat*, uint32_t, iButtonProtocolData*); static bool dallas_ds1971_save(FlipperFormat*, const iButtonProtocolData*); +static void dallas_ds1971_render_uid(FuriString*, const iButtonProtocolData*); static void dallas_ds1971_render_data(FuriString*, const iButtonProtocolData*); static void dallas_ds1971_render_brief_data(FuriString*, const iButtonProtocolData*); static void dallas_ds1971_render_error(FuriString*, const iButtonProtocolData*); @@ -58,6 +59,7 @@ const iButtonProtocolDallasBase ibutton_protocol_ds1971 = { .emulate = dallas_ds1971_emulate, .save = dallas_ds1971_save, .load = dallas_ds1971_load, + .render_uid = dallas_ds1971_render_uid, .render_data = dallas_ds1971_render_data, .render_brief_data = dallas_ds1971_render_brief_data, .render_error = dallas_ds1971_render_error, @@ -209,14 +211,26 @@ bool dallas_ds1971_save(FlipperFormat* ff, const iButtonProtocolData* protocol_d return success; } +void dallas_ds1971_render_uid(FuriString* result, const iButtonProtocolData* protocol_data) { + const DS1971ProtocolData* data = protocol_data; + dallas_common_render_uid(result, &data->rom_data); +} + void dallas_ds1971_render_data(FuriString* result, const iButtonProtocolData* protocol_data) { const DS1971ProtocolData* data = protocol_data; + FuriString* data_string = furi_string_alloc(); + pretty_format_bytes_hex_canonical( - result, + data_string, DS1971_DATA_BYTE_COUNT, PRETTY_FORMAT_FONT_MONOSPACE, data->eeprom_data, DS1971_EEPROM_DATA_SIZE); + + furi_string_cat_printf(result, "\e#Memory Data\n--------------------\n"); + furi_string_cat_printf(result, "%s", furi_string_get_cstr(data_string)); + + furi_string_free(data_string); } void dallas_ds1971_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) { diff --git a/lib/ibutton/protocols/dallas/protocol_ds1990.c b/lib/ibutton/protocols/dallas/protocol_ds1990.c index 86d39f1bd..67e7545f4 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1990.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1990.c @@ -27,6 +27,7 @@ static bool dallas_ds1990_write_blank(OneWireHost*, iButtonProtocolData*); static void dallas_ds1990_emulate(OneWireSlave*, iButtonProtocolData*); static bool dallas_ds1990_load(FlipperFormat*, uint32_t, iButtonProtocolData*); static bool dallas_ds1990_save(FlipperFormat*, const iButtonProtocolData*); +static void dallas_ds1990_render_uid(FuriString*, const iButtonProtocolData*); static void dallas_ds1990_render_brief_data(FuriString*, const iButtonProtocolData*); static void dallas_ds1990_render_error(FuriString*, const iButtonProtocolData*); static bool dallas_ds1990_is_data_valid(const iButtonProtocolData*); @@ -46,6 +47,7 @@ const iButtonProtocolDallasBase ibutton_protocol_ds1990 = { .emulate = dallas_ds1990_emulate, .save = dallas_ds1990_save, .load = dallas_ds1990_load, + .render_uid = dallas_ds1990_render_uid, .render_data = NULL, /* No data to render */ .render_brief_data = dallas_ds1990_render_brief_data, .render_error = dallas_ds1990_render_error, @@ -117,12 +119,20 @@ bool dallas_ds1990_load( return dallas_common_load_rom_data(ff, format_version, &data->rom_data); } +void dallas_ds1990_render_uid(FuriString* result, const iButtonProtocolData* protocol_data) { + const DS1990ProtocolData* data = protocol_data; + + dallas_common_render_uid(result, &data->rom_data); +} + void dallas_ds1990_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) { const DS1990ProtocolData* data = protocol_data; + furi_string_cat_printf(result, "ID: "); for(size_t i = 0; i < sizeof(DallasCommonRomData); ++i) { furi_string_cat_printf(result, "%02X ", data->rom_data.bytes[i]); } + furi_string_cat_printf(result, "\nFamily Code: %02X\n", data->rom_data.bytes[0]); } void dallas_ds1990_render_error(FuriString* result, const iButtonProtocolData* protocol_data) { diff --git a/lib/ibutton/protocols/dallas/protocol_ds1992.c b/lib/ibutton/protocols/dallas/protocol_ds1992.c index 7440882ea..5ddd8ef2c 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1992.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1992.c @@ -36,6 +36,7 @@ static bool dallas_ds1992_write_copy(OneWireHost*, iButtonProtocolData*); static void dallas_ds1992_emulate(OneWireSlave*, iButtonProtocolData*); static bool dallas_ds1992_load(FlipperFormat*, uint32_t, iButtonProtocolData*); static bool dallas_ds1992_save(FlipperFormat*, const iButtonProtocolData*); +static void dallas_ds1992_render_uid(FuriString*, const iButtonProtocolData*); static void dallas_ds1992_render_data(FuriString*, const iButtonProtocolData*); static void dallas_ds1992_render_brief_data(FuriString*, const iButtonProtocolData*); static void dallas_ds1992_render_error(FuriString*, const iButtonProtocolData*); @@ -57,6 +58,7 @@ const iButtonProtocolDallasBase ibutton_protocol_ds1992 = { .emulate = dallas_ds1992_emulate, .save = dallas_ds1992_save, .load = dallas_ds1992_load, + .render_uid = dallas_ds1992_render_uid, .render_data = dallas_ds1992_render_data, .render_brief_data = dallas_ds1992_render_brief_data, .render_error = dallas_ds1992_render_error, @@ -182,14 +184,26 @@ bool dallas_ds1992_save(FlipperFormat* ff, const iButtonProtocolData* protocol_d return success; } +void dallas_ds1992_render_uid(FuriString* result, const iButtonProtocolData* protocol_data) { + const DS1992ProtocolData* data = protocol_data; + dallas_common_render_uid(result, &data->rom_data); +} + void dallas_ds1992_render_data(FuriString* result, const iButtonProtocolData* protocol_data) { const DS1992ProtocolData* data = protocol_data; + FuriString* data_string = furi_string_alloc(); + pretty_format_bytes_hex_canonical( - result, + data_string, DS1992_DATA_BYTE_COUNT, PRETTY_FORMAT_FONT_MONOSPACE, data->sram_data, DS1992_SRAM_DATA_SIZE); + + furi_string_cat_printf(result, "\e#Memory Data\n--------------------\n"); + furi_string_cat_printf(result, "%s", furi_string_get_cstr(data_string)); + + furi_string_free(data_string); } void dallas_ds1992_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) { diff --git a/lib/ibutton/protocols/dallas/protocol_ds1996.c b/lib/ibutton/protocols/dallas/protocol_ds1996.c index 5970a67bb..6af61f355 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1996.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1996.c @@ -33,6 +33,7 @@ static bool dallas_ds1996_write_copy(OneWireHost*, iButtonProtocolData*); static void dallas_ds1996_emulate(OneWireSlave*, iButtonProtocolData*); static bool dallas_ds1996_load(FlipperFormat*, uint32_t, iButtonProtocolData*); static bool dallas_ds1996_save(FlipperFormat*, const iButtonProtocolData*); +static void dallas_ds1996_render_uid(FuriString*, const iButtonProtocolData*); static void dallas_ds1996_render_data(FuriString*, const iButtonProtocolData*); static void dallas_ds1996_render_brief_data(FuriString*, const iButtonProtocolData*); static void dallas_ds1996_render_error(FuriString*, const iButtonProtocolData*); @@ -53,6 +54,7 @@ const iButtonProtocolDallasBase ibutton_protocol_ds1996 = { .emulate = dallas_ds1996_emulate, .save = dallas_ds1996_save, .load = dallas_ds1996_load, + .render_uid = dallas_ds1996_render_uid, .render_data = dallas_ds1996_render_data, .render_brief_data = dallas_ds1996_render_brief_data, .render_error = dallas_ds1996_render_error, @@ -207,15 +209,27 @@ bool dallas_ds1996_save(FlipperFormat* ff, const iButtonProtocolData* protocol_d return success; } +void dallas_ds1996_render_uid(FuriString* result, const iButtonProtocolData* protocol_data) { + const DS1996ProtocolData* data = protocol_data; + dallas_common_render_uid(result, &data->rom_data); +} + void dallas_ds1996_render_data(FuriString* result, const iButtonProtocolData* protocol_data) { const DS1996ProtocolData* data = protocol_data; + FuriString* data_string = furi_string_alloc(); + pretty_format_bytes_hex_canonical( - result, + data_string, DS1996_DATA_BYTE_COUNT, PRETTY_FORMAT_FONT_MONOSPACE, data->sram_data, DS1996_SRAM_DATA_SIZE); + + furi_string_cat_printf(result, "\e#Memory Data\n--------------------\n"); + furi_string_cat_printf(result, "%s", furi_string_get_cstr(data_string)); + + furi_string_free(data_string); } void dallas_ds1996_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) { diff --git a/lib/ibutton/protocols/dallas/protocol_ds_generic.c b/lib/ibutton/protocols/dallas/protocol_ds_generic.c index 6c698bb89..101db1dbe 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds_generic.c +++ b/lib/ibutton/protocols/dallas/protocol_ds_generic.c @@ -8,7 +8,7 @@ #include "../blanks/tm2004.h" #define DALLAS_GENERIC_FAMILY_CODE 0x00U -#define DALLAS_GENERIC_FAMILY_NAME "DSGeneric" +#define DALLAS_GENERIC_FAMILY_NAME "(non-specific)" typedef struct { OneWireSlave* bus; @@ -24,6 +24,7 @@ static bool ds_generic_write_blank(OneWireHost*, iButtonProtocolData*); static void ds_generic_emulate(OneWireSlave*, iButtonProtocolData*); static bool ds_generic_load(FlipperFormat*, uint32_t, iButtonProtocolData*); static bool ds_generic_save(FlipperFormat*, const iButtonProtocolData*); +static void ds_generic_render_uid(FuriString*, const iButtonProtocolData*); static void ds_generic_render_brief_data(FuriString*, const iButtonProtocolData*); static void ds_generic_render_error(FuriString*, const iButtonProtocolData*); static bool ds_generic_is_data_valid(const iButtonProtocolData*); @@ -44,6 +45,7 @@ const iButtonProtocolDallasBase ibutton_protocol_ds_generic = { .save = ds_generic_save, .load = ds_generic_load, .render_data = NULL, /* No data to render */ + .render_uid = ds_generic_render_uid, .render_brief_data = ds_generic_render_brief_data, .render_error = ds_generic_render_error, .is_valid = ds_generic_is_data_valid, @@ -111,9 +113,15 @@ bool ds_generic_load( return dallas_common_load_rom_data(ff, format_version, &data->rom_data); } +void ds_generic_render_uid(FuriString* result, const iButtonProtocolData* protocol_data) { + const DallasGenericProtocolData* data = protocol_data; + dallas_common_render_uid(result, &data->rom_data); +} + void ds_generic_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) { const DallasGenericProtocolData* data = protocol_data; + furi_string_cat_printf(result, "ID: "); for(size_t i = 0; i < sizeof(DallasCommonRomData); ++i) { furi_string_cat_printf(result, "%02X ", data->rom_data.bytes[i]); } diff --git a/lib/ibutton/protocols/dallas/protocol_group_dallas.c b/lib/ibutton/protocols/dallas/protocol_group_dallas.c index 39cabbeb4..7dad75669 100644 --- a/lib/ibutton/protocols/dallas/protocol_group_dallas.c +++ b/lib/ibutton/protocols/dallas/protocol_group_dallas.c @@ -51,6 +51,12 @@ static bool ibutton_protocol_group_dallas_get_id_by_name( return true; } + // Handle files that refer to Dallas "Raw Data" as DSGeneric + if(strcmp(name, "DSGeneric") == 0) { + *id = iButtonProtocolDSGeneric; + return true; + } + for(iButtonProtocolLocalId i = 0; i < iButtonProtocolDSMax; ++i) { if(strcmp(ibutton_protocols_dallas[i]->name, name) == 0) { *id = i; @@ -212,6 +218,18 @@ static bool ibutton_protocol_group_dallas_load( return ibutton_protocols_dallas[id]->load(ff, version, data); } +static void ibutton_protocol_group_dallas_render_uid( + iButtonProtocolGroupDallas* group, + const iButtonProtocolData* data, + iButtonProtocolLocalId id, + FuriString* result) { + UNUSED(group); + furi_assert(id < iButtonProtocolDSMax); + const iButtonProtocolDallasBase* protocol = ibutton_protocols_dallas[id]; + furi_assert(protocol->render_uid); + protocol->render_uid(result, data); +} + static void ibutton_protocol_group_dallas_render_data( iButtonProtocolGroupDallas* group, const iButtonProtocolData* data, @@ -298,6 +316,7 @@ const iButtonProtocolGroupBase ibutton_protocol_group_dallas = { .save = (iButtonProtocolGroupSaveFunc)ibutton_protocol_group_dallas_save, .load = (iButtonProtocolGroupLoadFunc)ibutton_protocol_group_dallas_load, + .render_uid = (iButtonProtocolGroupRenderFunc)ibutton_protocol_group_dallas_render_uid, .render_data = (iButtonProtocolGroupRenderFunc)ibutton_protocol_group_dallas_render_data, .render_brief_data = (iButtonProtocolGroupRenderFunc)ibutton_protocol_group_dallas_render_brief_data, diff --git a/lib/ibutton/protocols/misc/protocol_cyfral.c b/lib/ibutton/protocols/misc/protocol_cyfral.c index 0cdb8766b..d38865bae 100644 --- a/lib/ibutton/protocols/misc/protocol_cyfral.c +++ b/lib/ibutton/protocols/misc/protocol_cyfral.c @@ -325,7 +325,15 @@ static LevelDuration protocol_cyfral_encoder_yield(ProtocolCyfral* proto) { return result; } +static void protocol_cyfral_render_uid(FuriString* result, ProtocolCyfral* proto) { + furi_string_cat_printf(result, "ID: "); + for(size_t i = 0; i < CYFRAL_DATA_SIZE; ++i) { + furi_string_cat_printf(result, "%02X ", ((uint8_t*)&proto->data)[i]); + } +} + static void protocol_cyfral_render_brief_data(ProtocolCyfral* proto, FuriString* result) { + furi_string_cat_printf(result, "ID: "); for(size_t i = 0; i < CYFRAL_DATA_SIZE; ++i) { furi_string_cat_printf(result, "%02X ", ((uint8_t*)&proto->data)[i]); } @@ -348,5 +356,6 @@ const ProtocolBase ibutton_protocol_misc_cyfral = { .start = (ProtocolEncoderStart)protocol_cyfral_encoder_start, .yield = (ProtocolEncoderYield)protocol_cyfral_encoder_yield, }, + .render_uid = (ProtocolRenderData)protocol_cyfral_render_uid, .render_brief_data = (ProtocolRenderData)protocol_cyfral_render_brief_data, }; diff --git a/lib/ibutton/protocols/misc/protocol_metakom.c b/lib/ibutton/protocols/misc/protocol_metakom.c index a2bd2cf7c..6d5e0339d 100644 --- a/lib/ibutton/protocols/misc/protocol_metakom.c +++ b/lib/ibutton/protocols/misc/protocol_metakom.c @@ -301,12 +301,17 @@ static LevelDuration protocol_metakom_encoder_yield(ProtocolMetakom* proto) { return result; } -static void protocol_metakom_render_brief_data(ProtocolMetakom* proto, FuriString* result) { +static void protocol_metakom_render_uid(ProtocolMetakom* proto, FuriString* result) { + furi_string_cat_printf(result, "ID: "); for(size_t i = 0; i < METAKOM_DATA_SIZE; ++i) { furi_string_cat_printf(result, "%02X ", ((uint8_t*)&proto->data)[i]); } } +static void protocol_metakom_render_brief_data(ProtocolMetakom* proto, FuriString* result) { + protocol_metakom_render_uid(proto, result); +} + const ProtocolBase ibutton_protocol_misc_metakom = { .name = "Metakom", .manufacturer = "Metakom", @@ -324,5 +329,6 @@ const ProtocolBase ibutton_protocol_misc_metakom = { .start = (ProtocolEncoderStart)protocol_metakom_encoder_start, .yield = (ProtocolEncoderYield)protocol_metakom_encoder_yield, }, + .render_uid = (ProtocolRenderData)protocol_metakom_render_uid, .render_brief_data = (ProtocolRenderData)protocol_metakom_render_brief_data, }; diff --git a/lib/ibutton/protocols/protocol_group_base.h b/lib/ibutton/protocols/protocol_group_base.h index c8fec70fe..ef57fe0bc 100644 --- a/lib/ibutton/protocols/protocol_group_base.h +++ b/lib/ibutton/protocols/protocol_group_base.h @@ -93,6 +93,7 @@ typedef struct { iButtonProtocolGroupSaveFunc save; iButtonProtocolGroupLoadFunc load; + iButtonProtocolGroupRenderFunc render_uid; iButtonProtocolGroupRenderFunc render_data; iButtonProtocolGroupRenderFunc render_brief_data; iButtonProtocolGroupRenderFunc render_error; diff --git a/lib/toolbox/protocols/protocol.h b/lib/toolbox/protocols/protocol.h index 5a8015b1e..0ee165d13 100644 --- a/lib/toolbox/protocols/protocol.h +++ b/lib/toolbox/protocols/protocol.h @@ -40,6 +40,7 @@ typedef struct { ProtocolGetData get_data; ProtocolDecoder decoder; ProtocolEncoder encoder; + ProtocolRenderData render_uid; ProtocolRenderData render_data; ProtocolRenderData render_brief_data; ProtocolWriteData write_data; diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 43500aa1c..7cc7639bd 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,61.0,, +Version,+,61.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index dbaadc4a1..727a512c9 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,61.0,, +Version,+,61.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -1894,6 +1894,7 @@ Function,+,ibutton_protocols_read,_Bool,"iButtonProtocols*, iButtonKey*" Function,+,ibutton_protocols_render_brief_data,void,"iButtonProtocols*, const iButtonKey*, FuriString*" Function,+,ibutton_protocols_render_data,void,"iButtonProtocols*, const iButtonKey*, FuriString*" Function,+,ibutton_protocols_render_error,void,"iButtonProtocols*, const iButtonKey*, FuriString*" +Function,+,ibutton_protocols_render_uid,void,"iButtonProtocols*, const iButtonKey*, FuriString*" Function,+,ibutton_protocols_save,_Bool,"iButtonProtocols*, const iButtonKey*, const char*" Function,+,ibutton_protocols_write_blank,_Bool,"iButtonProtocols*, iButtonKey*" Function,+,ibutton_protocols_write_copy,_Bool,"iButtonProtocols*, iButtonKey*" From 94c2d7a4e3d577d57531c8fb8c1766bc66080227 Mon Sep 17 00:00:00 2001 From: KRukus9 <65518085+KRukus9@users.noreply.github.com> Date: Wed, 17 Apr 2024 16:12:02 -0700 Subject: [PATCH 125/129] Update tv.ir (#3584) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../infrared/resources/infrared/assets/tv.ir | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/applications/main/infrared/resources/infrared/assets/tv.ir b/applications/main/infrared/resources/infrared/assets/tv.ir index 1159f2f04..5eab4c65c 100644 --- a/applications/main/infrared/resources/infrared/assets/tv.ir +++ b/applications/main/infrared/resources/infrared/assets/tv.ir @@ -1827,4 +1827,43 @@ name: Mute type: parsed protocol: NEC address: 01 00 00 00 -command: 17 00 00 00 \ No newline at end of file +command: 17 00 00 00 +# +# Visio TV +# +name: Power +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 08 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 02 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 03 00 00 00 +# +name: Ch_next +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 00 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 01 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 09 00 00 00 +# From 00970a4b12e1cafaeebcf6f4c8b2f89dbd774f4a Mon Sep 17 00:00:00 2001 From: MMX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 18 Apr 2024 03:34:33 +0300 Subject: [PATCH 126/129] Allow setting view_dispatcher callbacks to NULL again (#3580) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/services/gui/view_dispatcher.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/applications/services/gui/view_dispatcher.c b/applications/services/gui/view_dispatcher.c index 222ba817f..d4c2f61e7 100644 --- a/applications/services/gui/view_dispatcher.c +++ b/applications/services/gui/view_dispatcher.c @@ -51,7 +51,6 @@ void view_dispatcher_set_navigation_event_callback( ViewDispatcher* view_dispatcher, ViewDispatcherNavigationEventCallback callback) { furi_check(view_dispatcher); - furi_check(callback); view_dispatcher->navigation_event_callback = callback; } @@ -59,7 +58,6 @@ void view_dispatcher_set_custom_event_callback( ViewDispatcher* view_dispatcher, ViewDispatcherCustomEventCallback callback) { furi_check(view_dispatcher); - furi_check(callback); view_dispatcher->custom_event_callback = callback; } @@ -68,7 +66,6 @@ void view_dispatcher_set_tick_event_callback( ViewDispatcherTickEventCallback callback, uint32_t tick_period) { furi_check(view_dispatcher); - furi_check(callback); view_dispatcher->tick_event_callback = callback; view_dispatcher->tick_period = tick_period; } From be43e498698d623d6e9ddbcb36760a049b331038 Mon Sep 17 00:00:00 2001 From: gornekich Date: Thu, 18 Apr 2024 01:56:39 +0100 Subject: [PATCH 127/129] nfc app: fix false positive verification in bip plugin (#3595) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/main/nfc/plugins/supported_cards/bip.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/bip.c b/applications/main/nfc/plugins/supported_cards/bip.c index 548afed71..8b76c2d07 100644 --- a/applications/main/nfc/plugins/supported_cards/bip.c +++ b/applications/main/nfc/plugins/supported_cards/bip.c @@ -61,7 +61,7 @@ bool bip_verify(Nfc* nfc) { MfClassicError error = mf_classic_poller_sync_auth(nfc, block_num, &key_a_0, MfClassicKeyTypeA, &auth_ctx); - if(error == MfClassicErrorNotPresent) { + if(error != MfClassicErrorNone) { FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error); verified = false; } @@ -81,7 +81,8 @@ static bool bip_read(Nfc* nfc, NfcDevice* device) { do { MfClassicType type = MfClassicType1k; MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); - if(error == MfClassicErrorNotPresent) { + if(error != MfClassicErrorNone) break; + if(type != MfClassicType1k) { FURI_LOG_W(TAG, "Card not MIFARE Classic 1k"); break; } From 12112e70bc7065669ff98fc620c22b92bd848be2 Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+Willy-JL@users.noreply.github.com> Date: Thu, 18 Apr 2024 02:26:58 +0100 Subject: [PATCH 128/129] BLE: Add GapPairingNone support (#3596) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * BLE: Add GapPairingNone support * FuriHal: cleanup naming in ble gap, remove useless config options Co-authored-by: あく --- targets/f7/ble_glue/app_conf.h | 6 ------ targets/f7/ble_glue/gap.c | 13 +++++++++++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/targets/f7/ble_glue/app_conf.h b/targets/f7/ble_glue/app_conf.h index fbf6d0291..d0e089eb1 100644 --- a/targets/f7/ble_glue/app_conf.h +++ b/targets/f7/ble_glue/app_conf.h @@ -9,7 +9,6 @@ /** * Define IO Authentication */ -#define CFG_USED_FIXED_PIN USE_FIXED_PIN_FOR_PAIRING_FORBIDDEN #define CFG_ENCRYPTION_KEY_SIZE_MAX (16) #define CFG_ENCRYPTION_KEY_SIZE_MIN (8) @@ -18,11 +17,6 @@ */ #define CFG_IO_CAPABILITY IO_CAP_DISPLAY_YES_NO -/** - * Define MITM modes - */ -#define CFG_MITM_PROTECTION MITM_PROTECTION_REQUIRED - /** * Define Secure Connections Support */ diff --git a/targets/f7/ble_glue/gap.c b/targets/f7/ble_glue/gap.c index 8f4299c70..2e4a74a9e 100644 --- a/targets/f7/ble_glue/gap.c +++ b/targets/f7/ble_glue/gap.c @@ -348,22 +348,31 @@ static void gap_init_svc(Gap* gap) { // Set default PHY hci_le_set_default_phy(ALL_PHYS_PREFERENCE, TX_2M_PREFERRED, RX_2M_PREFERRED); // Set I/O capability + uint8_t auth_req_mitm_mode = MITM_PROTECTION_REQUIRED; + uint8_t auth_req_use_fixed_pin = USE_FIXED_PIN_FOR_PAIRING_FORBIDDEN; bool keypress_supported = false; if(gap->config->pairing_method == GapPairingPinCodeShow) { aci_gap_set_io_capability(IO_CAP_DISPLAY_ONLY); } else if(gap->config->pairing_method == GapPairingPinCodeVerifyYesNo) { aci_gap_set_io_capability(IO_CAP_DISPLAY_YES_NO); keypress_supported = true; + } else if(gap->config->pairing_method == GapPairingNone) { + // "Just works" pairing method (iOS accepts it, it seems Android and Linux don't) + auth_req_mitm_mode = MITM_PROTECTION_NOT_REQUIRED; + auth_req_use_fixed_pin = USE_FIXED_PIN_FOR_PAIRING_ALLOWED; + // If "just works" isn't supported, we want the numeric comparaison method + aci_gap_set_io_capability(IO_CAP_DISPLAY_YES_NO); + keypress_supported = true; } // Setup authentication aci_gap_set_authentication_requirement( gap->config->bonding_mode, - CFG_MITM_PROTECTION, + auth_req_mitm_mode, CFG_SC_SUPPORT, keypress_supported, CFG_ENCRYPTION_KEY_SIZE_MIN, CFG_ENCRYPTION_KEY_SIZE_MAX, - CFG_USED_FIXED_PIN, + auth_req_use_fixed_pin, 0, CFG_IDENTITY_ADDRESS); // Configure whitelist From 2a98739fe3b09b18f941094b52b93ade658b80d0 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Thu, 18 Apr 2024 03:09:15 +0100 Subject: [PATCH 129/129] Update symbols --- targets/f7/api_symbols.csv | 1 + 1 file changed, 1 insertion(+) diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 8c7128306..1fb2632b2 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -3808,6 +3808,7 @@ Variable,+,I_DolphinMafia_119x62,Icon, Variable,+,I_DolphinReadingSuccess_59x63,Icon, Variable,+,I_DolphinSaved_92x58,Icon, Variable,+,I_DolphinSuccess_91x55,Icon, +Variable,+,I_DolphinWait_59x54,Icon, Variable,+,I_DolphinWait_61x59,Icon, Variable,+,I_Drive_112x35,Icon, Variable,+,I_Dynamic_9x7,Icon,