From 772c944163b3d82b2aebba1b3e497080b6c74578 Mon Sep 17 00:00:00 2001 From: Alexey Zakharov <110112149+alexeyzakh@users.noreply.github.com> Date: Thu, 6 Nov 2025 13:21:44 +0000 Subject: [PATCH 01/10] Devboard docs update (#4301) Wi-Fi devboard pinout added, updated devboard hardware image, fixed the schematics link, and corrected typos. Co-authored-by: hedger --- .../Firmware update on Developer Board.md | 42 ++++++++++++------- documentation/doxygen/dev_board.dox | 8 +++- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/documentation/devboard/Firmware update on Developer Board.md b/documentation/devboard/Firmware update on Developer Board.md index 9cc9a8ea0..2ffe01dab 100644 --- a/documentation/devboard/Firmware update on Developer Board.md +++ b/documentation/devboard/Firmware update on Developer Board.md @@ -13,21 +13,31 @@ It's important to regularly update your Developer Board to ensure that you have **On Linux & macOS:** -Run the following command in the Terminal: - -``` -python3 -m pip install --upgrade ufbt -``` +1. Open a terminal. +2. Install `pipx` by following the instructions on the [official website](https://pipx.pypa.io/stable/installation/). +3. Restart the terminal. +4. Install `ufbt`: + ``` + pipx install ufbt + ``` **On Windows:** -1. Download the latest version of Python on -2. Run the following command in the PowerShell - +1. Download the latest version of Python on [the official website](https://www.python.org/downloads/windows/) and install it. +2. Open PowerShell. +3. Install `pipx`: ``` - py -m pip install --upgrade ufbt + py -m pip install --user pipx + ``` +4. Add `pipx` to PATH: + ``` + py -m pipx ensurepath + ``` +5. Restart PowerShell. +6. Install `ufbt`: + ``` + pipx install ufbt ``` - *** ## Step 2. Connect the Devboard to PC @@ -49,12 +59,12 @@ To update the firmware, you need to switch your Developer Board to Bootloader mo 3.1. Press and hold the **BOOT** button. - 3.2. Press the **RESET** button while holding the **BOOT** button. + 3.2. Press and release the **RESET** button while holding the **BOOT** button. 3.3. Release the **BOOT** button. - \image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_reboot_to_bootloader.png width=700 + \image html https://cdn.flipper.net/Flipper_Zero_devboard_bootloader.jpg width=700 -4. Repeat **Step 1** and view the name of your Developer Board that appeared in the list. +4. Repeat the first command above (listing serial devices) and view the name of your Developer Board that appeared in the list. *** @@ -66,7 +76,7 @@ To update the firmware, you need to switch your Developer Board to Bootloader mo python3 -m ufbt devboard_flash ``` -**On Windows:** Run the following command in the PowerShell: +**On Windows:** Run the following command in PowerShell: ``` py -m ufbt devboard_flash @@ -74,7 +84,7 @@ py -m ufbt devboard_flash You should see the following message: `WiFi board flashed successfully`. -### If flashing failed +### If flashing fails Occasionally, you might get an error message during the flashing process, such as: @@ -90,7 +100,7 @@ FileNotFoundError: [Errno 2] No such file or directory: '/dev/cu.usbmodem01' To fix it, try doing the following: -- Disconnect the Developer Board from your computer, then reconnect it. After that, switch your Developer Board to Bootloader mode once again, as described in +- Disconnect the Developer Board from your computer, then reconnect it. After that, switch your Developer Board to Bootloader mode once again, as described in Step 2. - Use a different USB port on your computer. diff --git a/documentation/doxygen/dev_board.dox b/documentation/doxygen/dev_board.dox index 8d5a2bf35..d90c6e7b0 100644 --- a/documentation/doxygen/dev_board.dox +++ b/documentation/doxygen/dev_board.dox @@ -26,11 +26,15 @@ Check out these guides to get started with the Devboard: The Developer Board is equipped with an [ESP32-S2-WROVER](https://www.espressif.com/en/products/socs/esp32-s2) module, which includes built-in Wi-Fi capabilities. It also offers GPIO pins for easy connectivity to various targets. Additionally, the Developer Board features a USB Type-C connector for data transfer and power supply. For user interaction, the Developer Board has tactile switches. -\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_developer_board_hardware_CDN.jpg width=700 +\image html https://cdn.flipper.net/Flipper_Zero_devboard_main_parts.jpg width=700 + +## Pinout + +\image html https://cdn.flipper.net/Flipper_Zero_wifi_devboard_pinout_updated.png width=700 ## Additional resources -To learn more about the Wi-Fi Developer Board hardware, visit [Schematics in Flipper Docs](https://docs.flipperzero.one/development/hardware/wifi-debugger-module/schematics). +To learn more about the Wi-Fi Developer Board hardware, visit [Schematics in Flipper Docs](https://docs.flipper.net/zero/development/hardware/devboard-schematics). For additional information about Flipper Zero GPIO pins, visit [GPIO & modules in Flipper Docs](https://docs.flipperzero.one/gpio-and-modules). From 67b906e6ba8a09aee1cf6a0230e5879aa1dd0a7d Mon Sep 17 00:00:00 2001 From: an5t <8015386+an5t@users.noreply.github.com> Date: Thu, 6 Nov 2025 18:31:17 +0300 Subject: [PATCH 02/10] github: fail submit_sdk on catalog api error (#4294) Co-authored-by: hedger --- .github/actions/submit_sdk/action.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/submit_sdk/action.yml b/.github/actions/submit_sdk/action.yml index b515b5285..2c556d296 100644 --- a/.github/actions/submit_sdk/action.yml +++ b/.github/actions/submit_sdk/action.yml @@ -49,7 +49,7 @@ runs: - name: Submit SDK shell: bash run: | - curl -sX 'GET' \ + curl --fail -sX 'GET' \ '${{ inputs.catalog-url }}/api/v0/0/sdk?length=500' \ -H 'Accept: application/json' > sdk_versions.json if jq -r -e ".[] | select((.api == \"${{ inputs.firmware-api }}\") and .target == \"${{ inputs.firmware-target }}\")" sdk_versions.json > found_sdk.json ; then @@ -61,7 +61,7 @@ runs: if ! echo "${{ inputs.firmware-version }}" | grep -q -- "-rc" ; then SDK_ID=$(jq -r ._id found_sdk.json) echo "Marking SDK $SDK_ID as released" - curl -X 'POST' \ + curl --fail-with-body -X 'POST' \ "${{ inputs.catalog-url }}/api/v0/0/sdk/${SDK_ID}/release" \ -H 'Accept: application/json' \ -H 'Authorization: Bearer ${{ inputs.catalog-api-token }}' \ @@ -69,7 +69,7 @@ runs: fi else echo "API version ${{ inputs.firmware-api }} doesn't exist in catalog, adding" - curl -X 'POST' \ + curl --fail-with-body -X 'POST' \ '${{ inputs.catalog-url }}/api/v0/0/sdk' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer ${{ inputs.catalog-api-token }}' \ From 6a5ae6cc0dee6ade5803905ec75d831be5ee5102 Mon Sep 17 00:00:00 2001 From: MMX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Nov 2025 19:23:59 +0300 Subject: [PATCH 03/10] Infrared Universal remote DBs unit test & move infrared_signal / infrared_brute_force into lib (#4284) * make infrared db unit tests * fix the tests, no assets are in asset folder oh no * fix formate * ship ir app along with unit_test pkg * libify ir signal and bruteforce parts * small cleanup * api: removed infrared methods (you can link with the lib if needed), unit_tests, infrared: adjusted to link with ir lib; api: added `__aeabi_f2d` --------- Co-authored-by: hedger Co-authored-by: hedger --- applications/debug/unit_tests/application.fam | 1 + .../unit_tests/tests/infrared/infrared_test.c | 76 +++++++++++++++++++ applications/main/infrared/application.fam | 8 +- applications/main/infrared/infrared_app.h | 4 +- applications/main/infrared/infrared_app_i.h | 2 +- applications/main/infrared/infrared_cli.c | 4 +- applications/main/infrared/infrared_remote.h | 2 +- fbt_options.py | 1 + lib/infrared/SConscript | 4 + .../infrared/signal}/infrared_brute_force.c | 0 .../infrared/signal}/infrared_brute_force.h | 8 ++ .../infrared/signal}/infrared_error_code.h | 8 ++ .../infrared/signal}/infrared_signal.c | 2 +- .../infrared/signal}/infrared_signal.h | 10 ++- targets/f18/api_symbols.csv | 3 +- targets/f7/api_symbols.csv | 34 ++++++++- .../f7/platform_specific/intrinsic_export.h | 1 + 17 files changed, 150 insertions(+), 18 deletions(-) rename {applications/main/infrared => lib/infrared/signal}/infrared_brute_force.c (100%) rename {applications/main/infrared => lib/infrared/signal}/infrared_brute_force.h (98%) rename {applications/main/infrared => lib/infrared/signal}/infrared_error_code.h (96%) rename {applications/main/infrared => lib/infrared/signal}/infrared_signal.c (99%) rename {applications/main/infrared => lib/infrared/signal}/infrared_signal.h (98%) diff --git a/applications/debug/unit_tests/application.fam b/applications/debug/unit_tests/application.fam index 252eb57c5..3190db3f0 100644 --- a/applications/debug/unit_tests/application.fam +++ b/applications/debug/unit_tests/application.fam @@ -131,6 +131,7 @@ App( apptype=FlipperAppType.PLUGIN, entry_point="get_api", requires=["unit_tests"], + fap_libs=["infrared"], ) App( diff --git a/applications/debug/unit_tests/tests/infrared/infrared_test.c b/applications/debug/unit_tests/tests/infrared/infrared_test.c index e64b630b2..9ca78c2f4 100644 --- a/applications/debug/unit_tests/tests/infrared/infrared_test.c +++ b/applications/debug/unit_tests/tests/infrared/infrared_test.c @@ -2,6 +2,7 @@ #include #include #include +#include #include "../test.h" // IWYU pragma: keep #define IR_TEST_FILES_DIR EXT_PATH("unit_tests/infrared/") @@ -13,6 +14,7 @@ typedef struct { InfraredEncoderHandler* encoder_handler; FuriString* file_path; FlipperFormat* ff; + InfraredBruteForce* brutedb; } InfraredTest; static InfraredTest* test; @@ -24,12 +26,14 @@ static void infrared_test_alloc(void) { test->encoder_handler = infrared_alloc_encoder(); test->ff = flipper_format_buffered_file_alloc(storage); test->file_path = furi_string_alloc(); + test->brutedb = infrared_brute_force_alloc(); } static void infrared_test_free(void) { furi_check(test); infrared_free_decoder(test->decoder_handler); infrared_free_encoder(test->encoder_handler); + infrared_brute_force_free(test->brutedb); flipper_format_free(test->ff); furi_string_free(test->file_path); furi_record_close(RECORD_STORAGE); @@ -523,6 +527,74 @@ MU_TEST(infrared_test_encoder_decoder_all) { infrared_test_run_encoder_decoder(InfraredProtocolPioneer, 1); } +MU_TEST(infrared_test_ac_database) { + infrared_brute_force_set_db_filename(test->brutedb, EXT_PATH("infrared/assets/ac.ir")); + uint32_t i = 0; + infrared_brute_force_add_record(test->brutedb, i++, "Off"); + infrared_brute_force_add_record(test->brutedb, i++, "Dh"); + infrared_brute_force_add_record(test->brutedb, i++, "Cool_hi"); + infrared_brute_force_add_record(test->brutedb, i++, "Heat_hi"); + infrared_brute_force_add_record(test->brutedb, i++, "Cool_lo"); + infrared_brute_force_add_record(test->brutedb, i++, "Heat_lo"); + + mu_assert( + infrared_brute_force_calculate_messages(test->brutedb) == InfraredErrorCodeNone, + "universal ac database is invalid"); + + infrared_brute_force_reset(test->brutedb); +} + +MU_TEST(infrared_test_audio_database) { + infrared_brute_force_set_db_filename(test->brutedb, EXT_PATH("infrared/assets/audio.ir")); + uint32_t i = 0; + infrared_brute_force_add_record(test->brutedb, i++, "Power"); + infrared_brute_force_add_record(test->brutedb, i++, "Mute"); + infrared_brute_force_add_record(test->brutedb, i++, "Play"); + infrared_brute_force_add_record(test->brutedb, i++, "Pause"); + infrared_brute_force_add_record(test->brutedb, i++, "Prev"); + infrared_brute_force_add_record(test->brutedb, i++, "Next"); + infrared_brute_force_add_record(test->brutedb, i++, "Vol_dn"); + infrared_brute_force_add_record(test->brutedb, i++, "Vol_up"); + + mu_assert( + infrared_brute_force_calculate_messages(test->brutedb) == InfraredErrorCodeNone, + "universal audio database is invalid"); + + infrared_brute_force_reset(test->brutedb); +} + +MU_TEST(infrared_test_projector_database) { + infrared_brute_force_set_db_filename(test->brutedb, EXT_PATH("infrared/assets/projector.ir")); + uint32_t i = 0; + infrared_brute_force_add_record(test->brutedb, i++, "Power"); + infrared_brute_force_add_record(test->brutedb, i++, "Mute"); + infrared_brute_force_add_record(test->brutedb, i++, "Vol_up"); + infrared_brute_force_add_record(test->brutedb, i++, "Vol_dn"); + + mu_assert( + infrared_brute_force_calculate_messages(test->brutedb) == InfraredErrorCodeNone, + "universal projector database is invalid"); + + infrared_brute_force_reset(test->brutedb); +} + +MU_TEST(infrared_test_tv_database) { + infrared_brute_force_set_db_filename(test->brutedb, EXT_PATH("infrared/assets/tv.ir")); + uint32_t i = 0; + infrared_brute_force_add_record(test->brutedb, i++, "Power"); + infrared_brute_force_add_record(test->brutedb, i++, "Mute"); + infrared_brute_force_add_record(test->brutedb, i++, "Vol_up"); + infrared_brute_force_add_record(test->brutedb, i++, "Ch_next"); + infrared_brute_force_add_record(test->brutedb, i++, "Vol_dn"); + infrared_brute_force_add_record(test->brutedb, i++, "Ch_prev"); + + mu_assert( + infrared_brute_force_calculate_messages(test->brutedb) == InfraredErrorCodeNone, + "universal tv database is invalid"); + + infrared_brute_force_reset(test->brutedb); +} + MU_TEST_SUITE(infrared_test) { MU_SUITE_CONFIGURE(&infrared_test_alloc, &infrared_test_free); @@ -543,6 +615,10 @@ MU_TEST_SUITE(infrared_test) { MU_RUN_TEST(infrared_test_decoder_pioneer); MU_RUN_TEST(infrared_test_decoder_mixed); MU_RUN_TEST(infrared_test_encoder_decoder_all); + MU_RUN_TEST(infrared_test_ac_database); + MU_RUN_TEST(infrared_test_audio_database); + MU_RUN_TEST(infrared_test_projector_database); + MU_RUN_TEST(infrared_test_tv_database); } int run_minunit_test_infrared(void) { diff --git a/applications/main/infrared/application.fam b/applications/main/infrared/application.fam index 79b3fdbfa..3ed928ad8 100644 --- a/applications/main/infrared/application.fam +++ b/applications/main/infrared/application.fam @@ -9,7 +9,7 @@ App( order=40, sources=["*.c", "!infrared_cli.c"], resources="resources", - fap_libs=["assets"], + fap_libs=["assets", "infrared"], fap_icon="icon.png", fap_category="Infrared", ) @@ -20,9 +20,5 @@ App( apptype=FlipperAppType.PLUGIN, entry_point="cli_ir_ep", requires=["cli"], - sources=[ - "infrared_cli.c", - "infrared_brute_force.c", - "infrared_signal.c", - ], + sources=["infrared_cli.c"], ) diff --git a/applications/main/infrared/infrared_app.h b/applications/main/infrared/infrared_app.h index a6f87402a..ef41f8f79 100644 --- a/applications/main/infrared/infrared_app.h +++ b/applications/main/infrared/infrared_app.h @@ -3,9 +3,7 @@ * @brief Infrared application - start here. * * @see infrared_app_i.h for the main application data structure and functions. - * @see infrared_signal.h for the infrared signal library - loading, storing and transmitting signals. - * @see infrared_remote.hl for the infrared remote library - loading, storing and manipulating remotes. - * @see infrared_brute_force.h for the infrared brute force - loading and transmitting multiple signals. + * @see infrared_remote.h for the infrared remote library - loading, storing and manipulating remotes */ #pragma once diff --git a/applications/main/infrared/infrared_app_i.h b/applications/main/infrared/infrared_app_i.h index 75d8502f2..caed2d601 100644 --- a/applications/main/infrared/infrared_app_i.h +++ b/applications/main/infrared/infrared_app_i.h @@ -31,7 +31,7 @@ #include "infrared_app.h" #include "infrared_remote.h" -#include "infrared_brute_force.h" +#include #include "infrared_custom_event.h" #include "scenes/infrared_scene.h" diff --git a/applications/main/infrared/infrared_cli.c b/applications/main/infrared/infrared_cli.c index eb13bcd79..b953daad6 100644 --- a/applications/main/infrared/infrared_cli.c +++ b/applications/main/infrared/infrared_cli.c @@ -8,8 +8,8 @@ #include #include -#include "infrared_signal.h" -#include "infrared_brute_force.h" +#include +#include #define INFRARED_CLI_BUF_SIZE (10U) #define INFRARED_CLI_FILE_NAME_SIZE (256U) diff --git a/applications/main/infrared/infrared_remote.h b/applications/main/infrared/infrared_remote.h index 14416cd65..65f00100f 100644 --- a/applications/main/infrared/infrared_remote.h +++ b/applications/main/infrared/infrared_remote.h @@ -11,7 +11,7 @@ */ #pragma once -#include "infrared_signal.h" +#include /** * @brief InfraredRemote opaque type declaration. diff --git a/fbt_options.py b/fbt_options.py index 191c4ef1a..a801c1c48 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -76,6 +76,7 @@ FIRMWARE_APPS = { "radio_device_cc1101_ext", "unit_tests", "js_app", + "infrared", "archive", ], } diff --git a/lib/infrared/SConscript b/lib/infrared/SConscript index a32248a64..aa4b52776 100644 --- a/lib/infrared/SConscript +++ b/lib/infrared/SConscript @@ -4,11 +4,15 @@ env.Append( CPPPATH=[ "#/lib/infrared/encoder_decoder", "#/lib/infrared/worker", + "#/lib/infrared/signal", ], SDK_HEADERS=[ File("encoder_decoder/infrared.h"), File("worker/infrared_worker.h"), File("worker/infrared_transmit.h"), + File("signal/infrared_error_code.h"), + File("signal/infrared_signal.h"), + File("signal/infrared_brute_force.h"), ], LINT_SOURCES=[ Dir("."), diff --git a/applications/main/infrared/infrared_brute_force.c b/lib/infrared/signal/infrared_brute_force.c similarity index 100% rename from applications/main/infrared/infrared_brute_force.c rename to lib/infrared/signal/infrared_brute_force.c diff --git a/applications/main/infrared/infrared_brute_force.h b/lib/infrared/signal/infrared_brute_force.h similarity index 98% rename from applications/main/infrared/infrared_brute_force.h rename to lib/infrared/signal/infrared_brute_force.h index 2c75d37f2..f6e31af98 100644 --- a/applications/main/infrared/infrared_brute_force.h +++ b/lib/infrared/signal/infrared_brute_force.h @@ -12,6 +12,10 @@ #include #include "infrared_error_code.h" +#ifdef __cplusplus +extern "C" { +#endif + /** * @brief InfraredBruteForce opaque type declaration. */ @@ -107,3 +111,7 @@ void infrared_brute_force_add_record( * @param[in,out] brute_force pointer to the instance to be reset. */ void infrared_brute_force_reset(InfraredBruteForce* brute_force); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/infrared/infrared_error_code.h b/lib/infrared/signal/infrared_error_code.h similarity index 96% rename from applications/main/infrared/infrared_error_code.h rename to lib/infrared/signal/infrared_error_code.h index 841721b17..7aa619c4a 100644 --- a/applications/main/infrared/infrared_error_code.h +++ b/lib/infrared/signal/infrared_error_code.h @@ -1,5 +1,9 @@ #pragma once +#ifdef __cplusplus +extern "C" { +#endif + typedef enum { InfraredErrorCodeNone = 0, InfraredErrorCodeFileOperationFailed = 0x800000, @@ -43,3 +47,7 @@ typedef enum { #define INFRARED_ERROR_PRESENT(error) (INFRARED_ERROR_GET_CODE(error) != InfraredErrorCodeNone) #define INFRARED_ERROR_CHECK(error, test_code) (INFRARED_ERROR_GET_CODE(error) == (test_code)) + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/infrared/infrared_signal.c b/lib/infrared/signal/infrared_signal.c similarity index 99% rename from applications/main/infrared/infrared_signal.c rename to lib/infrared/signal/infrared_signal.c index 75643f1d3..436de4e29 100644 --- a/applications/main/infrared/infrared_signal.c +++ b/lib/infrared/signal/infrared_signal.c @@ -85,7 +85,7 @@ static bool infrared_signal_is_raw_valid(const InfraredRawSignal* raw) { raw->frequency); return false; - } else if((raw->duty_cycle <= 0) || (raw->duty_cycle > 1)) { + } else if((raw->duty_cycle <= 0) || (raw->duty_cycle > 1.f)) { FURI_LOG_E(TAG, "Duty cycle is out of range (0 - 1): %f", (double)raw->duty_cycle); return false; diff --git a/applications/main/infrared/infrared_signal.h b/lib/infrared/signal/infrared_signal.h similarity index 98% rename from applications/main/infrared/infrared_signal.h rename to lib/infrared/signal/infrared_signal.h index 3fa7768b3..96afd8c2e 100644 --- a/applications/main/infrared/infrared_signal.h +++ b/lib/infrared/signal/infrared_signal.h @@ -10,7 +10,11 @@ #include "infrared_error_code.h" #include -#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /** * @brief InfraredSignal opaque type declaration. @@ -218,3 +222,7 @@ InfraredErrorCode * @param[in] signal pointer to the instance holding the signal to be transmitted. */ void infrared_signal_transmit(const InfraredSignal* signal); + +#ifdef __cplusplus +} +#endif diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 27b65e202..f66fcb9e4 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,87.0,, +Version,+,87.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -339,6 +339,7 @@ Function,-,Osal_MemSet,void*,"void*, int, unsigned int" Function,-,SystemCoreClockUpdate,void, Function,-,SystemInit,void, Function,-,_Exit,void,int +Function,+,__aeabi_f2d,double,float Function,+,__aeabi_uldivmod,void*,"uint64_t, uint64_t" Function,-,__assert,void,"const char*, int, const char*" Function,+,__assert_func,void,"const char*, int, const char*, const char*" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 15a3d360b..9963b3515 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,87.0,, +Version,+,87.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -63,6 +63,9 @@ Header,+,lib/ibutton/ibutton_protocols.h,, Header,+,lib/ibutton/ibutton_worker.h,, Header,+,lib/ieee754_parse_wrap/wrappers.h,, Header,+,lib/infrared/encoder_decoder/infrared.h,, +Header,+,lib/infrared/signal/infrared_brute_force.h,, +Header,+,lib/infrared/signal/infrared_error_code.h,, +Header,+,lib/infrared/signal/infrared_signal.h,, Header,+,lib/infrared/worker/infrared_transmit.h,, Header,+,lib/infrared/worker/infrared_worker.h,, Header,+,lib/lfrfid/lfrfid_dict_file.h,, @@ -418,6 +421,7 @@ Function,-,Osal_MemSet,void*,"void*, int, unsigned int" Function,-,SystemCoreClockUpdate,void, Function,-,SystemInit,void, Function,-,_Exit,void,int +Function,+,__aeabi_f2d,double,float Function,+,__aeabi_uldivmod,void*,"uint64_t, uint64_t" Function,-,__assert,void,"const char*, int, const char*" Function,+,__assert_func,void,"const char*, int, const char*, const char*" @@ -625,8 +629,8 @@ Function,-,arc4random_uniform,__uint32_t,__uint32_t Function,+,args_char_to_hex,_Bool,"char, char, uint8_t*" Function,+,args_get_first_word_length,size_t,FuriString* Function,+,args_length,size_t,FuriString* -Function,+,args_read_float_and_trim,_Bool,"FuriString*, float*" Function,+,args_read_duration,_Bool,"FuriString*, uint32_t*, const char*" +Function,+,args_read_float_and_trim,_Bool,"FuriString*, float*" Function,+,args_read_hex_bytes,_Bool,"FuriString*, uint8_t*, size_t" Function,+,args_read_int_and_trim,_Bool,"FuriString*, int*" Function,+,args_read_probably_quoted_string_and_trim,_Bool,"FuriString*, FuriString*" @@ -2065,6 +2069,16 @@ Function,-,infinity,double, Function,-,infinityf,float, Function,+,infrared_alloc_decoder,InfraredDecoderHandler*, Function,+,infrared_alloc_encoder,InfraredEncoderHandler*, +Function,-,infrared_brute_force_add_record,void,"InfraredBruteForce*, uint32_t, const char*" +Function,-,infrared_brute_force_alloc,InfraredBruteForce*, +Function,-,infrared_brute_force_calculate_messages,InfraredErrorCode,InfraredBruteForce* +Function,-,infrared_brute_force_free,void,InfraredBruteForce* +Function,-,infrared_brute_force_is_started,_Bool,const InfraredBruteForce* +Function,-,infrared_brute_force_reset,void,InfraredBruteForce* +Function,-,infrared_brute_force_send,_Bool,"InfraredBruteForce*, uint32_t" +Function,-,infrared_brute_force_set_db_filename,void,"InfraredBruteForce*, const char*" +Function,-,infrared_brute_force_start,_Bool,"InfraredBruteForce*, uint32_t, uint32_t*" +Function,-,infrared_brute_force_stop,void,InfraredBruteForce* Function,+,infrared_check_decoder_ready,const InfraredMessage*,InfraredDecoderHandler* Function,+,infrared_decode,const InfraredMessage*,"InfraredDecoderHandler*, _Bool, uint32_t" Function,+,infrared_encode,InfraredStatus,"InfraredEncoderHandler*, uint32_t*, _Bool*" @@ -2083,6 +2097,22 @@ Function,+,infrared_reset_encoder,void,"InfraredEncoderHandler*, const InfraredM Function,+,infrared_send,void,"const InfraredMessage*, int" Function,+,infrared_send_raw,void,"const uint32_t[], uint32_t, _Bool" Function,+,infrared_send_raw_ext,void,"const uint32_t[], uint32_t, _Bool, uint32_t, float" +Function,-,infrared_signal_alloc,InfraredSignal*, +Function,-,infrared_signal_free,void,InfraredSignal* +Function,-,infrared_signal_get_message,const InfraredMessage*,const InfraredSignal* +Function,-,infrared_signal_get_raw_signal,const InfraredRawSignal*,const InfraredSignal* +Function,-,infrared_signal_is_raw,_Bool,const InfraredSignal* +Function,-,infrared_signal_is_valid,_Bool,const InfraredSignal* +Function,-,infrared_signal_read,InfraredErrorCode,"InfraredSignal*, FlipperFormat*, FuriString*" +Function,-,infrared_signal_read_body,InfraredErrorCode,"InfraredSignal*, FlipperFormat*" +Function,-,infrared_signal_read_name,InfraredErrorCode,"FlipperFormat*, FuriString*" +Function,-,infrared_signal_save,InfraredErrorCode,"const InfraredSignal*, FlipperFormat*, const char*" +Function,-,infrared_signal_search_by_index_and_read,InfraredErrorCode,"InfraredSignal*, FlipperFormat*, size_t" +Function,-,infrared_signal_search_by_name_and_read,InfraredErrorCode,"InfraredSignal*, FlipperFormat*, const char*" +Function,-,infrared_signal_set_message,void,"InfraredSignal*, const InfraredMessage*" +Function,-,infrared_signal_set_raw_signal,void,"InfraredSignal*, const uint32_t*, size_t, uint32_t, float" +Function,-,infrared_signal_set_signal,void,"InfraredSignal*, const InfraredSignal*" +Function,-,infrared_signal_transmit,void,const InfraredSignal* Function,+,infrared_worker_alloc,InfraredWorker*, Function,+,infrared_worker_free,void,InfraredWorker* Function,+,infrared_worker_get_decoded_signal,const InfraredMessage*,const InfraredWorkerSignal* diff --git a/targets/f7/platform_specific/intrinsic_export.h b/targets/f7/platform_specific/intrinsic_export.h index d3c7be5e0..989bcafb2 100644 --- a/targets/f7/platform_specific/intrinsic_export.h +++ b/targets/f7/platform_specific/intrinsic_export.h @@ -8,6 +8,7 @@ extern "C" { void __clear_cache(void*, void*); void* __aeabi_uldivmod(uint64_t, uint64_t); +double __aeabi_f2d(float); #ifdef __cplusplus } From b54d63037b1764268cf16191d79dadc9e4fcd089 Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 6 Nov 2025 16:40:18 +0000 Subject: [PATCH 04/10] lib: infrared: fixed doxygen declarations to match implementation (#4305) --- lib/infrared/signal/infrared_brute_force.h | 9 +++++++-- lib/infrared/signal/infrared_signal.h | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/infrared/signal/infrared_brute_force.h b/lib/infrared/signal/infrared_brute_force.h index f6e31af98..b48d04533 100644 --- a/lib/infrared/signal/infrared_brute_force.h +++ b/lib/infrared/signal/infrared_brute_force.h @@ -55,11 +55,16 @@ void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const InfraredErrorCode infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force); /** - * @brief Start transmitting signals from a category stored in an InfraredBruteForce's instance dictionary. + * @brief Start transmitting signals from a category stored in the dictionary. + * + * The function locates the category identified by @p index, reports the number of + * records it contains via @p record_count, and prepares the brute-force instance + * to transmit those signals. On failure @p record_count is set to zero. * * @param[in,out] brute_force pointer to the instance to be started. * @param[in] index index of the signal category in the dictionary. - * @returns true on success, false otherwise. + * @param[out] record_count pointer that receives the number of records in the category. + * @returns true if the category is found and the backing database file is opened, false otherwise. */ bool infrared_brute_force_start( InfraredBruteForce* brute_force, diff --git a/lib/infrared/signal/infrared_signal.h b/lib/infrared/signal/infrared_signal.h index 96afd8c2e..93f1f88fe 100644 --- a/lib/infrared/signal/infrared_signal.h +++ b/lib/infrared/signal/infrared_signal.h @@ -163,7 +163,7 @@ InfraredErrorCode infrared_signal_read_name(FlipperFormat* ff, FuriString* name) * Same behaviour as infrared_signal_read(), but only the body is read. * * @param[in,out] ff pointer to the FlipperFormat file instance to read from. - * @param[out] body pointer to the InfraredSignal instance to hold the signal body. Must be properly allocated. + * @param[out] signal pointer to the InfraredSignal instance to hold the signal body. Must be properly allocated. * @returns InfraredErrorCodeNone if a signal body was successfully read, otherwise error code. */ InfraredErrorCode infrared_signal_read_body(InfraredSignal* signal, FlipperFormat* ff); From 6c83a67173858cda38ed66fef0d7c197d5afac2e Mon Sep 17 00:00:00 2001 From: Zinong Li <131403964+zinongli@users.noreply.github.com> Date: Thu, 6 Nov 2025 22:30:02 +0400 Subject: [PATCH 05/10] NFC FeliCa Improvement: Dump All Systems (#4293) * polling system code done * select service done * select system fixes. works fine now * clean up select sys by idx * reading, saving, loading done. render wrong * render first layer works * render fixed * fix Lite Render triage * regression Lite rendering * sync API with 1.40 release * clean up and lint --------- Co-authored-by: hedger --- .../helpers/protocol_support/felica/felica.c | 26 +- .../protocol_support/felica/felica_render.c | 23 +- .../protocol_support/felica/felica_render.h | 3 +- .../main/nfc/scenes/nfc_scene_config.h | 1 + .../nfc/scenes/nfc_scene_felica_more_info.c | 84 +--- .../main/nfc/scenes/nfc_scene_felica_system.c | 114 +++++ lib/nfc/protocols/felica/felica.c | 419 +++++++++--------- lib/nfc/protocols/felica/felica.h | 27 +- lib/nfc/protocols/felica/felica_i.c | 38 ++ lib/nfc/protocols/felica/felica_i.h | 5 + lib/nfc/protocols/felica/felica_poller.c | 91 +++- lib/nfc/protocols/felica/felica_poller_i.c | 32 +- lib/nfc/protocols/felica/felica_poller_i.h | 8 + targets/f7/api_symbols.csv | 2 +- 14 files changed, 557 insertions(+), 316 deletions(-) create mode 100644 applications/main/nfc/scenes/nfc_scene_felica_system.c diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica.c b/applications/main/nfc/helpers/protocol_support/felica/felica.c index 0996ca7a1..1b52b86bd 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica.c +++ b/applications/main/nfc/helpers/protocol_support/felica/felica.c @@ -102,19 +102,21 @@ static void nfc_scene_read_success_on_enter_felica(NfcApp* instance) { 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, "..."); + if(data->workflow_type == FelicaLite) { + 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); } - nfc_render_felica_blocks_count(data, temp_str, false); } felica_auth_reset(instance->felica_auth); 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 1ca992bcd..8773fa1f3 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica_render.c +++ b/applications/main/nfc/helpers/protocol_support/felica/felica_render.c @@ -6,14 +6,10 @@ void nfc_render_felica_blocks_count( bool render_auth_notification) { if(data->workflow_type == FelicaLite) { furi_string_cat_printf(str, "Blocks: %u\n", data->blocks_total); - 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!"); } - } else if(data->workflow_type == FelicaStandard) { - furi_string_cat_printf( - str, "Public blocks Read: %lu", simple_array_get_count(data->public_blocks)); } } @@ -54,11 +50,7 @@ void nfc_render_felica_info( } furi_string_cat_printf(str, "\n"); - furi_string_cat_printf( - str, - "Services found: %lu \nAreas found: %lu\n", - simple_array_get_count(data->services), - simple_array_get_count(data->areas)); + furi_string_cat_printf(str, "Systems found: %lu \n", simple_array_get_count(data->systems)); nfc_render_felica_blocks_count(data, str, true); } @@ -136,9 +128,9 @@ void nfc_more_info_render_felica_lite_dump(const FelicaData* data, FuriString* s nfc_render_felica_block(&data->data.fs.crc_check, str, "CRC_CHCK", 15, 17); } -void nfc_more_info_render_felica_dir(const FelicaData* data, FuriString* str) { - const size_t area_count = simple_array_get_count(data->areas); - const size_t service_count = simple_array_get_count(data->services); +void nfc_more_info_render_felica_dir(const FelicaSystem* system, FuriString* str) { + const size_t area_count = simple_array_get_count(system->areas); + const size_t service_count = simple_array_get_count(system->services); furi_string_cat_printf(str, "\e#Directory Tree:\n"); @@ -150,11 +142,12 @@ void nfc_more_info_render_felica_dir(const FelicaData* data, FuriString* str) { furi_string_cat_printf( str, "::: ... are readable services\n||| ... are locked services\n"); } - felica_write_directory_tree(data, str); + felica_write_directory_tree(system, str); } void nfc_more_info_render_felica_blocks( const FelicaData* data, + const FelicaSystem* system, FuriString* str, const uint16_t service_code_key) { furi_string_cat_printf(str, "\n"); @@ -190,9 +183,9 @@ void nfc_more_info_render_felica_blocks( nfc_render_felica_block(&data->data.fs.crc_check, str, "CRC_CHCK", 15, 17); } else if(data->workflow_type == FelicaStandard) { - uint32_t public_blocks_count = simple_array_get_count(data->public_blocks); + uint32_t public_blocks_count = simple_array_get_count(system->public_blocks); for(size_t i = 0; i < public_blocks_count; i++) { - FelicaPublicBlock* public_block = simple_array_get(data->public_blocks, i); + FelicaPublicBlock* public_block = simple_array_get(system->public_blocks, i); if(public_block->service_code != service_code_key) { continue; // Skip blocks not matching the requested service code } 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 5c7c6d036..e0c270a18 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica_render.h +++ b/applications/main/nfc/helpers/protocol_support/felica/felica_render.h @@ -21,9 +21,10 @@ void nfc_render_felica_idm( NfcProtocolFormatType format_type, FuriString* str); -void nfc_more_info_render_felica_dir(const FelicaData* data, FuriString* str); +void nfc_more_info_render_felica_dir(const FelicaSystem* system, FuriString* str); void nfc_more_info_render_felica_blocks( const FelicaData* data, + const FelicaSystem* system, FuriString* str, const uint16_t service_code_key); diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index 1e3fd506f..a4b0185ee 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -75,5 +75,6 @@ ADD_SCENE(nfc, slix_unlock, SlixUnlock) ADD_SCENE(nfc, slix_unlock_success, SlixUnlockSuccess) ADD_SCENE(nfc, felica_more_info, FelicaMoreInfo) +ADD_SCENE(nfc, felica_system, FelicaSystem) ADD_SCENE(nfc, generate_info, GenerateInfo) diff --git a/applications/main/nfc/scenes/nfc_scene_felica_more_info.c b/applications/main/nfc/scenes/nfc_scene_felica_more_info.c index 7eee3d7b3..1401dcaf7 100644 --- a/applications/main/nfc/scenes/nfc_scene_felica_more_info.c +++ b/applications/main/nfc/scenes/nfc_scene_felica_more_info.c @@ -9,7 +9,6 @@ enum { }; enum SubmenuIndex { - SubmenuIndexDirectory, SubmenuIndexDynamic, // dynamic indices start here }; @@ -21,33 +20,22 @@ void nfc_scene_felica_more_info_on_enter(void* context) { scene_manager_get_scene_state(nfc->scene_manager, NfcSceneFelicaMoreInfo); const FelicaData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolFelica); - submenu_add_item( - submenu, - "Directory", - SubmenuIndexDirectory, - nfc_protocol_support_common_submenu_callback, - nfc); - - FuriString* label = furi_string_alloc(); - switch(data->workflow_type) { case FelicaLite: - furi_string_printf(label, "All blocks"); - submenu_add_item( - submenu, - furi_string_get_cstr(label), - SubmenuIndexDynamic, - nfc_protocol_support_common_submenu_callback, - nfc); + widget_reset(nfc->widget); + FuriString* temp_str = furi_string_alloc(); + nfc_more_info_render_felica_lite_dump(data, temp_str); + widget_add_text_scroll_element(nfc->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + return; break; case FelicaStandard: - for(uint32_t i = 0; i < simple_array_get_count(data->services); ++i) { - const FelicaService* service = simple_array_cget(data->services, i); - bool is_public = (service->attr & FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ) == 1; - if(!is_public) { - continue; - } - furi_string_printf(label, "Readable serv %04X", service->code); + FuriString* label = furi_string_alloc(); + + for(uint32_t i = 0; i < simple_array_get_count(data->systems); ++i) { + const FelicaSystem* system = simple_array_cget(data->systems, i); + furi_string_printf(label, "System %04X", system->system_code); submenu_add_item( submenu, furi_string_get_cstr(label), @@ -55,13 +43,12 @@ void nfc_scene_felica_more_info_on_enter(void* context) { nfc_protocol_support_common_submenu_callback, nfc); } + furi_string_free(label); break; default: break; } - furi_string_free(label); - if(state >= FelicaMoreInfoStateItem) { submenu_set_selected_item( nfc->submenu, state - FelicaMoreInfoStateItem + SubmenuIndexDynamic); @@ -78,49 +65,16 @@ bool nfc_scene_felica_more_info_on_event(void* context, SceneManagerEvent event) const uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneFelicaMoreInfo); - const FelicaData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolFelica); if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubmenuIndexDirectory) { - FuriString* temp_str = furi_string_alloc(); - nfc_more_info_render_felica_dir(data, temp_str); + const uint32_t index = event.event - SubmenuIndexDynamic; - widget_add_text_scroll_element( - nfc->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); - furi_string_free(temp_str); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneFelicaMoreInfo, FelicaMoreInfoStateItem + index); + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneFelicaSystem, index << 4); + scene_manager_next_scene(nfc->scene_manager, NfcSceneFelicaSystem); + consumed = true; - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); - scene_manager_set_scene_state( - nfc->scene_manager, - NfcSceneFelicaMoreInfo, - FelicaMoreInfoStateItem + SubmenuIndexDirectory); - consumed = true; - } else { - const uint16_t service_ind = event.event - 1; // offset the three enums above - - text_box_reset(nfc->text_box); - furi_string_reset(nfc->text_box_store); - - switch(data->workflow_type) { - case FelicaLite: - nfc_more_info_render_felica_lite_dump(data, nfc->text_box_store); - break; - case FelicaStandard: - const FelicaService* service = simple_array_cget(data->services, service_ind); - furi_string_cat_printf(nfc->text_box_store, "Service 0x%04X\n", service->code); - nfc_more_info_render_felica_blocks(data, nfc->text_box_store, service->code); - break; - default: - furi_string_set_str(nfc->text_box_store, "IC type not implemented yet"); - break; - } - text_box_set_font(nfc->text_box, TextBoxFontHex); - text_box_set_text(nfc->text_box, furi_string_get_cstr(nfc->text_box_store)); - view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneFelicaMoreInfo, FelicaMoreInfoStateItem + event.event); - consumed = true; - } } else if(event.type == SceneManagerEventTypeBack) { if(state >= FelicaMoreInfoStateItem) { widget_reset(nfc->widget); diff --git a/applications/main/nfc/scenes/nfc_scene_felica_system.c b/applications/main/nfc/scenes/nfc_scene_felica_system.c new file mode 100644 index 000000000..87ebbbc36 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_felica_system.c @@ -0,0 +1,114 @@ +#include "../nfc_app_i.h" + +#include "../helpers/protocol_support/nfc_protocol_support_gui_common.h" +#include "../helpers/protocol_support/felica/felica_render.h" + +enum SubmenuIndex { + SubmenuIndexDirectory, + SubmenuIndexDynamic, // dynamic indices start here +}; + +static void nfc_scene_felica_system_submenu_callback(void* context, uint32_t index) { + NfcApp* nfc = context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, index); +} + +void nfc_scene_felica_system_on_enter(void* context) { + NfcApp* nfc = context; + Submenu* submenu = nfc->submenu; + submenu_reset(nfc->submenu); + + const uint32_t system_index = + scene_manager_get_scene_state(nfc->scene_manager, NfcSceneFelicaSystem) >> 4; + const FelicaData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolFelica); + + submenu_add_item( + submenu, "Directory", SubmenuIndexDirectory, nfc_scene_felica_system_submenu_callback, nfc); + + FuriString* label = furi_string_alloc(); + + const FelicaSystem* system = simple_array_cget(data->systems, system_index); + for(uint32_t i = 0; i < simple_array_get_count(system->services); ++i) { + const FelicaService* service = simple_array_cget(system->services, i); + bool is_public = (service->attr & FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ) == 1; + if(!is_public) { + continue; + } + furi_string_printf(label, "Readable serv %04X", service->code); + submenu_add_item( + submenu, + furi_string_get_cstr(label), + i + SubmenuIndexDynamic, + nfc_protocol_support_common_submenu_callback, + nfc); + } + + furi_string_free(label); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_felica_system_on_event(void* context, SceneManagerEvent event) { + NfcApp* nfc = context; + bool consumed = false; + + const uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneFelicaSystem); + const FelicaData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolFelica); + + const uint32_t system_index = state >> 4; + const FelicaSystem* system = simple_array_cget(data->systems, system_index); + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventViewExit) { + consumed = scene_manager_previous_scene(nfc->scene_manager); + } else { + if(event.event == SubmenuIndexDirectory) { + FuriString* temp_str = furi_string_alloc(); + nfc_more_info_render_felica_dir(system, temp_str); + + widget_add_text_scroll_element( + nfc->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + } else { + const uint32_t service_ind = + event.event - SubmenuIndexDynamic; // offset the three enums above + + text_box_reset(nfc->text_box); + furi_string_reset(nfc->text_box_store); + + const FelicaService* service = simple_array_cget(system->services, service_ind); + furi_string_cat_printf(nfc->text_box_store, "Service 0x%04X\n", service->code); + nfc_more_info_render_felica_blocks( + data, system, nfc->text_box_store, service->code); + + text_box_set_font(nfc->text_box, TextBoxFontHex); + text_box_set_text(nfc->text_box, furi_string_get_cstr(nfc->text_box_store)); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); + } + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneFelicaSystem, state | 1); + consumed = true; + } + + } else if(event.type == SceneManagerEventTypeBack) { + if(state & 1) { + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneFelicaSystem, state & ~1); + consumed = true; + } + } + + return consumed; +} + +void nfc_scene_felica_system_on_exit(void* context) { + NfcApp* nfc = context; + + // Clear views + widget_reset(nfc->widget); + text_box_reset(nfc->text_box); + furi_string_reset(nfc->text_box_store); + submenu_reset(nfc->submenu); +} diff --git a/lib/nfc/protocols/felica/felica.c b/lib/nfc/protocols/felica/felica.c index 4e0ff7472..2e8d5f92a 100644 --- a/lib/nfc/protocols/felica/felica.c +++ b/lib/nfc/protocols/felica/felica.c @@ -42,26 +42,16 @@ FelicaData* felica_alloc(void) { FelicaData* data = malloc(sizeof(FelicaData)); furi_check(data); - data->services = simple_array_alloc(&felica_service_array_cfg); - data->areas = simple_array_alloc(&felica_area_array_cfg); - data->public_blocks = simple_array_alloc(&felica_public_block_array_cfg); - furi_check(data->services); - furi_check(data->areas); - furi_check(data->public_blocks); + data->systems = simple_array_alloc(&felica_system_array_cfg); + furi_check(data->systems); return data; } void felica_free(FelicaData* data) { furi_check(data); - furi_check(data->services); - simple_array_free(data->services); - - furi_check(data->areas); - simple_array_free(data->areas); - - furi_check(data->public_blocks); - simple_array_free(data->public_blocks); + furi_check(data->systems); + simple_array_free(data->systems); free(data); } @@ -69,16 +59,8 @@ void felica_free(FelicaData* data) { void felica_reset(FelicaData* data) { furi_check(data); - if(data->services) { - simple_array_reset(data->services); - } - - if(data->areas) { - simple_array_reset(data->areas); - } - - if(data->public_blocks) { - simple_array_reset(data->public_blocks); + if(data->systems) { + simple_array_reset(data->systems); } data->blocks_read = 0; @@ -102,9 +84,7 @@ void felica_copy(FelicaData* data, const FelicaData* other) { data->data = other->data; data->workflow_type = other->workflow_type; - simple_array_copy(data->services, other->services); - simple_array_copy(data->areas, other->areas); - simple_array_copy(data->public_blocks, other->public_blocks); + simple_array_copy(data->systems, other->systems); } bool felica_verify(FelicaData* data, const FuriString* device_type) { @@ -175,99 +155,125 @@ bool felica_load(FelicaData* data, FlipperFormat* ff, uint32_t version) { } while(false); break; case FelicaStandard: - // Areas - do { - uint32_t area_count = 0; - if(!flipper_format_read_uint32(ff, "Area found", &area_count, 1)) break; - simple_array_init(data->areas, area_count); + uint32_t systems_total = 0; + if(!flipper_format_read_uint32(ff, "System found", &systems_total, 1)) break; + simple_array_init(data->systems, systems_total); + + for(uint8_t sys_idx = 0; sys_idx < systems_total; sys_idx++) { + FelicaSystem* system = simple_array_get(data->systems, sys_idx); + uint16_t system_code = 0; furi_string_reset(str_key_buffer); furi_string_reset(str_data_buffer); - for(uint16_t i = 0; i < area_count; i++) { - furi_string_printf(str_key_buffer, "Area %03X", i); - if(!flipper_format_read_string( - ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) { - break; - } - FelicaArea* area = simple_array_get(data->areas, i); - if(sscanf( - furi_string_get_cstr(str_data_buffer), - "| Code %04hX | Services #%03hX-#%03hX |", - &area->code, - &area->first_idx, - &area->last_idx) != 3) { - break; - } - } - } while(false); - - // Services - do { - uint32_t service_count = 0; - if(!flipper_format_read_uint32(ff, "Service found", &service_count, 1)) break; - simple_array_init(data->services, service_count); - - furi_string_reset(str_key_buffer); - furi_string_reset(str_data_buffer); - for(uint16_t i = 0; i < service_count; i++) { - furi_string_printf(str_key_buffer, "Service %03X", i); - if(!flipper_format_read_string( - ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) { - break; - } - FelicaService* service = simple_array_get(data->services, i); - - // all unread in the beginning. reserved for future block load - if(!sscanf( - furi_string_get_cstr(str_data_buffer), "| Code %04hX |", &service->code)) { - break; - } - service->attr = service->code & 0x3F; - } - } while(false); - - // Public blocks - do { - furi_string_reset(str_data_buffer); - furi_string_reset(str_key_buffer); - uint32_t public_block_count = 0; - if(!flipper_format_read_uint32(ff, "Public blocks read", &public_block_count, 1)) + furi_string_printf(str_key_buffer, "System %02X", sys_idx); + if(!flipper_format_read_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) break; - simple_array_init(data->public_blocks, public_block_count); - for(uint16_t i = 0; i < public_block_count; i++) { - furi_string_printf(str_key_buffer, "Block %04X", i); - if(!flipper_format_read_string( - ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) { - break; - } - FelicaPublicBlock* public_block = simple_array_get(data->public_blocks, i); - if(sscanf( - furi_string_get_cstr(str_data_buffer), - "| Service code %04hX | Block index %02hhX |", - &public_block->service_code, - &public_block->block_idx) != 2) { - break; - } - - size_t needle = furi_string_search_str(str_data_buffer, "Data: "); - if(needle == FURI_STRING_FAILURE) { - break; - } - needle += 6; // length of "Data: " = 6 - furi_string_mid(str_data_buffer, needle, 3 * FELICA_DATA_BLOCK_SIZE); - furi_string_replace_all(str_data_buffer, " ", ""); - if(!hex_chars_to_uint8( - furi_string_get_cstr(str_data_buffer), public_block->block.data)) { - break; - } - - furi_string_reset(str_data_buffer); - for(size_t j = 0; j < FELICA_DATA_BLOCK_SIZE; j++) { - furi_string_cat_printf(str_data_buffer, "%02X ", public_block->block.data[j]); - } + if(!sscanf(furi_string_get_cstr(str_data_buffer), "%04hX", &system_code)) { + break; } - } while(false); + + system->system_code = system_code; + system->system_code_idx = sys_idx; + + // Areas + do { + uint32_t area_count = 0; + if(!flipper_format_read_uint32(ff, "Area found", &area_count, 1)) break; + simple_array_init(system->areas, area_count); + + furi_string_reset(str_key_buffer); + furi_string_reset(str_data_buffer); + for(uint16_t i = 0; i < area_count; i++) { + furi_string_printf(str_key_buffer, "Area %03X", i); + if(!flipper_format_read_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) { + break; + } + FelicaArea* area = simple_array_get(system->areas, i); + if(sscanf( + furi_string_get_cstr(str_data_buffer), + "| Code %04hX | Services #%03hX-#%03hX |", + &area->code, + &area->first_idx, + &area->last_idx) != 3) { + break; + } + } + } while(false); + + // Services + do { + uint32_t service_count = 0; + if(!flipper_format_read_uint32(ff, "Service found", &service_count, 1)) break; + simple_array_init(system->services, service_count); + + furi_string_reset(str_key_buffer); + furi_string_reset(str_data_buffer); + for(uint16_t i = 0; i < service_count; i++) { + furi_string_printf(str_key_buffer, "Service %03X", i); + if(!flipper_format_read_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) { + break; + } + FelicaService* service = simple_array_get(system->services, i); + + // all unread in the beginning. reserved for future block load + if(!sscanf( + furi_string_get_cstr(str_data_buffer), + "| Code %04hX |", + &service->code)) { + break; + } + service->attr = service->code & 0x3F; + } + } while(false); + + // Public blocks + do { + furi_string_reset(str_data_buffer); + furi_string_reset(str_key_buffer); + uint32_t public_block_count = 0; + if(!flipper_format_read_uint32(ff, "Public blocks read", &public_block_count, 1)) + break; + simple_array_init(system->public_blocks, public_block_count); + for(uint16_t i = 0; i < public_block_count; i++) { + furi_string_printf(str_key_buffer, "Block %04X", i); + if(!flipper_format_read_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) { + break; + } + + FelicaPublicBlock* public_block = simple_array_get(system->public_blocks, i); + if(sscanf( + furi_string_get_cstr(str_data_buffer), + "| Service code %04hX | Block index %02hhX |", + &public_block->service_code, + &public_block->block_idx) != 2) { + break; + } + + size_t needle = furi_string_search_str(str_data_buffer, "Data: "); + if(needle == FURI_STRING_FAILURE) { + break; + } + needle += 6; // length of "Data: " = 6 + furi_string_mid(str_data_buffer, needle, 3 * FELICA_DATA_BLOCK_SIZE); + furi_string_replace_all(str_data_buffer, " ", ""); + if(!hex_chars_to_uint8( + furi_string_get_cstr(str_data_buffer), public_block->block.data)) { + break; + } + + furi_string_reset(str_data_buffer); + for(size_t j = 0; j < FELICA_DATA_BLOCK_SIZE; j++) { + furi_string_cat_printf( + str_data_buffer, "%02X ", public_block->block.data[j]); + } + } + } while(false); + } break; default: break; @@ -334,88 +340,107 @@ bool felica_save(const FelicaData* data, FlipperFormat* ff) { case FelicaStandard: if(!flipper_format_write_comment_cstr(ff, "Felica Standard specific data")) break; + uint32_t systems_count = simple_array_get_count(data->systems); + if(!flipper_format_write_uint32(ff, "System found", &systems_count, 1)) break; + for(uint32_t sys_idx = 0; sys_idx < systems_count; sys_idx++) { + FelicaSystem* system = simple_array_get(data->systems, sys_idx); - do { - uint32_t area_count = simple_array_get_count(data->areas); - uint32_t service_count = simple_array_get_count(data->services); - // Note: The theoretical max area/service count is 2^10 - // So uint16_t is already enough for practical usage - // The following key index print will use %03X because 12 bits are enough to cover 0-1023 - - // Area count - if(!flipper_format_write_uint32(ff, "Area found", &area_count, 1)) break; - - // Area data furi_string_reset(str_data_buffer); furi_string_reset(str_key_buffer); - for(uint16_t i = 0; i < area_count; i++) { - FelicaArea* area = simple_array_get(data->areas, i); - furi_string_printf(str_key_buffer, "Area %03X", i); - furi_string_printf( - str_data_buffer, - "| Code %04X | Services #%03X-#%03X |", - area->code, - area->first_idx, - area->last_idx); - if(!flipper_format_write_string( - ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) - break; - } - if(!flipper_format_write_empty_line(ff)) break; - - // Service count - if(!flipper_format_write_uint32(ff, "Service found", &service_count, 1)) break; - - // Service data - furi_string_reset(str_data_buffer); - furi_string_reset(str_key_buffer); - for(uint16_t i = 0; i < service_count; i++) { - FelicaService* service = simple_array_get(data->services, i); - furi_string_printf(str_key_buffer, "Service %03X", i); - furi_string_printf( - str_data_buffer, "| Code %04X | Attrib. %02X ", service->code, service->attr); - felica_service_get_attribute_string(service, str_data_buffer); - if(!flipper_format_write_string( - ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) - break; - } - if(!flipper_format_write_empty_line(ff)) break; - - // Directory tree - furi_string_reset(str_data_buffer); - furi_string_reset(str_key_buffer); - furi_string_printf( - str_data_buffer, "\n::: ... are public services\n||| ... are private services"); - felica_write_directory_tree(data, str_data_buffer); - furi_string_replace_all(str_data_buffer, ":", "+"); - // We use a clearer marker in saved text files - if(!flipper_format_write_string(ff, "Directory Tree", str_data_buffer)) break; - } while(false); - - // Public blocks - do { - uint32_t public_block_count = simple_array_get_count(data->public_blocks); - if(!flipper_format_write_uint32(ff, "Public blocks read", &public_block_count, 1)) + furi_string_printf(str_key_buffer, "\n\nSystem %02X", (uint8_t)sys_idx); + furi_string_printf(str_data_buffer, "%04X", system->system_code); + if(!flipper_format_write_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) break; - furi_string_reset(str_data_buffer); - furi_string_reset(str_key_buffer); - for(uint16_t i = 0; i < public_block_count; i++) { - FelicaPublicBlock* public_block = simple_array_get(data->public_blocks, i); - furi_string_printf(str_key_buffer, "Block %04X", i); + if(!flipper_format_write_empty_line(ff)) break; + + do { + uint32_t area_count = simple_array_get_count(system->areas); + uint32_t service_count = simple_array_get_count(system->services); + // Note: The theoretical max area/service count is 2^10 + // So uint16_t is already enough for practical usage + // The following key index print will use %03X because 12 bits are enough to cover 0-1023 + + // Area count + if(!flipper_format_write_uint32(ff, "Area found", &area_count, 1)) break; + + // Area data + furi_string_reset(str_data_buffer); + furi_string_reset(str_key_buffer); + for(uint16_t i = 0; i < area_count; i++) { + FelicaArea* area = simple_array_get(system->areas, i); + furi_string_printf(str_key_buffer, "Area %03X", i); + furi_string_printf( + str_data_buffer, + "| Code %04X | Services #%03X-#%03X |", + area->code, + area->first_idx, + area->last_idx); + if(!flipper_format_write_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) + break; + } + if(!flipper_format_write_empty_line(ff)) break; + + // Service count + if(!flipper_format_write_uint32(ff, "Service found", &service_count, 1)) break; + + // Service data + furi_string_reset(str_data_buffer); + furi_string_reset(str_key_buffer); + for(uint16_t i = 0; i < service_count; i++) { + FelicaService* service = simple_array_get(system->services, i); + furi_string_printf(str_key_buffer, "Service %03X", i); + furi_string_printf( + str_data_buffer, + "| Code %04X | Attrib. %02X ", + service->code, + service->attr); + felica_service_get_attribute_string(service, str_data_buffer); + if(!flipper_format_write_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) + break; + } + if(!flipper_format_write_empty_line(ff)) break; + + // Directory tree + furi_string_reset(str_data_buffer); + furi_string_reset(str_key_buffer); furi_string_printf( str_data_buffer, - "| Service code %04X | Block index %02X | Data: ", - public_block->service_code, - public_block->block_idx); - for(uint8_t j = 0; j < FELICA_DATA_BLOCK_SIZE; j++) { - furi_string_cat_printf(str_data_buffer, "%02X ", public_block->block.data[j]); - } - furi_string_cat_printf(str_data_buffer, "|"); - if(!flipper_format_write_string( - ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) + "\n::: ... are public services\n||| ... are private services"); + felica_write_directory_tree(system, str_data_buffer); + furi_string_replace_all(str_data_buffer, ":", "+"); + // We use a clearer marker in saved text files + if(!flipper_format_write_string(ff, "Directory Tree", str_data_buffer)) break; + } while(false); + + // Public blocks + do { + uint32_t public_block_count = simple_array_get_count(system->public_blocks); + if(!flipper_format_write_uint32(ff, "Public blocks read", &public_block_count, 1)) break; - } - } while(false); + furi_string_reset(str_data_buffer); + furi_string_reset(str_key_buffer); + for(uint16_t i = 0; i < public_block_count; i++) { + FelicaPublicBlock* public_block = simple_array_get(system->public_blocks, i); + furi_string_printf(str_key_buffer, "Block %04X", i); + furi_string_printf( + str_data_buffer, + "| Service code %04X | Block index %02X | Data: ", + public_block->service_code, + public_block->block_idx); + for(uint8_t j = 0; j < FELICA_DATA_BLOCK_SIZE; j++) { + furi_string_cat_printf( + str_data_buffer, "%02X ", public_block->block.data[j]); + } + furi_string_cat_printf(str_data_buffer, "|"); + if(!flipper_format_write_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) + break; + } + } while(false); + } break; default: break; @@ -436,9 +461,7 @@ bool felica_is_equal(const FelicaData* data, const FelicaData* other) { memcmp(data->pmm.data, other->pmm.data, sizeof(FelicaPMm)) == 0 && data->blocks_total == other->blocks_total && data->blocks_read == other->blocks_read && memcmp(&data->data, &other->data, sizeof(data->data)) == 0 && - simple_array_is_equal(data->services, other->services) && - simple_array_is_equal(data->areas, other->areas) && - simple_array_is_equal(data->public_blocks, other->public_blocks); + simple_array_is_equal(data->systems, other->systems); } const char* felica_get_device_name(const FelicaData* data, NfcDeviceNameType name_type) { @@ -640,8 +663,8 @@ void felica_calculate_mac_write( felica_calculate_mac(ctx, session_swapped, rc, first_block, data, FELICA_DATA_BLOCK_SIZE, mac); } -void felica_write_directory_tree(const FelicaData* data, FuriString* str) { - furi_check(data); +void felica_write_directory_tree(const FelicaSystem* system, FuriString* str) { + furi_check(system); furi_check(str); furi_string_cat_str(str, "\n"); @@ -650,12 +673,12 @@ void felica_write_directory_tree(const FelicaData* data, FuriString* str) { uint8_t depth = 0; size_t area_iter = 0; - const size_t area_count = simple_array_get_count(data->areas); - const size_t service_count = simple_array_get_count(data->services); + const size_t area_count = simple_array_get_count(system->areas); + const size_t service_count = simple_array_get_count(system->services); for(size_t svc_idx = 0; svc_idx < service_count; ++svc_idx) { while(area_iter < area_count) { - const FelicaArea* next_area = simple_array_get(data->areas, area_iter); + const FelicaArea* next_area = simple_array_get(system->areas, area_iter); if(next_area->first_idx != svc_idx) break; for(uint8_t i = 0; i < depth - 1; ++i) @@ -667,7 +690,7 @@ void felica_write_directory_tree(const FelicaData* data, FuriString* str) { area_iter++; } - const FelicaService* service = simple_array_get(data->services, svc_idx); + const FelicaService* service = simple_array_get(system->services, svc_idx); bool is_public = (service->attr & FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ) != 0; for(uint8_t i = 0; i < depth - 1; ++i) diff --git a/lib/nfc/protocols/felica/felica.h b/lib/nfc/protocols/felica/felica.h index 5a5a75215..bc83ff846 100644 --- a/lib/nfc/protocols/felica/felica.h +++ b/lib/nfc/protocols/felica/felica.h @@ -50,8 +50,10 @@ extern "C" { #define FELICA_TIME_SLOT_8 (0x07U) #define FELICA_TIME_SLOT_16 (0x0FU) -#define FELICA_CMD_LIST_SERVICE_CODE 0x0A -#define FELICA_CMD_LIST_SERVICE_CODE_RESP 0x0B +#define FELICA_CMD_LIST_SERVICE_CODE 0x0A +#define FELICA_CMD_LIST_SERVICE_CODE_RESP 0x0B +#define FELICA_CMD_REQUEST_SYSTEM_CODE 0x0C +#define FELICA_CMD_REQUEST_SYSTEM_CODE_RESP 0x0D #define FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ (0b000001) #define FELICA_SERVICE_ATTRIBUTE_READ_ONLY (0b000010) @@ -182,6 +184,14 @@ typedef struct { uint8_t block_idx; } FelicaPublicBlock; +typedef struct { + uint8_t system_code_idx; + uint16_t system_code; + SimpleArray* services; + SimpleArray* areas; + SimpleArray* public_blocks; +} FelicaSystem; + /** @brief Structure used to store Felica data and additional values about reading */ typedef struct { FelicaIDm idm; @@ -190,9 +200,8 @@ typedef struct { uint8_t blocks_read; FelicaFSUnion data; - SimpleArray* services; - SimpleArray* areas; - SimpleArray* public_blocks; + SimpleArray* systems; + FelicaWorkflowType workflow_type; } FelicaData; @@ -248,6 +257,12 @@ typedef struct { uint8_t data[]; } FelicaListServiceCommandResponse; +typedef struct { + FelicaCommandHeaderRaw header; + uint8_t system_count; + uint8_t system_code[]; +} FelicaListSystemCodeCommandResponse; + typedef FelicaCommandResponseHeader FelicaListenerWriteCommandResponse; typedef FelicaCommandResponseHeader FelicaPollerWriteCommandResponse; @@ -309,7 +324,7 @@ void felica_calculate_mac_write( const uint8_t* data, uint8_t* mac); -void felica_write_directory_tree(const FelicaData* data, FuriString* str); +void felica_write_directory_tree(const FelicaSystem* system, FuriString* str); void felica_get_workflow_type(FelicaData* data); diff --git a/lib/nfc/protocols/felica/felica_i.c b/lib/nfc/protocols/felica/felica_i.c index e265ea862..c5ecdf121 100644 --- a/lib/nfc/protocols/felica/felica_i.c +++ b/lib/nfc/protocols/felica/felica_i.c @@ -1,5 +1,36 @@ #include "felica_i.h" +void felica_system_init(FelicaSystem* system) { + system->system_code = 0; + system->system_code_idx = 0; + system->services = simple_array_alloc(&felica_service_array_cfg); + system->areas = simple_array_alloc(&felica_area_array_cfg); + system->public_blocks = simple_array_alloc(&felica_public_block_array_cfg); +} + +void felica_system_reset(FelicaSystem* system) { + furi_check(system); + system->system_code = 0; + system->system_code_idx = 0; + furi_check(system->services); + furi_check(system->areas); + furi_check(system->public_blocks); + simple_array_free(system->services); + simple_array_free(system->areas); + simple_array_free(system->public_blocks); + memset(system, 0, sizeof(FelicaSystem)); +} + +void felica_system_copy(FelicaSystem* system, const FelicaSystem* other) { + furi_check(system); + furi_check(other); + system->system_code = other->system_code; + system->system_code_idx = other->system_code_idx; + simple_array_copy(system->services, other->services); + simple_array_copy(system->areas, other->areas); + simple_array_copy(system->public_blocks, other->public_blocks); +} + const SimpleArrayConfig felica_service_array_cfg = { .init = NULL, .copy = NULL, @@ -20,3 +51,10 @@ const SimpleArrayConfig felica_public_block_array_cfg = { .reset = NULL, .type_size = sizeof(FelicaPublicBlock), }; + +const SimpleArrayConfig felica_system_array_cfg = { + .init = (SimpleArrayInit)felica_system_init, + .copy = (SimpleArrayCopy)felica_system_copy, + .reset = (SimpleArrayReset)felica_system_reset, + .type_size = sizeof(FelicaSystem), +}; diff --git a/lib/nfc/protocols/felica/felica_i.h b/lib/nfc/protocols/felica/felica_i.h index a708aa729..d55077c5e 100644 --- a/lib/nfc/protocols/felica/felica_i.h +++ b/lib/nfc/protocols/felica/felica_i.h @@ -8,3 +8,8 @@ extern const SimpleArrayConfig felica_service_array_cfg; extern const SimpleArrayConfig felica_area_array_cfg; extern const SimpleArrayConfig felica_public_block_array_cfg; +extern const SimpleArrayConfig felica_system_array_cfg; + +void felica_system_init(FelicaSystem* system); +void felica_system_reset(FelicaSystem* system); +void felica_system_copy(FelicaSystem* system, const FelicaSystem* other); diff --git a/lib/nfc/protocols/felica/felica_poller.c b/lib/nfc/protocols/felica/felica_poller.c index a6964c685..98e07df42 100644 --- a/lib/nfc/protocols/felica/felica_poller.c +++ b/lib/nfc/protocols/felica/felica_poller.c @@ -12,6 +12,7 @@ ARRAY_DEF(felica_service_array, FelicaService, M_POD_OPLIST); // -V658 ARRAY_DEF(felica_area_array, FelicaArea, M_POD_OPLIST); // -V658 ARRAY_DEF(felica_public_block_array, FelicaPublicBlock, M_POD_OPLIST); // -V658 +ARRAY_DEF(felica_system_array, FelicaSystem, M_POD_OPLIST); // -V658 typedef NfcCommand (*FelicaPollerReadHandler)(FelicaPoller* instance); @@ -43,6 +44,9 @@ static FelicaPoller* felica_poller_alloc(Nfc* nfc) { instance->general_event.event_data = &instance->felica_event; instance->general_event.instance = instance; + instance->systems_read = 0; + instance->systems_total = 0; + return instance; } @@ -94,7 +98,7 @@ NfcCommand felica_poller_state_handler_activate(FelicaPoller* instance) { switch(instance->data->workflow_type) { case FelicaStandard: - instance->state = FelicaPollerStateTraverseStandardSystem; + instance->state = FelicaPollerStateListSystem; break; case FelicaLite: instance->state = FelicaPollerStateReadLiteBlocks; @@ -117,6 +121,44 @@ NfcCommand felica_poller_state_handler_activate(FelicaPoller* instance) { return command; } +NfcCommand felica_poller_state_handler_list_system(FelicaPoller* instance) { + FURI_LOG_D(TAG, "List System"); + + NfcCommand command = NfcCommandContinue; + + FelicaListSystemCodeCommandResponse* response_system_code; + FelicaError error = felica_poller_list_system_code(instance, &response_system_code); + + instance->systems_total = response_system_code->system_count; + simple_array_init(instance->data->systems, instance->systems_total); + uint8_t* system_codes = response_system_code->system_code; + + for(uint8_t i = 0; i < instance->systems_total; i++) { + FelicaSystem* system = simple_array_get(instance->data->systems, i); + system->system_code = system_codes[i * 2] << 8 | system_codes[i * 2 + 1]; + system->system_code_idx = i; + } + + if(error == FelicaErrorNone) { + instance->state = FelicaPollerStateSelectSystemIndex; + } 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_select_system_idx(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Select System Index %d", instance->systems_read); + uint8_t system_index_mask = instance->systems_read << 4; + instance->data->idm.data[0] &= 0x0F; + instance->data->idm.data[0] |= system_index_mask; + instance->state = FelicaPollerStateTraverseStandardSystem; + + return NfcCommandContinue; +} + NfcCommand felica_poller_state_handler_auth_internal(FelicaPoller* instance) { FURI_LOG_D(TAG, "Auth Internal"); @@ -274,6 +316,8 @@ NfcCommand felica_poller_state_handler_traverse_standard_system(FelicaPoller* in service->code = code_begin; service->attr = (uint8_t)(code_begin & 0x3F); + FURI_LOG_D(TAG, "Service %04X", service->code); + if(felica_area_array_size(area_buffer)) { FelicaArea* current_area = felica_area_array_back(area_buffer); current_area->last_idx = (uint16_t)(felica_service_array_size(service_buffer) - 1); @@ -284,31 +328,30 @@ NfcCommand felica_poller_state_handler_traverse_standard_system(FelicaPoller* in const size_t service_num = felica_service_array_size(service_buffer); const size_t area_num = felica_area_array_size(area_buffer); + FelicaSystem* system = simple_array_get(instance->data->systems, instance->systems_read); if(service_num) { - simple_array_init(instance->data->services, (uint32_t)service_num); + simple_array_init(system->services, (uint32_t)service_num); memcpy( - simple_array_get(instance->data->services, 0), + simple_array_get(system->services, 0), service_buffer->ptr, service_num * sizeof(FelicaService)); } else { - simple_array_reset(instance->data->services); + simple_array_reset(system->services); } if(area_num) { - simple_array_init(instance->data->areas, (uint32_t)area_num); + simple_array_init(system->areas, (uint32_t)area_num); memcpy( - simple_array_get(instance->data->areas, 0), - area_buffer->ptr, - area_num * sizeof(FelicaArea)); + simple_array_get(system->areas, 0), area_buffer->ptr, area_num * sizeof(FelicaArea)); } else { - simple_array_reset(instance->data->areas); + simple_array_reset(system->areas); } FURI_LOG_I( TAG, "Services found: %lu, Areas found: %lu", - simple_array_get_count(instance->data->services), - simple_array_get_count(instance->data->areas)); + simple_array_get_count(system->services), + simple_array_get_count(system->areas)); felica_service_array_clear(service_buffer); felica_area_array_clear(area_buffer); @@ -320,22 +363,22 @@ NfcCommand felica_poller_state_handler_traverse_standard_system(FelicaPoller* in NfcCommand felica_poller_state_handler_read_standard_blocks(FelicaPoller* instance) { FURI_LOG_D(TAG, "Read Standard Blocks"); - const uint32_t service_count = simple_array_get_count(instance->data->services); + FelicaSystem* system = simple_array_get(instance->data->systems, instance->systems_read); + const uint32_t service_count = simple_array_get_count(system->services); felica_public_block_array_t public_block_buffer; felica_public_block_array_init(public_block_buffer); - instance->state = FelicaPollerStateReadSuccess; bool have_read_anything = false; + FelicaError error = FelicaErrorNone; for(uint32_t i = 0; i < service_count; i++) { - const FelicaService* service = simple_array_get(instance->data->services, i); + const FelicaService* service = simple_array_get(system->services, i); if((service->attr & FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ) == 0) continue; uint8_t block_count = 1; uint8_t block_list[1] = {0}; - FelicaError error = FelicaErrorNone; FelicaPollerReadCommandResponse* response; do { error = felica_poller_read_blocks( @@ -369,11 +412,20 @@ NfcCommand felica_poller_state_handler_read_standard_blocks(FelicaPoller* instan } } + if(error == FelicaErrorNone) { + instance->systems_read++; + if(instance->systems_read == instance->systems_total) { + instance->state = FelicaPollerStateReadSuccess; + } else { + instance->state = FelicaPollerStateSelectSystemIndex; + } + } + if(have_read_anything) { const size_t n = felica_public_block_array_size(public_block_buffer); - simple_array_init(instance->data->public_blocks, (uint32_t)n); + simple_array_init(system->public_blocks, (uint32_t)n); memcpy( - simple_array_get(instance->data->public_blocks, 0), + simple_array_get(system->public_blocks, 0), public_block_buffer->ptr, n * sizeof(FelicaPublicBlock)); } @@ -446,6 +498,8 @@ NfcCommand felica_poller_state_handler_read_success(FelicaPoller* instance) { instance->felica_event.type = FelicaPollerEventTypeReady; } + instance->data->idm.data[0] &= 0x0F; + instance->felica_event_data.error = FelicaErrorNone; return instance->callback(instance->general_event, instance->context); } @@ -453,6 +507,7 @@ NfcCommand felica_poller_state_handler_read_success(FelicaPoller* instance) { NfcCommand felica_poller_state_handler_read_failed(FelicaPoller* instance) { FURI_LOG_D(TAG, "Read Fail"); instance->callback(instance->general_event, instance->context); + instance->data->idm.data[0] &= 0x0F; return NfcCommandStop; } @@ -460,6 +515,8 @@ NfcCommand felica_poller_state_handler_read_failed(FelicaPoller* instance) { static const FelicaPollerReadHandler felica_poller_handler[FelicaPollerStateNum] = { [FelicaPollerStateIdle] = felica_poller_state_handler_idle, [FelicaPollerStateActivated] = felica_poller_state_handler_activate, + [FelicaPollerStateListSystem] = felica_poller_state_handler_list_system, + [FelicaPollerStateSelectSystemIndex] = felica_poller_state_handler_select_system_idx, [FelicaPollerStateAuthenticateInternal] = felica_poller_state_handler_auth_internal, [FelicaPollerStateAuthenticateExternal] = felica_poller_state_handler_auth_external, [FelicaPollerStateTraverseStandardSystem] = diff --git a/lib/nfc/protocols/felica/felica_poller_i.c b/lib/nfc/protocols/felica/felica_poller_i.c index 4ab9a8e4c..bbbb824d3 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.c +++ b/lib/nfc/protocols/felica/felica_poller_i.c @@ -233,7 +233,9 @@ static void felica_poller_prepare_tx_buffer_raw( cmd.length = sizeof(FelicaCommandHeaderRaw) + data_length; bit_buffer_reset(instance->tx_buffer); bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)&cmd, sizeof(FelicaCommandHeaderRaw)); - bit_buffer_append_bytes(instance->tx_buffer, data, data_length); + if(data_length > 0) { + bit_buffer_append_bytes(instance->tx_buffer, data, data_length); + } } FelicaError felica_poller_list_service_by_cursor( @@ -264,3 +266,31 @@ FelicaError felica_poller_list_service_by_cursor( *response_ptr = (FelicaListServiceCommandResponse*)bit_buffer_get_data(instance->rx_buffer); return error; } + +FelicaError felica_poller_list_system_code( + FelicaPoller* instance, + FelicaListSystemCodeCommandResponse** const response_ptr) { + furi_assert(instance); + furi_assert(response_ptr); + + uint8_t data[] = {0}; + + felica_poller_prepare_tx_buffer_raw(instance, FELICA_CMD_REQUEST_SYSTEM_CODE, data, 0); + + 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) { + FURI_LOG_E(TAG, "Request system code failed with error: %d", error); + return error; + } + + size_t rx_len = bit_buffer_get_size_bytes(instance->rx_buffer); + if(rx_len < sizeof(FelicaCommandHeaderRaw) + 3) return FelicaErrorProtocol; + // at least 1 system code + the count being 0x01 + + // error is known to be FelicaErrorNone here + *response_ptr = (FelicaListSystemCodeCommandResponse*)bit_buffer_get_data(instance->rx_buffer); + return error; +} diff --git a/lib/nfc/protocols/felica/felica_poller_i.h b/lib/nfc/protocols/felica/felica_poller_i.h index 98948db86..446f34c43 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.h +++ b/lib/nfc/protocols/felica/felica_poller_i.h @@ -17,6 +17,8 @@ extern "C" { typedef enum { FelicaPollerStateIdle, FelicaPollerStateActivated, + FelicaPollerStateListSystem, + FelicaPollerStateSelectSystemIndex, FelicaPollerStateAuthenticateInternal, FelicaPollerStateAuthenticateExternal, FelicaPollerStateTraverseStandardSystem, @@ -42,6 +44,8 @@ struct FelicaPoller { FelicaPollerEventData felica_event_data; NfcGenericCallback callback; uint8_t block_index; + uint8_t systems_read; + uint8_t systems_total; void* context; }; @@ -116,6 +120,10 @@ FelicaError felica_poller_list_service_by_cursor( uint16_t cursor, FelicaListServiceCommandResponse** response_ptr); +FelicaError felica_poller_list_system_code( + FelicaPoller* instance, + FelicaListSystemCodeCommandResponse** response_ptr); + #ifdef __cplusplus } #endif diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 9963b3515..676340085 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1087,7 +1087,7 @@ Function,+,felica_save,_Bool,"const FelicaData*, FlipperFormat*" Function,+,felica_service_get_attribute_string,void,"const FelicaService*, FuriString*" Function,+,felica_set_uid,_Bool,"FelicaData*, const uint8_t*, size_t" Function,+,felica_verify,_Bool,"FelicaData*, const FuriString*" -Function,+,felica_write_directory_tree,void,"const FelicaData*, FuriString*" +Function,+,felica_write_directory_tree,void,"const FelicaSystem*, FuriString*" Function,-,feof,int,FILE* Function,-,feof_unlocked,int,FILE* Function,-,ferror,int,FILE* From 6d93a90a0a94a14674bda691e850c7d1f3f9e769 Mon Sep 17 00:00:00 2001 From: Zinong Li <131403964+zinongli@users.noreply.github.com> Date: Thu, 6 Nov 2025 22:37:33 +0400 Subject: [PATCH 06/10] NFC lib: Expose `nfc_common.h` (#4283) * expose nfc_common.h * Update api_symbols.csv --------- Co-authored-by: hedger --- lib/nfc/SConscript | 1 + targets/f7/api_symbols.csv | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index 585e98197..64a5dd466 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -14,6 +14,7 @@ env.Append( File("nfc_listener.h"), File("nfc_poller.h"), File("nfc_scanner.h"), + File("nfc_common.h"), # Protocols File("protocols/iso14443_3a/iso14443_3a.h"), File("protocols/iso14443_3b/iso14443_3b.h"), diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 676340085..2bb63b88e 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -138,6 +138,7 @@ Header,+,lib/nfc/helpers/iso14443_crc.h,, Header,+,lib/nfc/helpers/nfc_data_generator.h,, Header,+,lib/nfc/helpers/nfc_util.h,, Header,+,lib/nfc/nfc.h,, +Header,+,lib/nfc/nfc_common.h,, Header,+,lib/nfc/nfc_device.h,, Header,+,lib/nfc/nfc_listener.h,, Header,+,lib/nfc/nfc_poller.h,, From a42e30f394b809ad3df10586fc3148193f37cc41 Mon Sep 17 00:00:00 2001 From: Zinong Li <131403964+zinongli@users.noreply.github.com> Date: Thu, 6 Nov 2025 22:45:52 +0400 Subject: [PATCH 07/10] NFC FeliCa Minor Fix: `FelicaPollerEventType` should only be `Incomplete` if the tag is FeliCa Lite (#4279) * poller read is only incomp if in Lite workflow * another small fix for the "255/28 pages read" bug --------- Co-authored-by: hedger --- lib/nfc/protocols/felica/felica_poller.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/nfc/protocols/felica/felica_poller.c b/lib/nfc/protocols/felica/felica_poller.c index 98e07df42..056ed4320 100644 --- a/lib/nfc/protocols/felica/felica_poller.c +++ b/lib/nfc/protocols/felica/felica_poller.c @@ -486,9 +486,12 @@ NfcCommand felica_poller_state_handler_read_lite_blocks(FelicaPoller* instance) 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--; + if((!instance->auth.context.auth_status.internal || + !instance->auth.context.auth_status.external) && + instance->data->workflow_type == FelicaLite) { + if(instance->data->blocks_read != 0) { + instance->data->blocks_read--; + } instance->felica_event.type = FelicaPollerEventTypeIncomplete; } else { memcpy( From c873eb5e3692c18369e732390c280288e2eae3cc Mon Sep 17 00:00:00 2001 From: Nathan N Date: Thu, 6 Nov 2025 14:05:16 -0500 Subject: [PATCH 08/10] Fix Ultralight EV1 regression (#4287) * Fix EV1 regression introduced in e7634d7 * ./fbt format --------- Co-authored-by: hedger Co-authored-by: hedger --- .../mf_ultralight/mf_ultralight.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) 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 5ed564897..3afca397b 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 @@ -152,13 +152,16 @@ static NfcCommand if(!mf_ultralight_event->data->auth_context.skip_auth) { mf_ultralight_event->data->auth_context.password = instance->mf_ul_auth->password; - // Only set tdes_key for Manual/Reader auth types, not for dictionary attacks - if(instance->mf_ul_auth->type == MfUltralightAuthTypeManual || - instance->mf_ul_auth->type == MfUltralightAuthTypeReader) { - mf_ultralight_event->data->key_request_data.key = instance->mf_ul_auth->tdes_key; - mf_ultralight_event->data->key_request_data.key_provided = true; - } else { - mf_ultralight_event->data->key_request_data.key_provided = false; + if(data->type == MfUltralightTypeMfulC) { + // Only set tdes_key for Manual/Reader auth types, not for dictionary attacks + if(instance->mf_ul_auth->type == MfUltralightAuthTypeManual || + instance->mf_ul_auth->type == MfUltralightAuthTypeReader) { + mf_ultralight_event->data->key_request_data.key = + instance->mf_ul_auth->tdes_key; + mf_ultralight_event->data->key_request_data.key_provided = true; + } else { + mf_ultralight_event->data->key_request_data.key_provided = false; + } } } } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthSuccess) { From f4c92dcd76d9c29e184d245cb493d0aa86809c40 Mon Sep 17 00:00:00 2001 From: Nathan N Date: Thu, 6 Nov 2025 14:34:21 -0500 Subject: [PATCH 09/10] Fix MIFARE Plus SL1 sector overrun issue in state machine (#4288) Co-authored-by: hedger --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index ec37c8015..b2d9b114a 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1921,7 +1921,8 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance sizeof(MfClassicKey)) : NULL; } - if((is_weak || is_last_iter_for_hard_key) && dict_attack_ctx->nested_nonce.count > 0) { + if((is_weak && (dict_attack_ctx->nested_nonce.count == 1)) || + (is_last_iter_for_hard_key && (dict_attack_ctx->nested_nonce.count == 8))) { // Key verify and reuse dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttackVerify; dict_attack_ctx->auth_passed = false; From 7518dc73c285faa3528095874bfefde3c1386cfd Mon Sep 17 00:00:00 2001 From: Mykhailo Shevchuk Date: Thu, 6 Nov 2025 21:54:50 +0200 Subject: [PATCH 10/10] Returning fix for reading PWD locked MFUL (#4295) Co-authored-by: hedger --- .../protocols/mf_ultralight/mf_ultralight_poller.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index 5f872952e..80518b1a8 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -566,16 +566,12 @@ static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* in instance->error = mf_ultralight_poller_read_page(instance, start_page, &data); } - // Regression review - const uint8_t read_cnt = instance->data->type == MfUltralightTypeMfulC ? 1 : 4; if(instance->error == MfUltralightErrorNone) { - for(size_t i = 0; i < read_cnt; i++) { - if(start_page + i < instance->pages_total) { - FURI_LOG_D(TAG, "Read page %d success", start_page + i); - instance->data->page[start_page + i] = data.page[i]; - instance->pages_read++; - instance->data->pages_read = instance->pages_read; - } + if(start_page < instance->pages_total) { + FURI_LOG_D(TAG, "Read page %d success", start_page); + instance->data->page[start_page] = data.page[0]; + instance->pages_read++; + instance->data->pages_read = instance->pages_read; } if(instance->pages_read == instance->pages_total) {