From 05e21c715809b43943c97b173189cdf2e540987e Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 24 Apr 2025 21:50:03 +0300 Subject: [PATCH 01/75] upd docs [ci skip] --- documentation/SubGHzRemoteProg.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/documentation/SubGHzRemoteProg.md b/documentation/SubGHzRemoteProg.md index 9594b9e6c..a8eea8dac 100644 --- a/documentation/SubGHzRemoteProg.md +++ b/documentation/SubGHzRemoteProg.md @@ -122,14 +122,24 @@ How to get seed to make full clone of your remote (**will conflict with original 1. Open `Read` in SubGHz on your flipper 2. (ONLY FOR ORIGINAL REMOTES) Hold all buttons on your remote at same time, example -> for 2 button remote - press them both at same time and hold OR press hidden button on back of remote with a pin or paper clip +For 4 buttons remote press & hold two buttons at upper row 3. You will receive signal on your flipper, open that signal and see `Fix:` value, it should start from `F` like `F00F1C9B` -4. If `Fix:` is showing first `F` see `Hop:` value -> This is your remote Seed -5. Write down Hop value +4. If `Fix:` is showing first `F` see `Hop:` value -> This is your remote Seed (except first digit `F` (this is the button code, aka programming button pressed means `F`)) +5. Write down Hop value and replace first digit - `F` with `0` 6. Press button on your remote that you want to clone and receive its signal on your flipper 7. Open and write down `Fix:` value where first digit will be same as your button ID `Btn:` 8. Create new remote using BFT Mitto [Manual] - Enter FIX from step 7, enter counter `FF F9`, enter seed from step 5 9. Using counter values like `FF F9` can help bypassing current original remote counter value, and in result it also can fully desync original remote, only one remote can work at same time using this method -10. Throw away your original remote since now it needs to be re-added into receiver board :C +10. Also you can do this: Save your signal of the original remote (will say KL: Unknown), +then copy file to the PC and edit it and insert/replace those values after the `Key: 01 23 45 67 89 AB CD EF` (your key will have different value) +``` +Seed: 0X XX XX XX +Manufacture: BFT +``` +Replace `X`'s with digits from your Seed that you obtained by reading two button hold at the first steps, +Save and copy that file back to the flipper +Now you will have exact clone of your remote that will have same counter, by making couple presses you will make it higher than original and receiver will work with it, but original remote will reguire same amount of presses to work again, and vice versa. +11. Also your original remote may become non working since it needs to be re-added into receiver board if you made counter much higher than original :C ## CAME Atomo From 8cbf9a40a74378523574483ff248b41c478affb3 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 27 Apr 2025 11:51:05 +0300 Subject: [PATCH 02/75] upd docs [ci skip] --- documentation/SubGHzRemoteProg.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/documentation/SubGHzRemoteProg.md b/documentation/SubGHzRemoteProg.md index a8eea8dac..d9809f8cb 100644 --- a/documentation/SubGHzRemoteProg.md +++ b/documentation/SubGHzRemoteProg.md @@ -73,6 +73,7 @@ Watch this video to learn more and see how different boards can be programmed (v ## Doorhan +With access to the receiver box: 1. Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> KL: Doorhan 433Mhz or 315Mhz depends on your receiver (find out by reading your existing remote) 2. Open your new remote file 3. Push `P` button for ~2 sec, led will start flashing @@ -80,7 +81,29 @@ Watch this video to learn more and see how different boards can be programmed (v 5. Led on the receiver board will flash and turn off 6. Done! -Also you can program new remote using old remote on newer boards! See first video below: + +With existing remote: +1. Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> KL: Doorhan 433Mhz or 315Mhz depends on your receiver (find out by reading your existing remote) +2. Open your new remote file +3. For next steps be close to the receiver board, around 1-2 meters +4. Press second button (lowest one) on the old remote, do not release second button and press 1st (upper) button, hold buttons for 1 sec and release them +5. Press working button on the old remote (the button you use for operating the receiver, aka opening the gate, etc) hold for 1 sec and release +6. Actions with old remote must be done in 5 seconds time, do not hold buttons for too long, and do not make it very fast +7. Receiver will beep, you will have 10 seconds to add new remote, now press Send on new remote on flipper two times holding for at least 1 sec +8. Receiver will beep again telling that new remote is added sucessfuly! +9. Done! + +With copy of existing remote on flipper: +1. Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> KL: Doorhan 433Mhz or 315Mhz depends on your receiver (find out by reading your existing remote) +2. Open your existing remote (original) file +3. For next steps be close to the receiver board, around 1-2 meters +4. Press left button (0x8) on the flipper, hold for 1 sec and release the button and press right (0xA) button, hold button for 1 sec and release +5. Press working button on the flipper, should be center one aka Send (the button you use for operating the receiver, aka opening the gate, etc) hold for 1 sec and release +6. Actions with original remote copy must be done in 5 seconds time, do not hold buttons for too long, and do not make it very fast +7. Receiver will beep, now hold back and open new remote file, you will have 10 seconds to add new remote, press Send on new remote on flipper two times holding for at least 1 sec +8. Receiver will beep again telling that new remote is added sucessfuly! +9. Done! + Watch this videos to learn more (videos in Russian language): https://www.youtube.com/watch?v=wZ5121HYv50 / https://www.youtube.com/watch?v=1ucrDKF3vWc ## Somfy Telis From 0c7500399e1723742ca00bf17e486e77b81ffccd Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Mon, 28 Apr 2025 14:14:20 +0400 Subject: [PATCH 03/75] js_gpio: stop pwm on exit --- applications/system/js_app/modules/js_gpio.c | 33 ++++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/applications/system/js_app/modules/js_gpio.c b/applications/system/js_app/modules/js_gpio.c index 63de6900a..df7d494a6 100644 --- a/applications/system/js_app/modules/js_gpio.c +++ b/applications/system/js_app/modules/js_gpio.c @@ -23,6 +23,7 @@ typedef struct { } JsGpioPinInst; ARRAY_DEF(ManagedPinsArray, JsGpioPinInst*, M_PTR_OPLIST); //-V575 +#define M_OPL_ManagedPinsArray_t() ARRAY_OPLIST(ManagedPinsArray) /** * Per-module instance control structure @@ -444,20 +445,26 @@ static void js_gpio_destroy(void* inst) { JsGpioInst* module = (JsGpioInst*)inst; // reset pins - ManagedPinsArray_it_t iterator; - for(ManagedPinsArray_it(iterator, module->managed_pins); !ManagedPinsArray_end_p(iterator); - ManagedPinsArray_next(iterator)) { - JsGpioPinInst* manager_data = *ManagedPinsArray_cref(iterator); - if(manager_data->had_interrupt) { - furi_hal_gpio_disable_int_callback(manager_data->pin); - furi_hal_gpio_remove_int_callback(manager_data->pin); + for + M_EACH(item, module->managed_pins, ManagedPinsArray_t) { + JsGpioPinInst* manager_data = *item; + + if(manager_data->had_interrupt) { + furi_hal_gpio_disable_int_callback(manager_data->pin); + furi_hal_gpio_remove_int_callback(manager_data->pin); + } + if(manager_data->pwm_output != FuriHalPwmOutputIdNone) { + if(furi_hal_pwm_is_running(manager_data->pwm_output)) + furi_hal_pwm_stop(manager_data->pwm_output); + } + furi_hal_gpio_init(manager_data->pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + + furi_event_loop_maybe_unsubscribe(module->loop, manager_data->interrupt_semaphore); + furi_semaphore_free(manager_data->interrupt_semaphore); + + free(manager_data->interrupt_contract); + free(manager_data); } - furi_hal_gpio_init(manager_data->pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_event_loop_maybe_unsubscribe(module->loop, manager_data->interrupt_semaphore); - furi_semaphore_free(manager_data->interrupt_semaphore); - free(manager_data->interrupt_contract); - free(manager_data); - } // free buffers furi_hal_adc_release(module->adc_handle); From 5bba71e2cbc940c1bff10ca61764353bb2af130e Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 28 Apr 2025 18:41:52 +0300 Subject: [PATCH 04/75] upd changelog --- CHANGELOG.md | 88 ++-------------------------------------------------- 1 file changed, 2 insertions(+), 86 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dd43e977..b2a6b29ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,93 +1,9 @@ ## Main changes - Current API: 86.0 -**WARNING! After install of this version your Desktop (fav apps) and LCD & Notifications settings will be reset to default values, please configure them again after this update!** (this is required due to big updates on that parts and config struct changes) -* SubGHz: Add **Feron** protocol (static 32 bit) **full support** (by @xMasterX) -* SubGHz: Add **Revers RB2 / RB2M Protocol** (static 64 bit) **full support** with add manually (by @xMasterX) -* SubGHz: **Fix Hollarm protocol with more verification** -* SubGHz: **Fix GangQi protocol** (by @DoberBit and @mishamyte (who spent 2 weeks on this)) -* SubGHz: **Came Atomo button hold simulation with full cycle** simulation (to allow proper pairing with receiver) -* SubGHz: Add **Prastel (42bit static code)** support (OFW PR 4178 by @pmazzini) -* Desktop: **Add support for Favorite App - Ok Long** (Warning! Old favourites apps list will be reset!) (PR #886 | by @DrEverr) -* Display: **LCD Color Inversion** (Settings - LCD and Notifications - LCD Inversion) (PR #887 #893 | by @Dmitry422) -* Display: **Night Shift Feature** (dimming backlight in selected time interval) (PR #885 #896 | by @Dmitry422) -* Display: **Сombining RGB Backlight mod** (by @quen0n) and original backlight support **in one firmware** (+ Rainbow/Wave effect (based on @Willy-JL idea)) (PR #877 #881 #890 | by @Dmitry422) - (**To enable RGB Backlight support go into Notifications settings**) -* NFC: Use default UL/UL-C pwd/key as default value for key input (PR #891 | by @mishamyte) -* OFW: LFRFID - **EM4305 support** -* OFW: **Universal IR signal selection** -* OFW: **BadUSB: Mouse control** -* OFW: **Pinning of settings options** -* OFW: NFC app now can launch MFKey32 -* OFW: BadUSB arbitrary key combinations -* OFW PR 4136: BadUSB: Full USB/BLE parameter customization, UI improvements, and more (by @Willy-JL) -* OFW: NFC - Added naming for DESFire cards + fix MF3ICD40 cards unable to be read -* Apps: Add **FindMyFlipper to system apps and allow autostart** on system boot [app by @MatthewKuKanich](https://github.com/MatthewKuKanich/FindMyFlipper) and autoloader by @Willy-JL - to use app please check how to add keys in [app repo](https://github.com/MatthewKuKanich/FindMyFlipper) -* README Update: Enhanced Visuals & Navigation (PR #871 #872 | by @m-xim) -* Docs: Update FAQ.md (PR #865 | by @mi-lrn) -* Input: **Vibro on Button press option** (PR #867 | by @Dmitry422) -* Power: **Option to limit battery charging** (suppress charging on selected charge level) (PR #867 | by @Dmitry422) (idea and example by @oltenxyz) +* OFW PR 4206: Stop JS PWM on exit (by @portasynthinca3) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes -* SubGHz: Move hardcoded extra modulations to user config - uncomment them in setting_user.example and remove .example from filename -* SubGHz: Various bugfixes and experimental options (rolling counter overflow) (by @xMasterX) -* Anims: Disable winter anims -* NFC: mfclassic poller fix early key reuse in dictionary attack state machine (by @noproto) -* OFW: RC fixes -* OFW: Desktop: Fix freeze on boot if PIN set -* OFW PR 4189: USB-UART bridge fix (by @portasynthinca3) -* OFW: FBT: Fix for Python 3.13 -* OFW: sdk: bump API to force re-upload for the catalog -* OFW: SDK: Fix missing RECORD_CLI define -* OFW: Fix NULL dereference in CLI completions -* OFW PR 4181: vcp, cli: Handle Tx/Rx events before Connect/Disconnect + extra fixes (by @portasynthinca3) -* OFW: BLE: Slightly increase mfg_data size -* OFW: fbt: Deterministic STARTUP order & additional checks -* OFW: JS: Update and fix docs, fix Number.toString() with decimals -* OFW: New JS value destructuring -* OFW: Docs: Fix doxygen references from PR 4168 -* OFW: BLE advertising improvements -* OFW: **New CLI architecture** -* OFW: **CLI autocomplete and other sugar** -* OFW: CLI commands in fals and threads -* OFW: cli: fixed `free_blocks` command -* OFW: docs: badusb arbitrary modkey chains -* OFW: Separate cli_shell into toolbox -* OFW: Move JS modules to new arg parser -* OFW: Application chaining -* OFW: Fix DWARF dead code elimination and linking -* OFW: NFC: Fix crash on ISO15693-3 save when memory is empty or cannot be read -* OFW: Reduced ieee754 parser size -* OFW: Added Doom animation (by @doomwastaken) -* OFW PR 4133: add nfc apdu cli command back (by @leommxj) -* OFW: NFC: Support DESFire Transaction MAC file type (by @Willy-JL) -* OFW: NFC: Fix NDEF parser for MIFARE Classic (by @Willy-JL) -* OFW: GUI: Fix widget text scroll with 256+ lines (by @Willy-JL) -* OFW: Infrared: Fix universals sending (by @Willy-JL) -* OFW: HID Ble: increased stack and improvements (by @doomwastaken) -* OFW: Stricter constness for const data (by @hedger) -* OFW PR 4017: Alarm improvements: Snooze, timeouts, and dismissing from the locked state (by @Astrrra) -* OFW: fix: flipper detected before it was rebooted -* OFW: NFC: FeliCa Protocol Expose Read Block API and Allow Specifying Service -* OFW: LFRFID: Fix Detection Conflict Between Securakey and Noralsy Format (by @zinongli) -* OFW: Stdio API improvements -* OFW: GUI: Widget view extra options for JS -* OFW: Update heap implementation -* OFW: Updated Button Panel -* OFW: UART framing mode selection -* OFW: gpio: clear irq status before calling user handler -* OFW: Fix 5V on GPIO -* OFW: Fixed repeat in subghz tx_from_file command -* OFW: LFRFID: Noralsy Format/Brand -* OFW: Faster di card reading -* OFW: vscode: disabled auto-update for clangd since correct version is in the toolchain -* OFW: Furi, USB, BLE, Debug: various bug fixes and improvements -* OFW: EventLoop unsubscribe fix -* OFW: nfc: Enable MFUL sync poller to be provided with passwords -* OFW: ST25TB poller mode check -* OFW: JS features & bugfixes (SDK 0.2) **Existing Widget JS module was removed and replaced with new ofw gui/widget module, old apps using widget may be incompatible now!** -* OFW: Infrared: increase max carrier limit -* OFW: Ensure that `furi_record_create` is passed a non-NULL data pointer -* OFW: Update mbedtls & expose AES -* OFW: Add the Showtime animation +* Docs: Some updates on subghz remotes programming

#### Known NFC post-refactor regressions list: - Mifare Mini clones reading is broken (original mini working fine) (OFW) From ef3d17ea4ecc7f306f238fdac230b3720ca6947a Mon Sep 17 00:00:00 2001 From: Mykhailo Shevchuk Date: Tue, 29 Apr 2025 00:07:54 +0300 Subject: [PATCH 05/75] Extracted TDES auth to separate method --- .../notification_settings_app.c | 2 +- .../mf_ultralight/mf_ultralight_poller.c | 26 +++------------ .../mf_ultralight/mf_ultralight_poller.h | 13 ++++++++ .../mf_ultralight/mf_ultralight_poller_i.c | 33 +++++++++++++++++++ targets/f7/api_symbols.csv | 3 +- 5 files changed, 53 insertions(+), 24 deletions(-) diff --git a/applications/settings/notification_settings/notification_settings_app.c b/applications/settings/notification_settings/notification_settings_app.c index be7af4c42..9c6957c0b 100644 --- a/applications/settings/notification_settings/notification_settings_app.c +++ b/applications/settings/notification_settings/notification_settings_app.c @@ -556,7 +556,7 @@ static void night_shift_changed(VariableItem* item) { // force demo night_shift brightness to rgb backlight and stock backlight notification_message(app->notification, &sequence_display_backlight_on); - + for(int i = 4; i < 6; i++) { VariableItem* t_item = variable_item_list_get(app->variable_item_list, i); if(index == 0) { diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index 7d51f6c6e..3eb6524ed 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -453,29 +453,11 @@ static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPol if(!instance->mfu_event.data->auth_context.skip_auth) { FURI_LOG_D(TAG, "Trying to authenticate with 3des key"); instance->auth_context.tdes_key = instance->mfu_event.data->auth_context.tdes_key; - do { - uint8_t output[MF_ULTRALIGHT_C_AUTH_DATA_SIZE]; - uint8_t RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0}; - furi_hal_random_fill_buf(RndA, sizeof(RndA)); - instance->error = mf_ultralight_poller_authenticate_start(instance, RndA, output); - if(instance->error != MfUltralightErrorNone) break; + instance->error = mf_ultralight_poller_auth_tdes(instance, &instance->auth_context); - uint8_t decoded_shifted_RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0}; - const uint8_t* RndB = output + MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET; - instance->error = mf_ultralight_poller_authenticate_end( - instance, RndB, output, decoded_shifted_RndA); - if(instance->error != MfUltralightErrorNone) break; - - mf_ultralight_3des_shift_data(RndA); - instance->auth_context.auth_success = - (memcmp(RndA, decoded_shifted_RndA, sizeof(decoded_shifted_RndA)) == 0); - - if(instance->auth_context.auth_success) { - FURI_LOG_D(TAG, "Auth success"); - } - } while(false); - - if(instance->error != MfUltralightErrorNone || !instance->auth_context.auth_success) { + if(instance->error == MfUltralightErrorNone && instance->auth_context.auth_success) { + FURI_LOG_D(TAG, "Auth success"); + } else { FURI_LOG_D(TAG, "Auth failed"); iso14443_3a_poller_halt(instance->iso14443_3a_poller); } diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h index e50017324..661c6db74 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h @@ -81,6 +81,19 @@ MfUltralightError mf_ultralight_poller_auth_pwd( MfUltralightPoller* instance, MfUltralightPollerAuthContext* data); +/** + * @brief Perform 3DES authentication with key. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in, out] data pointer to the authentication context. + * @return MfUltralightErrorNone on success, an error code on failure. + */ +MfUltralightError mf_ultralight_poller_auth_tdes( + MfUltralightPoller* instance, + MfUltralightPollerAuthContext* data); + /** * @brief Start authentication procedure. * diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c index d84377612..79c7b1d1a 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c @@ -1,6 +1,7 @@ #include "mf_ultralight_poller_i.h" #include +#include #define TAG "MfUltralightPoller" @@ -62,6 +63,38 @@ MfUltralightError mf_ultralight_poller_auth_pwd( return ret; } +MfUltralightError mf_ultralight_poller_auth_tdes( + MfUltralightPoller* instance, + MfUltralightPollerAuthContext* data) { + furi_check(instance); + furi_check(data); + + MfUltralightError ret = MfUltralightErrorNone; + + uint8_t output[MF_ULTRALIGHT_C_AUTH_DATA_SIZE]; + uint8_t RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0}; + furi_hal_random_fill_buf(RndA, sizeof(RndA)); + + ret = mf_ultralight_poller_authenticate_start(instance, RndA, output); + + if(ret != MfUltralightErrorNone) { + return ret; + } + + uint8_t decoded_shifted_RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0}; + const uint8_t* RndB = output + MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET; + ret = mf_ultralight_poller_authenticate_end(instance, RndB, output, decoded_shifted_RndA); + + if(ret != MfUltralightErrorNone) { + return ret; + } + + mf_ultralight_3des_shift_data(RndA); + data->auth_success = (memcmp(RndA, decoded_shifted_RndA, sizeof(decoded_shifted_RndA)) == 0); + + return ret; +} + static MfUltralightError mf_ultralight_poller_send_authenticate_cmd( MfUltralightPoller* instance, const uint8_t* cmd, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 7c4dca564..66b8bb703 100755 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,86.0,, +Version,+,86.2,, 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,, @@ -2741,6 +2741,7 @@ Function,+,mf_ultralight_is_equal,_Bool,"const MfUltralightData*, const MfUltral Function,+,mf_ultralight_is_page_pwd_or_pack,_Bool,"MfUltralightType, uint16_t" Function,+,mf_ultralight_load,_Bool,"MfUltralightData*, FlipperFormat*, uint32_t" Function,+,mf_ultralight_poller_auth_pwd,MfUltralightError,"MfUltralightPoller*, MfUltralightPollerAuthContext*" +Function,+,mf_ultralight_poller_auth_tdes,MfUltralightError,"MfUltralightPoller*, MfUltralightPollerAuthContext*" Function,+,mf_ultralight_poller_authenticate_end,MfUltralightError,"MfUltralightPoller*, const uint8_t*, const uint8_t*, uint8_t*" Function,+,mf_ultralight_poller_authenticate_start,MfUltralightError,"MfUltralightPoller*, const uint8_t*, uint8_t*" Function,+,mf_ultralight_poller_read_counter,MfUltralightError,"MfUltralightPoller*, uint8_t, MfUltralightCounter*" From c0e169a22940973ad503e29af3de0b79fefc3c81 Mon Sep 17 00:00:00 2001 From: Mykhailo Shevchuk Date: Tue, 29 Apr 2025 02:31:41 +0300 Subject: [PATCH 06/75] Attempt to auth with default 3DES key --- .../mf_ultralight/mf_ultralight_poller.c | 74 +++++++++++++++---- .../mf_ultralight/mf_ultralight_poller_i.c | 6 +- .../mf_ultralight/mf_ultralight_poller_i.h | 1 + targets/f7/api_symbols.csv | 2 +- 4 files changed, 65 insertions(+), 18 deletions(-) diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index 3eb6524ed..6c6e230f0 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -445,25 +445,35 @@ static NfcCommand mf_ultralight_poller_handler_auth(MfUltralightPoller* instance static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPoller* instance) { NfcCommand command = NfcCommandContinue; FURI_LOG_D(TAG, "MfulC auth"); - if(mf_ultralight_support_feature( - instance->feature_set, MfUltralightFeatureSupportAuthenticate)) { - instance->mfu_event.type = MfUltralightPollerEventTypeAuthRequest; - command = instance->callback(instance->general_event, instance->context); - if(!instance->mfu_event.data->auth_context.skip_auth) { - FURI_LOG_D(TAG, "Trying to authenticate with 3des key"); - instance->auth_context.tdes_key = instance->mfu_event.data->auth_context.tdes_key; - instance->error = mf_ultralight_poller_auth_tdes(instance, &instance->auth_context); + do { + if(mf_ultralight_support_feature( + instance->feature_set, MfUltralightFeatureSupportAuthenticate)) { + instance->mfu_event.type = MfUltralightPollerEventTypeAuthRequest; - if(instance->error == MfUltralightErrorNone && instance->auth_context.auth_success) { - FURI_LOG_D(TAG, "Auth success"); + command = instance->callback(instance->general_event, instance->context); + if(!instance->mfu_event.data->auth_context.skip_auth) { + FURI_LOG_D(TAG, "Trying to authenticate with 3des key"); + instance->auth_context.tdes_key = instance->mfu_event.data->auth_context.tdes_key; + instance->error = + mf_ultralight_poller_auth_tdes(instance, &instance->auth_context); + + if(instance->error == MfUltralightErrorNone && + instance->auth_context.auth_success) { + FURI_LOG_D(TAG, "Auth success"); + } else { + FURI_LOG_D(TAG, "Auth failed"); + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + } } else { - FURI_LOG_D(TAG, "Auth failed"); - iso14443_3a_poller_halt(instance->iso14443_3a_poller); + // We assume here that it is card read without explicitly provided key + // So we try to auth with default one + instance->state = MfUltralightPollerStateTryDefaultMfulCKey; + break; } } - } - instance->state = MfUltralightPollerStateReadPages; + instance->state = MfUltralightPollerStateReadPages; + } while(false); return command; } @@ -560,6 +570,40 @@ static NfcCommand mf_ultralight_poller_handler_try_default_pass(MfUltralightPoll return NfcCommandContinue; } +static NfcCommand + mf_ultralight_poller_handler_try_default_ultralight_c_key(MfUltralightPoller* instance) { + do { + if(!mf_ultralight_support_feature( + instance->feature_set, MfUltralightFeatureSupportAuthenticate)) { + break; + } + + if(instance->auth_context.auth_success) { + break; + } + + FURI_LOG_D(TAG, "Trying authentication with default 3DES key"); + + memcpy( + &instance->auth_context.tdes_key.data, + MF_ULTRALIGHT_C_DEFAULT_KEY, + MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE); + + instance->error = mf_ultralight_poller_auth_tdes(instance, &instance->auth_context); + + if(instance->error == MfUltralightErrorNone && instance->auth_context.auth_success) { + FURI_LOG_D(TAG, "Default 3DES key detected"); + } else { + FURI_LOG_D(TAG, "Authentication attempt with default 3DES key failed"); + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + } + + } while(false); + + instance->state = MfUltralightPollerStateReadPages; + return NfcCommandContinue; +} + static NfcCommand mf_ultralight_poller_handler_check_mfuc_auth_status(MfUltralightPoller* instance) { instance->state = MfUltralightPollerStateReadSuccess; @@ -724,6 +768,8 @@ static const MfUltralightPollerReadHandler mf_ultralight_poller_handler_read_tearing_flags, [MfUltralightPollerStateAuth] = mf_ultralight_poller_handler_auth, [MfUltralightPollerStateTryDefaultPass] = mf_ultralight_poller_handler_try_default_pass, + [MfUltralightPollerStateTryDefaultMfulCKey] = + mf_ultralight_poller_handler_try_default_ultralight_c_key, [MfUltralightPollerStateCheckMfulCAuthStatus] = mf_ultralight_poller_handler_check_mfuc_auth_status, [MfUltralightPollerStateAuthMfulC] = mf_ultralight_poller_handler_auth_ultralight_c, diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c index 79c7b1d1a..fdafaf37d 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c @@ -167,7 +167,7 @@ MfUltralightError mf_ultralight_poller_authenticate_start( uint8_t* RndB = output + MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET; mf_ultralight_3des_decrypt( &instance->des_context, - instance->mfu_event.data->auth_context.tdes_key.data, + instance->auth_context.tdes_key.data, iv, encRndB, sizeof(encRndB), @@ -178,7 +178,7 @@ MfUltralightError mf_ultralight_poller_authenticate_start( mf_ultralight_3des_encrypt( &instance->des_context, - instance->mfu_event.data->auth_context.tdes_key.data, + instance->auth_context.tdes_key.data, encRndB, output, MF_ULTRALIGHT_C_AUTH_DATA_SIZE, @@ -212,7 +212,7 @@ MfUltralightError mf_ultralight_poller_authenticate_end( mf_ultralight_3des_decrypt( &instance->des_context, - instance->mfu_event.data->auth_context.tdes_key.data, + instance->auth_context.tdes_key.data, RndB, bit_buffer_get_data(instance->rx_buffer) + 1, MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE, diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h index 6880a0c43..7db9a77d9 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h @@ -59,6 +59,7 @@ typedef enum { MfUltralightPollerStateAuthMfulC, MfUltralightPollerStateReadPages, MfUltralightPollerStateTryDefaultPass, + MfUltralightPollerStateTryDefaultMfulCKey, MfUltralightPollerStateCheckMfulCAuthStatus, MfUltralightPollerStateReadFailed, MfUltralightPollerStateReadSuccess, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 66b8bb703..101a9f1bb 100755 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,86.2,, +Version,+,86.0,, 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,, From 5a0c4e6678ba2225de76b70f563afb256cf693f2 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 29 Apr 2025 15:49:43 +0300 Subject: [PATCH 07/75] ofw pr 4205 fix sample durations when using external CC1101 https://github.com/flipperdevices/flipperzero-firmware/pull/4205/files by Aerosnail --- applications/drivers/subghz/cc1101_ext/cc1101_ext.c | 10 ++++++---- .../notification_settings/notification_settings_app.c | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index b57ace0b3..abca098eb 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -612,11 +612,13 @@ void subghz_device_cc1101_ext_start_async_rx( furi_hal_bus_enable(FuriHalBusTIM17); // Configure TIM + LL_TIM_InitTypeDef TIM_InitStruct = {0}; //Set the timer resolution to 2 us - LL_TIM_SetPrescaler(TIM17, (64 << 1) - 1); - LL_TIM_SetCounterMode(TIM17, LL_TIM_COUNTERMODE_UP); - LL_TIM_SetAutoReload(TIM17, 0xFFFF); - LL_TIM_SetClockDivision(TIM17, LL_TIM_CLOCKDIVISION_DIV1); + TIM_InitStruct.Prescaler = (64 << 1) - 1; + TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP; + TIM_InitStruct.Autoreload = 0xFFFF; + TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1; + LL_TIM_Init(TIM17, &TIM_InitStruct); // Timer: advanced LL_TIM_SetClockSource(TIM17, LL_TIM_CLOCKSOURCE_INTERNAL); diff --git a/applications/settings/notification_settings/notification_settings_app.c b/applications/settings/notification_settings/notification_settings_app.c index be7af4c42..9c6957c0b 100644 --- a/applications/settings/notification_settings/notification_settings_app.c +++ b/applications/settings/notification_settings/notification_settings_app.c @@ -556,7 +556,7 @@ static void night_shift_changed(VariableItem* item) { // force demo night_shift brightness to rgb backlight and stock backlight notification_message(app->notification, &sequence_display_backlight_on); - + for(int i = 4; i < 6; i++) { VariableItem* t_item = variable_item_list_get(app->variable_item_list, i); if(index == 0) { From 79450ff3cb62b3cfa929f35818ec4eb5c4b9d1f9 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 29 Apr 2025 15:51:35 +0300 Subject: [PATCH 08/75] upd changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2a6b29ab..98b8686ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## Main changes - Current API: 86.0 +* OFW PR 4205: fix sample durations when using external CC1101 (by @Aerosnail) * OFW PR 4206: Stop JS PWM on exit (by @portasynthinca3) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes From c1dbfd351e8d748f000a1be5f38996be031076ad Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 29 Apr 2025 18:40:27 +0300 Subject: [PATCH 09/75] upd changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98b8686ac..8e09fba3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## Main changes - Current API: 86.0 +* NFC: Ultralight C - Attempt of authentication with default key (PR #898 | by @mishamyte) * OFW PR 4205: fix sample durations when using external CC1101 (by @Aerosnail) * OFW PR 4206: Stop JS PWM on exit (by @portasynthinca3) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) From 1a4d197e380ec2c8f9b45bdd400b68adc5ddb772 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 1 May 2025 00:17:12 +0300 Subject: [PATCH 10/75] log level none after update --- applications/system/updater/util/update_task_worker_flasher.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/system/updater/util/update_task_worker_flasher.c b/applications/system/updater/util/update_task_worker_flasher.c index a464815f0..f18339847 100644 --- a/applications/system/updater/util/update_task_worker_flasher.c +++ b/applications/system/updater/util/update_task_worker_flasher.c @@ -345,7 +345,7 @@ int32_t update_task_worker_flash_writer(void* context) { furi_hal_rtc_set_flag(FuriHalRtcFlagStorageFormatInternal); #ifdef FURI_NDEBUG // Production - furi_hal_rtc_set_log_level(FuriLogLevelDefault); + furi_hal_rtc_set_log_level(FuriLogLevelNone); furi_hal_rtc_reset_flag(FuriHalRtcFlagDebug); furi_hal_rtc_reset_flag(FuriHalRtcFlagLegacySleep); furi_hal_rtc_set_heap_track_mode(FuriHalRtcHeapTrackModeNone); From f87102362b991a0731e52ab619bb003ea264bfc5 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 3 May 2025 00:30:02 +0300 Subject: [PATCH 11/75] upd changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e09fba3f..84b709b82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * OFW PR 4206: Stop JS PWM on exit (by @portasynthinca3) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes +* System: log level none after update * Docs: Some updates on subghz remotes programming

#### Known NFC post-refactor regressions list: From 70a4731874de8b0b2137ea3d06e65581214406a8 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 4 May 2025 21:10:49 +0300 Subject: [PATCH 12/75] Remove stupid "!" that broke subghz chat cli ofw pr4212 by GameLord2011 --- applications/main/subghz/subghz_cli.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index 947920964..88b5f7f0d 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -1099,7 +1099,7 @@ static void subghz_cli_command_chat(PipeSide* pipe, FuriString* args) { break; } } - if(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { + if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) { printf("\r\n"); chat_event.event = SubGhzChatEventUserExit; subghz_chat_worker_put_event_chat(subghz_chat, &chat_event); From e46c5cb650d3797ad419d026c32988218d63dcfb Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 4 May 2025 21:11:40 +0300 Subject: [PATCH 13/75] upd changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84b709b82..cbcd4866e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ * NFC: Ultralight C - Attempt of authentication with default key (PR #898 | by @mishamyte) * OFW PR 4205: fix sample durations when using external CC1101 (by @Aerosnail) * OFW PR 4206: Stop JS PWM on exit (by @portasynthinca3) +* OFW PR 4212: Remove stupid "!" that broke subghz chat cli (by @GameLord2011) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * System: log level none after update From 5ca63ff1a477b4cf40b6f2084d0c6e4d3e463ad6 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 6 May 2025 03:38:34 +0300 Subject: [PATCH 14/75] update came atomo docs --- documentation/SubGHzRemoteProg.md | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/documentation/SubGHzRemoteProg.md b/documentation/SubGHzRemoteProg.md index d9809f8cb..c02c6ee43 100644 --- a/documentation/SubGHzRemoteProg.md +++ b/documentation/SubGHzRemoteProg.md @@ -165,9 +165,34 @@ Now you will have exact clone of your remote that will have same counter, by mak 11. Also your original remote may become non working since it needs to be re-added into receiver board if you made counter much higher than original :C ## CAME Atomo +Known names are: TOP42R / TOP44R - TOP44RGR (806TS-0130) -1. Use google to find instructions - `how to program new CAME Atomo remote into receiver` -2. Watch this video to learn more (video in Russian language): https://www.youtube.com/watch?v=XeHUwfcSS30 +How to create new remote and bind it to receiver (will not conflict with original remotes): + +With original remote (or copy of the original remote): + +1. Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> CAME Atomo 433MHz or 868MHz +2. Open your new remote file +3. You need to be in minimum 3 meters to receiver +4. Original Remote: Press and hold button that is bound with that receiver (the one you use with it), and hold it for about 10 seconds. +5. You will have about 20 seconds to add new remote +6. Long press Send on Flipper in new remote for like 3-4 sec and release - this will add new remote to the receiver +7. Press and hold Send again after waiting 20 seconds - this will trigger the receiver +8. Done, when using CAME Atomo from flipper please hold Send button for at least 2 seconds to allow code to be fully transmit, flipper transmits only while button is held + +Note: Static 24/12 bit or TWEE remotes cannot trigger programming mode in the receiver and cannot be bound if programming mode was triggered by Atomo type remote, only Atomo remotes can be added if remote programming was done by Atomo remote, Static remotes have option to clone from one remote to another, but it requires first remote to be added to the receiver via button on the receiver board + +With access to receiver box: + +1. Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> CAME Atomo 433MHz or 868MHz +2. Open your new remote file +3. Open the receiver box and find programming button related to the used channel, for example RE432M/RE862M receiver has two independent channels which can have different remotes / buttons on them, when you found connected channel press "1" or "2" button on the receiver board to enter programming mode +4. Long press Send on Flipper new remote for like 3-4 sec and release - this will add new remote to the receiver +5. Click CLEAR button one time on the receiver board to exit programming mode, or wait about 20 seconds it will exit from programming mode automatically +6. Done, when using CAME Atomo from flipper please hold Send button for at least 2 seconds to allow code to be fully transmit, flipper transmits only while button is held + + +Watch this video to learn more (video in Russian language): https://www.youtube.com/watch?v=XeHUwfcSS30 ## Nice Flor S From 760079ee2c70fc02a1c7b4c3aaeec6cdbb670daa Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 6 May 2025 04:11:01 +0300 Subject: [PATCH 15/75] better came decoder --- lib/subghz/protocols/came.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/subghz/protocols/came.c b/lib/subghz/protocols/came.c index bbe3e487f..c3d9c8662 100644 --- a/lib/subghz/protocols/came.c +++ b/lib/subghz/protocols/came.c @@ -244,8 +244,11 @@ void subghz_protocol_decoder_came_feed(void* context, bool level, uint32_t durat switch(instance->decoder.parser_step) { case CameDecoderStepReset: if((!level) && (DURATION_DIFF(duration, subghz_protocol_came_const.te_short * 56) < - subghz_protocol_came_const.te_delta * 47)) { + subghz_protocol_came_const.te_delta * 63)) { + // 17920 us + 7050 us = 24970 us max possible value old one + // delta = 150 us x 63 = 9450 us + 17920 us = 27370 us max possible value //Found header CAME + // 26700 us or 24000 us max possible values instance->decoder.parser_step = CameDecoderStepFoundStartBit; } break; From fb02568fab8d02491631d1fa752535ed04824aad Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 6 May 2025 04:11:46 +0300 Subject: [PATCH 16/75] tune holtek to decode holtek only and not conflict with came 12bit --- lib/subghz/protocols/holtek_ht12x.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/subghz/protocols/holtek_ht12x.c b/lib/subghz/protocols/holtek_ht12x.c index 302b78598..be2dfd406 100644 --- a/lib/subghz/protocols/holtek_ht12x.c +++ b/lib/subghz/protocols/holtek_ht12x.c @@ -234,8 +234,10 @@ void subghz_protocol_decoder_holtek_th12x_feed(void* context, bool level, uint32 switch(instance->decoder.parser_step) { case Holtek_HT12XDecoderStepReset: - if((!level) && (DURATION_DIFF(duration, subghz_protocol_holtek_th12x_const.te_short * 36) < - subghz_protocol_holtek_th12x_const.te_delta * 36)) { + if((!level) && (DURATION_DIFF(duration, subghz_protocol_holtek_th12x_const.te_short * 28) < + subghz_protocol_holtek_th12x_const.te_delta * 20)) { + // 18720 us old max value + // 12960 us corrected max value //Found Preambula instance->decoder.parser_step = Holtek_HT12XDecoderStepFoundStartBit; } From b16bca1b60918509818aa98e2ed0a4cc08282eca Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 6 May 2025 04:13:09 +0300 Subject: [PATCH 17/75] upd changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cbcd4866e..c2c55d5f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## Main changes - Current API: 86.0 +* SubGHz: Fix CAME 24bit decoder +* SubGHz: Tune holtek ht12x to decode holtek only and not conflict with came 12bit * NFC: Ultralight C - Attempt of authentication with default key (PR #898 | by @mishamyte) * OFW PR 4205: fix sample durations when using external CC1101 (by @Aerosnail) * OFW PR 4206: Stop JS PWM on exit (by @portasynthinca3) From 6c798a5d4b505a643ca64e6c8a2df2480b13287b Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 6 May 2025 13:13:16 +0300 Subject: [PATCH 18/75] fix subghz rename scene bug (or hidden feature) --- applications/main/subghz/scenes/subghz_scene_save_name.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_save_name.c b/applications/main/subghz/scenes/subghz_scene_save_name.c index 1b89d1d4d..d31f1ce25 100644 --- a/applications/main/subghz/scenes/subghz_scene_save_name.c +++ b/applications/main/subghz/scenes/subghz_scene_save_name.c @@ -93,6 +93,9 @@ void subghz_scene_save_name_on_enter(void* context) { bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeBack) { + // Set file path to default + furi_string_set(subghz->file_path, SUBGHZ_APP_FOLDER); + // if(!(strcmp(subghz->file_name_tmp, "") == 0) || scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != SubGhzCustomEventManagerNoSet) { @@ -106,8 +109,6 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { } else { scene_manager_previous_scene(subghz->scene_manager); } - // Set file path to default - furi_string_set(subghz->file_path, SUBGHZ_APP_FOLDER); return true; } else if(event.type == SceneManagerEventTypeCustom) { From ee80ea115d1e1d5cb2ebd34b9ee078a6509cbc86 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 6 May 2025 13:14:15 +0300 Subject: [PATCH 19/75] upd changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2c55d5f6..beb90d40f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - Current API: 86.0 * SubGHz: Fix CAME 24bit decoder * SubGHz: Tune holtek ht12x to decode holtek only and not conflict with came 12bit +* SubGHz: Fix Rename scene bug, that was replacing file name with random name when Rename is opened then closed then opened again * NFC: Ultralight C - Attempt of authentication with default key (PR #898 | by @mishamyte) * OFW PR 4205: fix sample durations when using external CC1101 (by @Aerosnail) * OFW PR 4206: Stop JS PWM on exit (by @portasynthinca3) From 32d104c591ea1bd66cbeb18252dc6e555c7faf6b Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 9 May 2025 15:24:07 +0300 Subject: [PATCH 20/75] add il100 smart copy cloner support --- .../resources/subghz/assets/keeloq_mfcodes | 119 +++++++++--------- 1 file changed, 60 insertions(+), 59 deletions(-) diff --git a/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes index 97e1aab47..3f3e825af 100644 --- a/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes +++ b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes @@ -1,62 +1,63 @@ Filetype: Flipper SubGhz Keystore File Version: 0 Encryption: 1 -IV: 46 75 72 69 20 63 68 65 63 6B 20 4F 77 4F 21 3F -796353C129CC2B688FE158D36E82001F7450D58DD763BCF4D6FA1CE6C3D598EA -1E7DDEB3B54A42B8993C32AF209CBCC9A1137CE334449F016B993D673EB15C69 -F9CB2DDC4E3D45694292C7DE45F1DC0BD74235B36F624AF39E8C3D211A713408 -538F46EF4250801434FEA469EE07E8C8BF71C6179442718A05455BB501D797C4 -F2BF384DBF7F828E025F020E47D1D637B497FB470444F0C6F9DC67C6830EDDE8A26A6EE89A321D3924D9099895AB2EA1 -2CA8A8A4866D5C2B715B520F641B41355A81BA73170842233806D8AA3E3F3D62 -80CF5DEF931CF902EE602319F7CD506D42E5FBC06ABBF9F05D474C8E8A2AAC4E -15CB465F7A646226C987BB4928E92A61F350ADCDDE355B717730ADDD1B738950 -86B7597ED3EDF6826124ABD7AE419197DC4A93FA064179ACAD853EC670F93995 -28263A286F1ADA0E851E8A27AFD7706CF3513D8A24D41CF7E24A925FE3D86305 -AF29A08AB877F12681706D71B82B8E2447DA18EBF9731EF3CE91FA5ECAC4E98F8FDC817E0F67C3D7348DD2AF128F1E62 -A675C5BB6AC41336B5F5A27FC062FBF30DC39B1E5C498F0FD823261C3177FA58416C5402554742370CB0DAFD2895F6F8 -0073790C26BD0224F5560161A4D4C4F8E05498E995FD2BF12EA9926A5127E9A5 -235ADFE253B51260DCF5C66F9CB9602B91A0C57BE14D1BE1E11EB309643B29DB -F9C2E3A926AAA108F474075F0B4EDFF3135492B1529EAB150CF78E748BF36E17 -50F442CC9D90B9FF8C786245EC442B911A28A04D829D0E45D7D2D4C5A3F5B864 -9EA00CD21C0BE1BB15A0CAD31097A44CAF42868F259E8ED25F361986C26161514FA85D78F3889B4185D2A1484C1DA88C -52DFE7D9052F29071922876723C30CF6FE6247D6439590C5C7C0493120ACA096 -D5AF176D59779A92F6EFB34F271D76D60780C3EA83306326E0119CD3B0417687 -C4CC4E589CCCEC609B4DE39F926F83CCFFCC966871B9A998514910A3F59E86FB56C476DFFB4181E3D22E86A760C4C137 -50A64FEDA64B61A5C123906426EF726D98AC70C15F38245A931B5649F0944930679513D9862091275F10A804EBB610DE -E7938C4F32E571582D73F855ACC40FF0153EFBAB6D184F2DBE8EAC63C4276D92 -7667993F35F1363C0A3AEB5222F07F91904B3F6FA375BF062B269B09706764CB -9DF764EE4A70D331BA8870F4D27C3E90E811A5E306BC72701A99A0377AC7B189 -324D0FBC096A489983C45E82B2745A83A3725A87D2D2CA676A521075065D5047 -8EA1A30AE08CEBD03A52EB2512C7C69CB824E0B9BE900E25F15FAB17ADBB1188 -CE379182103FC4E0442745F6F202AB6ED8EBC051B5F5537916938D9DB8FCD6BE -752BD22AA37D030C60E48CB57309AF631682AC4A0D67C3A7D1130EB056717FB6 -9ECFE7D24BF25BD543E1EAE8116D95C110BD4514EE279BCA71234865EB9166BD -14B3FA8704BA3B284C65F1A6FD114E53B883E12EAFB24B574F84585BD20157DA -D026E1E2CA70E33291482EEFCE11123540BBF591D400C92CEBB8CAF99A9DE882 -88E618EF5E76D1F5A60926D48D3D58FF67B4A92DE6EECA271EDAFC2419AE787B -74E14AD8ADE2BC575048325D1B3990669CDB35D840169B913043DAC938862AE4 -BEB5388B6A7D5C9EF65BDD7577D1DD654B7FAECE5A4CF0937BB7D0C0C5494CD0 -761356D494E3C947CBDCA887A30071A675FE1BE6E77FC9016DB9B5659B7FF9E7 -7503725DFC7212F5F719AFC9DF29F07321511BE4896E12D1D10C68BB07C6483A2CE5FFDFD074CFC279E42E6DD860C496 -BD03D78399DE44449AEE9F00EBCABBF419EDEA1701B9979A97D57AECB5139D1E -E80EB84DF9DA2E34B78F50D6488A30F8EFD11E7C6DBEB7CFEDE83BE5CA86B6DF -9332A130138F2AC11A12030CF43EFF77E7CABE761EDE14748112E13267496CE9 -E657F3C95EEF0AF92A5C49F66BD9C053A82493C7D6267F1E7C038473AE488116 -6F37491FFE130F90B77D7E5EA4AA75A1DB0CA3644F68B6502DFC302DDB80367B -3B37C5CCDD510873628D92B352907FEFF0AC2B38C751C2E46C3B97C3E365972B -A4C845187BEC75669234EE07B839DCFF618678D2EAA5596350F0936A400099A5 -2C961EF4F2454771B2646ECBAA1D0B7DAA2FD8ECC7228037A36E24FE30C43ABAA446C1B5968C0E2DD141C55557A4CCB8 -C4E0D43E9670C2B91F6AF03D60F216625CC19C697331BC443194D2BB88E042DF -3DC5584F43061AF79907D6342DF3344435B5AA6277B33D7DC56C429D1EB81BC4 -D9186791E907CBB9EF26EDDAEA9F0DBC8D24E213E55942ED5E1790B5A55F8758 -2B54CB727BBFD8567543448D2D24B3865329F89936D3B035ED2185BA88F1DF2E -A08F0D881AF001E52FF2CF9D232A9A566EB1900B351AFEEFEE666BEC40588F64 -46A11CA89BA1998A247275EEDF504DAFC5F97B41DFF4943F531A9F8F43DF19C5 -EF70A3858E84B13DF606317381B2E9DFFC346E96AF7C1DF0001586B8BF35808F -38EAF18DB8096C7427EBD36CBC5B0E945A3286278CC0227EB056F7ACB9E450D4 -28278D1DAA263E9A45ED17F67F6B6B0CF00F8C0F58F86C8161F8D4266FE556CB -0B0C79FDD7C9EA31FAA4AA829EDDD2A3453C05A74F5B53BBEAE83E1F4913FD1C18BD235D14D06D9E567DDB273E4C1F2A -7D663A93AE1B9A2E00E944B92838DED3376E09C5179C8F3037B2EAE9E7326C2A -D64EC2C7BD8CFA152368DF6BB75D66EF24EAA9C864A1386184B793C0585D82BC -51D8EB188E833891CCD15919FAC8FE56ACAB1007699F4FDCB53A6DDAB02E5CAA -650866D34DECD1D1F3559EFD8D2A4C1DB51C005F5932608CB6062B384D7A1F59C8E3FBF2C0A5AEFFB631D7B88A630AAB +IV: 4E 6F 20 66 75 72 69 20 63 68 65 63 6B 3F 21 3F +2F0767B5B190608EB032D12BFA937D760A77D08D37F851E940767F1915E97ACF +332F8DCCFDBF0485EC2EEED0C279F277E52A86A93BC5E4E96BE5F7276CC66713 +D9A02CC785FC0495063C424B0B1BAE7C120A2C24D4C0EE743F5D216718B16490 +4D9DD617090BDB100986B6987CAAC3652D2ADAB1AD9E368C5806D98562FF6B2F +28D21748FF3826FA13C785A6721CC5927C81EDAB0C5CF31C92EAFF12AA91608298485D8A3AB443640237372ADF0DDC49 +5058E12C0A41EDCB5C0812554F619DADFB6E895B94421952ECD9255A04EE5E1A +83A3EB8B22D94487A6B0F37856FB6AE9F42272BF25E1AE06DE03AA881A12D15F +D0E207DE64402B43ECD0C341216B6BCDC449508116E81D8ACDE7FA0BFBEA56F7 +6C4F723DE3B775D4C07E12ED3C369250B4D2089ADE2207816DED130D4B498CDF +B041911C56555E5F4676BF16819F61BF7A92402EB0427B8C2E7367B0AEA6B53C +1AD460260F20146A763BF6D4CD26DF5139EE29FFF8B53F6C5367EA779E1BEE56D5DFD872EA0268FE27204175925079AA +B1A9331AED36137CD078536A67775E2880D3CD7305373BC44A5649435E466AD2DC9FDE8AC1F572EF094D4B438C9509EA +105819300A9152F16E3478151799ECBBB7CCCE63DADA3F6C6D16D46830E1E035 +354186E04BC90D672F76A427FC1CD35C2EFAE8D4D1C36247FFB71ACB862A3601 +84B533148282D0D8121E5BBBBD39DE16F398365B015E02417ECC535C88EB9C57 +E899C9DC779F82E798EE923D257D5F051E1254DCDA2A6A955882727AA8E98ED8 +B8EC34F9B75E61D34E9C075A5316FAFC395E8FBA4F76B215620C5B5C76C53DB7BF316E53582161AD20F64CAB8E3347B2 +966C3B0873F48889B51D7D9DACC0674CBC95B06E231E30F235C826285D529A54 +370DED014764D7278E342D3AB631AB37D8F8A8FAE08987E03D7FC279DEEEB913 +2318A2DA42EEA2A35FFC9BDFBB244DF3FF6933010D74B3465336C0E37FFDC48A200568F8D6003AB215388734B8AC1F20 +475B35437FECEE9792F53A671252E78566AA9894DE7A4DEC9AED70834864E804E87478009F424CE1424C00F162BB03C5 +01CE6251ED9682BA6366075081167196CD740D346C4DAC4E0012C7951C475AE7 +CB225891F937CA491B711AA942B04C61C7CFA6A8E912D91D0906B401737E03B4 +F35D279815DEF19C9C5BC9ED55E7C1A0366E37DCD0A93E6E168B5A2602201C7B +3569D8DF2490797D40978F8325B94CC707383FEA1B46240BFDAECFEFB1F8176D +3D7BAF13573BBF3102757C68D52236638CC57126FF3795A604CFFA2D3F1B9C26 +B9102C87D7DBCF35463F38B6B80B70408968B6E01A47F6A7E8A3E87A0577B4ED +7673FAC14D94ABF72800A78E2DC4CAF2166FBB24719C22CFC1010492F4C87734 +1AF74DA07EA3A418EB86BB7ABAD6192B8E5A53F61B3E74CB898CB3EE4A7E244A +832D18C44062DDE856384E19D1417FA48D809C2CB2107CDEC5281943559791A6 +CD482A8FAB2A2CBE25A0B4A4788F274CA7095AA24508C00DBB78DD12BFB11C37 +EAC52E802DB76B51058752D7EFA91BCB1212AB96B589F9A88465195C1DE3242E +96CC75952A513AB5FE62A69AB6CDDA93C2156A3EA607C25B3201CE7284B3DAA9 +986E71EE87E860192141A1453929E575706E3FE72B7A9FEF5ACA696388649EB6 +FFF89FECC1C01FA3F266B95BDEF61A16F514E59599DAA07E908C604E9FE686C0 +ACC159D4AE78E26B5A1468F69D961028D0BF962D03E865415E7FE746553FEF15 +0FF46B2F9D4E907B9924675081D17C38C09957AA2F4C3C1F5568461DBA850F6301328CDC0FCEE83C7E8BA00CF8FC0F97 +7FD793C05E499739C3C4F8CC1D2D205A55928AB5BC60752A81D86DFBE30C50BD +CE444F4A1BEB38C9E93579E1B7FB3E90B4F85D8DA94DFC622272DED093A35192 +C7C31D8AB9D717FAF842F850A3E0D6B404EB9A980D9679362ABA90594B1957AB +1D48A6CFFBB6F14DD5BED1F8E74E6CC0292150546EDD76361792677434A8FE5F +F7335B8877DDF17F0737ECF77E2E6286E78534CE28E72077360E8E24D7750DFE +51051D9A8D5941661EBCF437B4640E6DA0B9B27518D761F3EF26BF7EABC691D4 +79F279733E18393FEDB50D654A0D0A532A64BED5ACBD13319439EEC007BC359C +646666FDB75D439C0427A9E3EF47F145DBD4FF5FE2E244909D74F075B24FF5A9B47E7AF98271210057D937A0E4B1F46D +DE7E814A2BD4D8823D9F2380EFAFFA1380A90391F87CBF24CE46BD46205EABAB +1335C4C3E819E942F5C212E9BEFAF5D984316C0A2FF6E9886886B565625618A9 +65386F906F18FF9C3A20AB57F3241D4975FE312ACDEB7FB1B91F2B816CAA46E7 +DF8A8B33782D56667F4C98F8F91B49B71A9E83AF015D8841986D41663233A0DC +27264455248878BB226FA1DED0922BD10313FF65F8A6A0E3CCDFB77890C838BB +43A08F784F36A3E8049BA63A401F3F15B3CA2ED263F8638595B5F22A0B081369 +F9F82F89C15AD970320E3D7A83B272EB00CD0ED657E4D230AB33C995859EA77F +70AD020D172E18E1011DF88E5F934F03F34DCE8148F8053B4FFA6F92CAC9FC93 +2B845F67BAB432CED64F2D68454A2B4B3BC46FFDC2A16D3340360C7BEA110BBB +B85F16A2370B278FDB3C85E6455B8DA239D6413B727839DEFBCB5628A6C747266291AB9D9F4F5DA1826B219C1A29F956 +FFB7B10D96F241FDB994008AF85EC85D147A97AA599D05F5EE1BB2FC27644A26 +0BD42CA312CBBCAE556AA0159EC2CC2FA70BBB00D8DF7B63BBEA60A282481AED +9CC73810056A21EA6F311B01BA7F44655A075D1F60947FBC9B6924C3BD0ED819 +024FCB96977ECA1C0D4B9C7C461361329D96E5AFF315124FEFC0DF2A400DE312F45D602DB40CD4EB088F144EB0B8DF41 From 341246038ac917b133006e85ff5d1d172fffd300 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 9 May 2025 15:25:11 +0300 Subject: [PATCH 21/75] upd changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index beb90d40f..02d49c03e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## Main changes - Current API: 86.0 +* SubGHz: Add keeloq ironlogic (aka il100) smart clone cloners support * SubGHz: Fix CAME 24bit decoder * SubGHz: Tune holtek ht12x to decode holtek only and not conflict with came 12bit * SubGHz: Fix Rename scene bug, that was replacing file name with random name when Rename is opened then closed then opened again From d5c6933b3538e5f123e18760672ad6f21075b16c Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 9 May 2025 15:29:10 +0300 Subject: [PATCH 22/75] upd readme [ci skip] --- CHANGELOG.md | 2 +- ReadMe.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02d49c03e..fd2192f53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## Main changes - Current API: 86.0 -* SubGHz: Add keeloq ironlogic (aka il100) smart clone cloners support +* SubGHz: Add keeloq ironlogic (aka il100) smart clone cloners support (thanks to Vitaly for RAWs) * SubGHz: Fix CAME 24bit decoder * SubGHz: Tune holtek ht12x to decode holtek only and not conflict with came 12bit * SubGHz: Fix Rename scene bug, that was replacing file name with random name when Rename is opened then closed then opened again diff --git a/ReadMe.md b/ReadMe.md index 6ee86e881..2711ea794 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -155,7 +155,7 @@ Thanks to Official team (to their SubGHz Developer, Skorp) for implementing supp > | Came_Space | FAAC_RC,XT | Kingates_Stylo4k | Pantera | Tomahawk_TZ-9030 | > | Cenmax | FAAC_SLH | KGB/Subaru | Pantera_CLK | Tomahawk_Z,X_3-5 | > | Cenmax_St-5 | Faraon | Leopard | Pantera_XS/Jaguar | ZX-730-750-1055 | -> | Cenmax_St-7 | Genius_Bravo | Magic_1 | Partisan_RX | | +> | Cenmax_St-7 | Genius_Bravo | Magic_1 | Partisan_RX | IL-100(Smart) | > | Centurion | Gibidi | Magic_2 | Reff | | > | Monarch | Jolly Motors | Magic_3 | Sheriff | | > @@ -173,7 +173,7 @@ Thanks to Official team (to their SubGHz Developer, Skorp) for implementing supp - Hay21 (dynamic 21 bit) with button parsing - 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 !), Hormann EcoStar, Novoferm, Sommer, Monarch (thanks @ashphx !), Jolly Motors (thanks @pkooiman !) +- 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 !), Hormann EcoStar, Novoferm, Sommer, Monarch (thanks @ashphx !), Jolly Motors (thanks @pkooiman !), IL-100(Smart) (thx Vitaly for RAWs)
From c5d077fc7a73893d67958c984483347e2d0b0741 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 11 May 2025 19:38:34 +0300 Subject: [PATCH 23/75] various fixes by WillyJL --- .../main/bad_usb/helpers/ducky_script.c | 4 +--- applications/main/nfc/nfc_cli.c | 19 ++----------------- lib/toolbox/cli/cli_registry.c | 4 ++-- 3 files changed, 5 insertions(+), 22 deletions(-) diff --git a/applications/main/bad_usb/helpers/ducky_script.c b/applications/main/bad_usb/helpers/ducky_script.c index a64629af2..43621de78 100644 --- a/applications/main/bad_usb/helpers/ducky_script.c +++ b/applications/main/bad_usb/helpers/ducky_script.c @@ -213,9 +213,7 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) { // Main key char next_char = *line_cstr; - uint16_t main_key = ducky_get_keycode_by_name(line_cstr); - if(!main_key && next_char) main_key = BADUSB_ASCII_TO_KEY(bad_usb, next_char); - key = modifiers | main_key; + key = modifiers | ducky_get_keycode(bad_usb, line_cstr, true); if(key == 0 && next_char) ducky_error(bad_usb, "No keycode defined for %s", line_cstr); diff --git a/applications/main/nfc/nfc_cli.c b/applications/main/nfc/nfc_cli.c index 5b54d38db..2b4cdf6d3 100644 --- a/applications/main/nfc/nfc_cli.c +++ b/applications/main/nfc/nfc_cli.c @@ -7,7 +7,6 @@ #include #include #include -#include #include #include @@ -15,13 +14,12 @@ #define FLAG_EVENT (1 << 10) #define NFC_MAX_BUFFER_SIZE (256) -#define NFC_BASE_PROTOCOL_MAX (3) +#define NFC_BASE_PROTOCOL_MAX (2) #define POLLER_DONE (1 << 0) #define POLLER_ERR (1 << 1) static NfcProtocol BASE_PROTOCOL[NFC_BASE_PROTOCOL_MAX] = { NfcProtocolIso14443_4a, - NfcProtocolIso14443_4b, - NfcProtocolIso15693_3}; + NfcProtocolIso14443_4b}; typedef struct ApduContext { BitBuffer* tx_buffer; BitBuffer* rx_buffer; @@ -88,19 +86,6 @@ static NfcCommand trx_callback(NfcGenericEvent event, void* context) { furi_thread_flags_set(apdu_context->thread_id, POLLER_ERR); return NfcCommandStop; } - } else if(NfcProtocolIso15693_3 == event.protocol) { - Iso15693_3Error err = iso15693_3_poller_send_frame( - event.instance, - apdu_context->tx_buffer, - apdu_context->rx_buffer, - ISO15693_3_FDT_POLL_FC); - if(Iso15693_3ErrorNone == err) { - furi_thread_flags_set(apdu_context->thread_id, POLLER_DONE); - return NfcCommandContinue; - } else { - furi_thread_flags_set(apdu_context->thread_id, POLLER_ERR); - return NfcCommandStop; - } } else { // should never reach here furi_crash("Unknown protocol"); diff --git a/lib/toolbox/cli/cli_registry.c b/lib/toolbox/cli/cli_registry.c index 91f7c4046..45661ee90 100644 --- a/lib/toolbox/cli/cli_registry.c +++ b/lib/toolbox/cli/cli_registry.c @@ -136,9 +136,9 @@ void cli_registry_reload_external_commands( FURI_LOG_T(TAG, "Plugin: %s", plugin_filename); furi_string_set_str(plugin_name, plugin_filename); - furi_check(furi_string_end_with_str(plugin_name, ".fal")); + if(!furi_string_end_with_str(plugin_name, ".fal")) continue; furi_string_replace_all_str(plugin_name, ".fal", ""); - furi_check(furi_string_start_with_str(plugin_name, config->fal_prefix)); + if(!furi_string_start_with_str(plugin_name, config->fal_prefix)) continue; furi_string_replace_at(plugin_name, 0, strlen(config->fal_prefix), ""); CliRegistryCommand command = { From 604141cac3d67e7b18c0409749dbdf90fa10d9cd Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 11 May 2025 19:44:29 +0300 Subject: [PATCH 24/75] update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd2192f53..201cd4d6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ * OFW PR 4212: Remove stupid "!" that broke subghz chat cli (by @GameLord2011) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes +* CLI: Various fixes (by @WillyJL) +* BadUSB: Fix key combos main keys being case sensitive (by @WillyJL) * System: log level none after update * Docs: Some updates on subghz remotes programming

From 78e2adbf5c9123cd2c7ffe96fe23d96a4bad6ea3 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 12 May 2025 01:06:13 +0300 Subject: [PATCH 25/75] fix desktop anim switch override by favourites --- applications/services/desktop/views/desktop_view_main.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/applications/services/desktop/views/desktop_view_main.c b/applications/services/desktop/views/desktop_view_main.c index 9ea144364..73210a5af 100644 --- a/applications/services/desktop/views/desktop_view_main.c +++ b/applications/services/desktop/views/desktop_view_main.c @@ -73,8 +73,9 @@ bool desktop_main_input_callback(InputEvent* event, void* context) { } else if(event->key == InputKeyOk) { if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { main_view->callback(DesktopAnimationEventNewIdleAnimation, main_view->context); + } else { + main_view->callback(DesktopMainEventOpenFavoriteOkLong, main_view->context); } - main_view->callback(DesktopMainEventOpenFavoriteOkLong, main_view->context); } } } else { From 02bc2f0ce79d3786b1ed860a2818c6927ac184be Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 12 May 2025 01:06:44 +0300 Subject: [PATCH 26/75] upd changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 201cd4d6d..e1aa07dc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * OFW PR 4212: Remove stupid "!" that broke subghz chat cli (by @GameLord2011) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes +* Desktop: DEBUG - fix desktop anim switch override by favourite apps * CLI: Various fixes (by @WillyJL) * BadUSB: Fix key combos main keys being case sensitive (by @WillyJL) * System: log level none after update From e108405fe42d2fe343ddc00cc8d897e6fc5bb20d Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 14 May 2025 01:55:18 +0300 Subject: [PATCH 27/75] add 462750000 to default subghz freqs --- .../main/subghz/helpers/subghz_frequency_analyzer_worker.c | 3 ++- documentation/SubGHzSettings.md | 1 + lib/subghz/subghz_setting.c | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c index 81b785ab9..40660b5fe 100644 --- a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c +++ b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c @@ -118,7 +118,8 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { for(size_t i = 0; i < subghz_setting_get_frequency_count(instance->setting); i++) { uint32_t current_frequency = subghz_setting_get_frequency(instance->setting, i); if(furi_hal_subghz_is_frequency_valid(current_frequency) && - (((current_frequency != 467750000) && (current_frequency != 464000000)) && + (((current_frequency != 462750000) && (current_frequency != 467750000) && + (current_frequency != 464000000)) && (current_frequency <= 920000000))) { furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); cc1101_switch_to_idle(&furi_hal_spi_bus_handle_subghz); diff --git a/documentation/SubGHzSettings.md b/documentation/SubGHzSettings.md index a93618778..95e19bdbf 100644 --- a/documentation/SubGHzSettings.md +++ b/documentation/SubGHzSettings.md @@ -60,6 +60,7 @@ if you need your custom one, make sure it doesn't listed here 434775000, /* LPD433 last channels */ 438900000, 440175000, + 462750000, 464000000, 467750000, diff --git a/lib/subghz/subghz_setting.c b/lib/subghz/subghz_setting.c index ab8c77910..8f41f576c 100644 --- a/lib/subghz/subghz_setting.c +++ b/lib/subghz/subghz_setting.c @@ -66,6 +66,7 @@ static const uint32_t subghz_frequency_list[] = { 434775000, /* LPD433 last channels */ 438900000, 440175000, + 462750000, 464000000, 467750000, From 069d0435b1a709d098621bc0331094f10f68e54b Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 14 May 2025 01:55:55 +0300 Subject: [PATCH 28/75] upd changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1aa07dc3..0a294a927 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - Current API: 86.0 * SubGHz: Add keeloq ironlogic (aka il100) smart clone cloners support (thanks to Vitaly for RAWs) * SubGHz: Fix CAME 24bit decoder +* SubGHz: Add 462.750 MHz to default subghz freqs list * SubGHz: Tune holtek ht12x to decode holtek only and not conflict with came 12bit * SubGHz: Fix Rename scene bug, that was replacing file name with random name when Rename is opened then closed then opened again * NFC: Ultralight C - Attempt of authentication with default key (PR #898 | by @mishamyte) From e538bd7c8e6d6f245f4f1cbfdfcb41fb0a3715e7 Mon Sep 17 00:00:00 2001 From: Methodius Date: Wed, 14 May 2025 16:32:17 +0300 Subject: [PATCH 29/75] tm01x dallas write support --- lib/ibutton/protocols/blanks/rw1990.c | 4 ++ lib/ibutton/protocols/blanks/tm01x.c | 58 +++++++++++++++++++ lib/ibutton/protocols/blanks/tm01x.h | 7 +++ lib/ibutton/protocols/blanks/tm2004.c | 2 + .../protocols/dallas/protocol_ds1990.c | 5 +- lib/one_wire/one_wire_host.c | 46 +++++++++++---- lib/one_wire/one_wire_host.h | 4 ++ targets/f18/api_symbols.csv | 4 +- targets/f7/api_symbols.csv | 4 +- 9 files changed, 120 insertions(+), 14 deletions(-) create mode 100644 lib/ibutton/protocols/blanks/tm01x.c create mode 100644 lib/ibutton/protocols/blanks/tm01x.h diff --git a/lib/ibutton/protocols/blanks/rw1990.c b/lib/ibutton/protocols/blanks/rw1990.c index d8017ca83..6edb4777c 100644 --- a/lib/ibutton/protocols/blanks/rw1990.c +++ b/lib/ibutton/protocols/blanks/rw1990.c @@ -38,6 +38,8 @@ static bool rw1990_read_and_compare(OneWireHost* host, const uint8_t* data, size } bool rw1990_write_v1(OneWireHost* host, const uint8_t* data, size_t data_size) { + onewire_host_set_timings_default(host); + // Unlock sequence onewire_host_reset(host); onewire_host_write(host, RW1990_1_CMD_WRITE_RECORD_FLAG); @@ -67,6 +69,8 @@ bool rw1990_write_v1(OneWireHost* host, const uint8_t* data, size_t data_size) { } bool rw1990_write_v2(OneWireHost* host, const uint8_t* data, size_t data_size) { + onewire_host_set_timings_default(host); + // Unlock sequence onewire_host_reset(host); onewire_host_write(host, RW1990_2_CMD_WRITE_RECORD_FLAG); diff --git a/lib/ibutton/protocols/blanks/tm01x.c b/lib/ibutton/protocols/blanks/tm01x.c new file mode 100644 index 000000000..6bdcb43d0 --- /dev/null +++ b/lib/ibutton/protocols/blanks/tm01x.c @@ -0,0 +1,58 @@ +#include +#include +#include +#include "tm01x.h" + +// Commands for TM01x +#define TM01X_CMD_WRITE_FLAG 0xC1 +#define TM01X_CMD_WRITE_ROM 0xC5 +#define TM01X_CMD_READ_ROM 0x33 + +#define TM01X_CMD_FINALIZE_CYFRAL 0xCA +#define TM01X_CMD_FINALIZE_METAKOM 0xCB + +static void tm01x_write_byte(OneWireHost* host, uint8_t value) { + for(uint8_t bitMask = 0x01; bitMask; bitMask <<= 1) { + onewire_host_write_bit(host, (bool)(bitMask & value)); + furi_delay_us(5000); // 5ms pause after each bit + } +} + +// Helper function to read and verify written data +static bool tm01x_read_and_verify(OneWireHost* host, const uint8_t* data, size_t data_size) { + bool success = false; + + if(onewire_host_reset(host)) { + success = true; + onewire_host_write(host, TM01X_CMD_READ_ROM); + + for(size_t i = 0; i < data_size; ++i) { + if(data[i] != onewire_host_read(host)) { + success = false; + break; + } + } + } + + return success; +} + +bool tm01x_write_dallas(OneWireHost* host, const uint8_t* data, size_t data_size) { + // Set TM01x specific timings + onewire_host_set_timings_tm01x(host); + + // Write sequence + onewire_host_reset(host); + onewire_host_write(host, TM01X_CMD_WRITE_FLAG); + onewire_host_write_bit(host, true); + furi_delay_us(5000); + + onewire_host_reset(host); + onewire_host_write(host, TM01X_CMD_WRITE_ROM); + + for(size_t i = 0; i < data_size; ++i) { + tm01x_write_byte(host, data[i]); + } + + return tm01x_read_and_verify(host, data, data_size); +} diff --git a/lib/ibutton/protocols/blanks/tm01x.h b/lib/ibutton/protocols/blanks/tm01x.h new file mode 100644 index 000000000..1d66e215b --- /dev/null +++ b/lib/ibutton/protocols/blanks/tm01x.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include + +// Function to write Dallas protocol to TM01x +bool tm01x_write_dallas(OneWireHost* host, const uint8_t* data, size_t data_size); diff --git a/lib/ibutton/protocols/blanks/tm2004.c b/lib/ibutton/protocols/blanks/tm2004.c index a275dda0a..a93b69410 100644 --- a/lib/ibutton/protocols/blanks/tm2004.c +++ b/lib/ibutton/protocols/blanks/tm2004.c @@ -9,6 +9,8 @@ #define TM2004_ANSWER_READ_MEMORY 0xF5 bool tm2004_write(OneWireHost* host, const uint8_t* data, size_t data_size) { + onewire_host_set_timings_default(host); + onewire_host_reset(host); onewire_host_write(host, TM2004_CMD_WRITE_ROM); // Starting writing from address 0x0000 diff --git a/lib/ibutton/protocols/dallas/protocol_ds1990.c b/lib/ibutton/protocols/dallas/protocol_ds1990.c index 5ed2171c6..44fd60192 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1990.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1990.c @@ -7,7 +7,7 @@ #include "../blanks/rw1990.h" #include "../blanks/tm2004.h" - +#include "../blanks/tm01x.h" #define DS1990_FAMILY_CODE 0x01U #define DS1990_FAMILY_NAME "DS1990" @@ -66,7 +66,8 @@ bool dallas_ds1990_write_id(OneWireHost* host, iButtonProtocolData* protocol_dat return rw1990_write_v1(host, data->rom_data.bytes, sizeof(DallasCommonRomData)) || rw1990_write_v2(host, data->rom_data.bytes, sizeof(DallasCommonRomData)) || - tm2004_write(host, data->rom_data.bytes, sizeof(DallasCommonRomData)); + tm2004_write(host, data->rom_data.bytes, sizeof(DallasCommonRomData)) || + tm01x_write_dallas(host, data->rom_data.bytes, sizeof(DallasCommonRomData)); } static bool dallas_ds1990_reset_callback(bool is_short, void* context) { diff --git a/lib/one_wire/one_wire_host.c b/lib/one_wire/one_wire_host.c index f3f3d953e..62d325cf5 100644 --- a/lib/one_wire/one_wire_host.c +++ b/lib/one_wire/one_wire_host.c @@ -8,16 +8,16 @@ #include "one_wire_host.h" typedef struct { - uint16_t a; - uint16_t b; - uint16_t c; - uint16_t d; - uint16_t e; - uint16_t f; - uint16_t g; - uint16_t h; - uint16_t i; - uint16_t j; + uint16_t a; // Write 1 low time + uint16_t b; // Write 1 high time + uint16_t c; // Write 0 low time + uint16_t d; // Write 0 high time + uint16_t e; // Read low time + uint16_t f; // Read high time + uint16_t g; // Reset pre-delay + uint16_t h; // Reset pulse + uint16_t i; // Presence detect + uint16_t j; // Reset post-delay } OneWireHostTimings; static const OneWireHostTimings onewire_host_timings_normal = { @@ -46,6 +46,20 @@ static const OneWireHostTimings onewire_host_timings_overdrive = { .j = 40, }; +// TM01x specific timings +static const OneWireHostTimings onewire_host_timings_tm01x = { + .a = 5, + .b = 80, + .c = 70, + .d = 10, + .e = 5, + .f = 70, + .g = 0, + .h = 740, + .i = 140, + .j = 410, +}; + struct OneWireHost { const GpioPin* gpio_pin; const OneWireHostTimings* timings; @@ -354,3 +368,15 @@ void onewire_host_set_overdrive(OneWireHost* host, bool set) { host->timings = set ? &onewire_host_timings_overdrive : &onewire_host_timings_normal; } + +void onewire_host_set_timings_default(OneWireHost* host) { + furi_check(host); + + host->timings = &onewire_host_timings_normal; +} + +void onewire_host_set_timings_tm01x(OneWireHost* host) { + furi_check(host); + + host->timings = &onewire_host_timings_tm01x; +} diff --git a/lib/one_wire/one_wire_host.h b/lib/one_wire/one_wire_host.h index 9f9bd4ffd..e61dc63e2 100644 --- a/lib/one_wire/one_wire_host.h +++ b/lib/one_wire/one_wire_host.h @@ -125,6 +125,10 @@ bool onewire_host_search(OneWireHost* host, uint8_t* new_addr, OneWireHostSearch */ void onewire_host_set_overdrive(OneWireHost* host, bool set); +void onewire_host_set_timings_default(OneWireHost* host); + +void onewire_host_set_timings_tm01x(OneWireHost* host); + #ifdef __cplusplus } #endif diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 23c9edb63..634ab079e 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,86.0,, +Version,+,86.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,, @@ -2281,6 +2281,8 @@ Function,+,onewire_host_reset,_Bool,OneWireHost* Function,+,onewire_host_reset_search,void,OneWireHost* Function,+,onewire_host_search,_Bool,"OneWireHost*, uint8_t*, OneWireHostSearchMode" Function,+,onewire_host_set_overdrive,void,"OneWireHost*, _Bool" +Function,+,onewire_host_set_timings_default,void,OneWireHost* +Function,+,onewire_host_set_timings_tm01x,void,OneWireHost* Function,+,onewire_host_start,void,OneWireHost* Function,+,onewire_host_stop,void,OneWireHost* Function,+,onewire_host_target_search,void,"OneWireHost*, uint8_t" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index d142a6374..17e4fd37e 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,86.0,, +Version,+,86.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,, @@ -2919,6 +2919,8 @@ Function,+,onewire_host_reset,_Bool,OneWireHost* Function,+,onewire_host_reset_search,void,OneWireHost* Function,+,onewire_host_search,_Bool,"OneWireHost*, uint8_t*, OneWireHostSearchMode" Function,+,onewire_host_set_overdrive,void,"OneWireHost*, _Bool" +Function,+,onewire_host_set_timings_default,void,OneWireHost* +Function,+,onewire_host_set_timings_tm01x,void,OneWireHost* Function,+,onewire_host_start,void,OneWireHost* Function,+,onewire_host_stop,void,OneWireHost* Function,+,onewire_host_target_search,void,"OneWireHost*, uint8_t" From fdab87795fdab40bb1a012d1aa8c258873c75c31 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 14 May 2025 18:37:22 +0300 Subject: [PATCH 30/75] upd changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a294a927..cfc073b9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## Main changes - Current API: 86.0 +* iButton: TM01x Dallas write support (PR #899 | by @Leptopt1los) * SubGHz: Add keeloq ironlogic (aka il100) smart clone cloners support (thanks to Vitaly for RAWs) * SubGHz: Fix CAME 24bit decoder * SubGHz: Add 462.750 MHz to default subghz freqs list From 5471b211d2a8691e7871875d0acada6f4c4b9e81 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 16 May 2025 17:48:35 +0300 Subject: [PATCH 31/75] Infrared: Add text scroll to remote buttons OFW PR 4210 by 956MB https://github.com/flipperdevices/flipperzero-firmware/pull/4210/files --- .../services/gui/modules/button_menu.c | 116 +++++++++++++----- 1 file changed, 88 insertions(+), 28 deletions(-) diff --git a/applications/services/gui/modules/button_menu.c b/applications/services/gui/modules/button_menu.c index d9c178dd2..5204c6f22 100644 --- a/applications/services/gui/modules/button_menu.c +++ b/applications/services/gui/modules/button_menu.c @@ -10,6 +10,7 @@ #include #include +#define SCROLL_INTERVAL (333) #define ITEM_FIRST_OFFSET 17 #define ITEM_NEXT_OFFSET 4 #define ITEM_HEIGHT 14 @@ -35,13 +36,56 @@ typedef struct { ButtonMenuItemArray_t items; size_t position; const char* header; + size_t scroll_counter; + FuriTimer* scroll_timer; } ButtonMenuModel; +static void button_menu_draw_text( + Canvas* canvas, + uint8_t item_x, + uint8_t item_y, + const char* text, + bool selected, + ButtonMenuModel* model) { + FuriString* disp_str; + disp_str = furi_string_alloc_set(text); + bool draw_static = true; + + if(selected) { + size_t text_width = canvas_string_width(canvas, furi_string_get_cstr(disp_str)); + if(text_width >= ITEM_WIDTH - 8) { + elements_scrollable_text_line( + canvas, + item_x + 4, + item_y + ITEM_HEIGHT - 4, + ITEM_WIDTH - 8, + disp_str, + model->scroll_counter, + false); + draw_static = false; + } + } + + if(draw_static) { + elements_string_fit_width(canvas, disp_str, ITEM_WIDTH - 6); + canvas_draw_str_aligned( + canvas, + item_x + (ITEM_WIDTH / 2), + item_y + (ITEM_HEIGHT / 2), + AlignCenter, + AlignCenter, + furi_string_get_cstr(disp_str)); + } + + furi_string_free(disp_str); +} + static void button_menu_draw_control_button( Canvas* canvas, uint8_t item_position, const char* text, - bool selected) { + bool selected, + ButtonMenuModel* model) { furi_assert(canvas); furi_assert(text); @@ -54,20 +98,16 @@ static void button_menu_draw_control_button( elements_slightly_rounded_box(canvas, item_x, item_y, ITEM_WIDTH, ITEM_HEIGHT); canvas_set_color(canvas, ColorWhite); } - canvas_draw_str_aligned( - canvas, - item_x + (ITEM_WIDTH / 2), - item_y + (ITEM_HEIGHT / 2), - AlignCenter, - AlignCenter, - text); + + button_menu_draw_text(canvas, item_x, item_y, text, selected, model); } static void button_menu_draw_common_button( Canvas* canvas, uint8_t item_position, const char* text, - bool selected) { + bool selected, + ButtonMenuModel* model) { furi_assert(canvas); furi_assert(text); @@ -83,19 +123,7 @@ static void button_menu_draw_common_button( canvas_draw_rframe(canvas, item_x, item_y, ITEM_WIDTH, ITEM_HEIGHT, 5); } - FuriString* disp_str; - disp_str = furi_string_alloc_set(text); - elements_string_fit_width(canvas, disp_str, ITEM_WIDTH - 6); - - canvas_draw_str_aligned( - canvas, - item_x + (ITEM_WIDTH / 2), - item_y + (ITEM_HEIGHT / 2), - AlignCenter, - AlignCenter, - furi_string_get_cstr(disp_str)); - - furi_string_free(disp_str); + button_menu_draw_text(canvas, item_x, item_y, text, selected, model); } static void button_menu_view_draw_callback(Canvas* canvas, void* _model) { @@ -120,9 +148,17 @@ static void button_menu_view_draw_callback(Canvas* canvas, void* _model) { if(model->header) { FuriString* disp_str; disp_str = furi_string_alloc_set(model->header); - elements_string_fit_width(canvas, disp_str, ITEM_WIDTH - 6); - canvas_draw_str_aligned( - canvas, 32, 10, AlignCenter, AlignCenter, furi_string_get_cstr(disp_str)); + size_t header_width = canvas_string_width(canvas, furi_string_get_cstr(disp_str)); + + if(header_width >= ITEM_WIDTH - 8) { + elements_scrollable_text_line( + canvas, 3, 13, ITEM_WIDTH - 8, disp_str, model->scroll_counter, false); + } else { + elements_string_fit_width(canvas, disp_str, ITEM_WIDTH - 8); + canvas_draw_str_aligned( + canvas, 32, 10, AlignCenter, AlignCenter, furi_string_get_cstr(disp_str)); + } + furi_string_free(disp_str); } @@ -137,13 +173,15 @@ static void button_menu_view_draw_callback(Canvas* canvas, void* _model) { canvas, item_position % BUTTONS_PER_SCREEN, ButtonMenuItemArray_cref(it)->label, - (item_position == model->position)); + (item_position == model->position), + model); } else if(ButtonMenuItemArray_cref(it)->type == ButtonMenuItemTypeCommon) { button_menu_draw_common_button( canvas, item_position % BUTTONS_PER_SCREEN, ButtonMenuItemArray_cref(it)->label, - (item_position == model->position)); + (item_position == model->position), + model); } } } @@ -158,8 +196,10 @@ static void button_menu_process_up(ButtonMenu* button_menu) { { if(model->position > 0) { model->position--; + model->scroll_counter = 0; } else { model->position = ButtonMenuItemArray_size(model->items) - 1; + model->scroll_counter = 0; } }, true); @@ -174,8 +214,10 @@ static void button_menu_process_down(ButtonMenu* button_menu) { { if(model->position < (ButtonMenuItemArray_size(model->items) - 1)) { model->position++; + model->scroll_counter = 0; } else { model->position = 0; + model->scroll_counter = 0; } }, true); @@ -193,8 +235,10 @@ static void button_menu_process_right(ButtonMenu* button_menu) { position_candidate -= position_candidate % BUTTONS_PER_SCREEN; if(position_candidate < (ButtonMenuItemArray_size(model->items))) { model->position = position_candidate; + model->scroll_counter = 0; } else { model->position = 0; + model->scroll_counter = 0; } } }, @@ -217,6 +261,7 @@ static void button_menu_process_left(ButtonMenu* button_menu) { }; position_candidate -= position_candidate % BUTTONS_PER_SCREEN; model->position = position_candidate; + model->scroll_counter = 0; } }, true); @@ -314,6 +359,7 @@ void button_menu_reset(ButtonMenu* button_menu) { ButtonMenuItemArray_reset(model->items); model->position = 0; model->header = NULL; + model->scroll_counter = 0; }, true); } @@ -351,6 +397,12 @@ ButtonMenuItem* button_menu_add_item( return item; } +static void button_menu_process_timer_callback(void* context) { + ButtonMenu* button_menu = context; + with_view_model( + button_menu->view, ButtonMenuModel * model, { model->scroll_counter++; }, true); +} + ButtonMenu* button_menu_alloc(void) { ButtonMenu* button_menu = malloc(sizeof(ButtonMenu)); button_menu->view = view_alloc(); @@ -367,6 +419,10 @@ ButtonMenu* button_menu_alloc(void) { ButtonMenuItemArray_init(model->items); model->position = 0; model->header = NULL; + model->scroll_counter = 0; + model->scroll_timer = furi_timer_alloc( + button_menu_process_timer_callback, FuriTimerTypePeriodic, button_menu); + furi_timer_start(model->scroll_timer, SCROLL_INTERVAL); }, true); @@ -380,7 +436,11 @@ void button_menu_free(ButtonMenu* button_menu) { with_view_model( button_menu->view, ButtonMenuModel * model, - { ButtonMenuItemArray_clear(model->items); }, + { + ButtonMenuItemArray_clear(model->items); + furi_timer_stop(model->scroll_timer); + furi_timer_free(model->scroll_timer); + }, true); view_free(button_menu->view); free(button_menu); From 04d66f71622fab522dddcec16810cb72f781ed5a Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 16 May 2025 17:50:24 +0300 Subject: [PATCH 32/75] upd changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfc073b9d..398f242ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * SubGHz: Tune holtek ht12x to decode holtek only and not conflict with came 12bit * SubGHz: Fix Rename scene bug, that was replacing file name with random name when Rename is opened then closed then opened again * NFC: Ultralight C - Attempt of authentication with default key (PR #898 | by @mishamyte) +* OFW PR 4210: Infrared: Add text scroll to remote buttons (by @956MB) * OFW PR 4205: fix sample durations when using external CC1101 (by @Aerosnail) * OFW PR 4206: Stop JS PWM on exit (by @portasynthinca3) * OFW PR 4212: Remove stupid "!" that broke subghz chat cli (by @GameLord2011) From ee3e7bc3bf4ef14898fb48c87cb5e80c7c9ccb5a Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Mon, 19 May 2025 00:37:28 +0700 Subject: [PATCH 33/75] RGB vibro control tune + Display backlight always ON option. --- applications/services/notification/notification_app.c | 5 ++++- .../notification_settings/notification_settings_app.c | 10 ++++++++-- lib/drivers/SK6805.c | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c index ef9677f31..c82f53afd 100644 --- a/applications/services/notification/notification_app.c +++ b/applications/services/notification/notification_app.c @@ -377,7 +377,10 @@ static void notification_reset_notification_layer( app->settings.display_brightness * 0xFF * app->current_night_shift * 1.0f); // --- NIGHT SHIFT END--- } - furi_timer_start(app->display_timer, notification_settings_display_off_delay_ticks(app)); + if(app->settings.display_off_delay_ms > 0) { + furi_timer_start( + app->display_timer, notification_settings_display_off_delay_ticks(app)); + } } } diff --git a/applications/settings/notification_settings/notification_settings_app.c b/applications/settings/notification_settings/notification_settings_app.c index 3713d7e26..e9ccc90f6 100644 --- a/applications/settings/notification_settings/notification_settings_app.c +++ b/applications/settings/notification_settings/notification_settings_app.c @@ -84,8 +84,9 @@ const float volume_value[VOLUME_COUNT] = { 0.55f, 0.60f, 0.65f, 0.70f, 0.75f, 0.80f, 0.85f, 0.90f, 0.95f, 1.00f, }; -#define DELAY_COUNT 11 +#define DELAY_COUNT 12 const char* const delay_text[DELAY_COUNT] = { + "Always ON", "1s", "5s", "10s", @@ -99,7 +100,7 @@ const char* const delay_text[DELAY_COUNT] = { "30min", }; const uint32_t delay_value[DELAY_COUNT] = - {1000, 5000, 10000, 15000, 30000, 60000, 90000, 120000, 300000, 600000, 1800000}; + {0, 1000, 5000, 10000, 15000, 30000, 60000, 90000, 120000, 300000, 600000, 1800000}; #define VIBRO_COUNT 2 const char* const vibro_text[VIBRO_COUNT] = { @@ -303,6 +304,11 @@ static void screen_changed(VariableItem* item) { variable_item_set_current_value_text(item, delay_text[index]); app->notification->settings.display_off_delay_ms = delay_value[index]; + + // Switch off current backlight delay timer if user choose "Always ON" + if((delay_value[index] == 0) & (furi_timer_is_running(app->notification->display_timer))) { + furi_timer_stop(app->notification->display_timer); + } notification_message(app->notification, &sequence_display_backlight_on); } diff --git a/lib/drivers/SK6805.c b/lib/drivers/SK6805.c index 2ad8e18d3..43130b777 100644 --- a/lib/drivers/SK6805.c +++ b/lib/drivers/SK6805.c @@ -58,7 +58,7 @@ void SK6805_set_led_color(uint8_t led_index, uint8_t r, uint8_t g, uint8_t b) { void SK6805_update(void) { SK6805_init(); FURI_CRITICAL_ENTER(); - furi_delay_us(100); + furi_delay_us(150); uint32_t end; /* Последовательная отправка цветов светодиодов */ for(uint8_t lednumber = 0; lednumber < SK6805_LED_COUNT; lednumber++) { From 08205f9ff9b036548f90bda9bb4de7ac308ad417 Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Mon, 19 May 2025 23:43:40 +0700 Subject: [PATCH 34/75] RGB backlight bug removed. --- applications/services/notification/notification_app.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c index c82f53afd..c0ab417b3 100644 --- a/applications/services/notification/notification_app.c +++ b/applications/services/notification/notification_app.c @@ -231,7 +231,7 @@ static void rainbow_timer_callback(void* context) { break; } - rgb_backlight_update(app->settings.led_brightness * app->current_night_shift); + rgb_backlight_update(app->settings.display_brightness * app->current_night_shift); } } From 82c7231c5795c71460c516931b82c044c8d127a8 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 20 May 2025 03:04:57 +0300 Subject: [PATCH 35/75] upd changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 398f242ed..dbf2738d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * SubGHz: Add 462.750 MHz to default subghz freqs list * SubGHz: Tune holtek ht12x to decode holtek only and not conflict with came 12bit * SubGHz: Fix Rename scene bug, that was replacing file name with random name when Rename is opened then closed then opened again +* Display: Backlight option "always on" and RGB bug removed (PR #900 | by @Dmitry422) * NFC: Ultralight C - Attempt of authentication with default key (PR #898 | by @mishamyte) * OFW PR 4210: Infrared: Add text scroll to remote buttons (by @956MB) * OFW PR 4205: fix sample durations when using external CC1101 (by @Aerosnail) From d1c3b0bc680c4eb4c79bbbc1f969b8756781aa34 Mon Sep 17 00:00:00 2001 From: GameLord2011 <119822417+GameLord2011@users.noreply.github.com> Date: Tue, 20 May 2025 17:50:47 -0400 Subject: [PATCH 36/75] Fixed inverted logic condition in subghz chat cli (#4212) --- applications/main/subghz/subghz_cli.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index 674738851..08f2406dd 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -1092,7 +1092,7 @@ static void subghz_cli_command_chat(PipeSide* pipe, FuriString* args) { break; } } - if(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { + if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) { printf("\r\n"); chat_event.event = SubGhzChatEventUserExit; subghz_chat_worker_put_event_chat(subghz_chat, &chat_event); From b3523424347c3512c6c6539da05b0c2976c3f178 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 21 May 2025 17:34:37 +0300 Subject: [PATCH 37/75] rename and extend ignore alarms option in subghz --- .../main/subghz/scenes/subghz_scene_receiver_config.c | 8 ++++---- lib/subghz/protocols/gangqi.c | 3 ++- lib/subghz/protocols/hollarm.c | 3 ++- lib/subghz/protocols/kia.c | 2 +- lib/subghz/protocols/scher_khan.c | 2 +- lib/subghz/types.h | 2 +- 6 files changed, 11 insertions(+), 9 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_config.c b/applications/main/subghz/scenes/subghz_scene_receiver_config.c index f82a45976..e3806eac6 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_config.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_config.c @@ -9,7 +9,7 @@ enum SubGhzSettingIndex { SubGhzSettingIndexModulation, SubGhzSettingIndexBinRAW, SubGhzSettingIndexIgnoreStarline, - SubGhzSettingIndexIgnoreCars, + SubGhzSettingIndexIgnoreAlarms, SubGhzSettingIndexIgnoreMagellan, SubGhzSettingIndexIgnorePrinceton, SubGhzSettingIndexIgnoreNiceFlorS, @@ -306,7 +306,7 @@ static void subghz_scene_receiver_config_set_starline(VariableItem* item) { } static void subghz_scene_receiver_config_set_auto_alarms(VariableItem* item) { - subghz_scene_receiver_config_set_ignore_filter(item, SubGhzProtocolFlag_AutoAlarms); + subghz_scene_receiver_config_set_ignore_filter(item, SubGhzProtocolFlag_Alarms); } static void subghz_scene_receiver_config_set_magellan(VariableItem* item) { @@ -459,13 +459,13 @@ void subghz_scene_receiver_config_on_enter(void* context) { item = variable_item_list_add( subghz->variable_item_list, - "Ignore Cars", + "Ignore Alarms", COMBO_BOX_COUNT, subghz_scene_receiver_config_set_auto_alarms, subghz); value_index = subghz_scene_receiver_config_ignore_filter_get_index( - subghz->ignore_filter, SubGhzProtocolFlag_AutoAlarms); + subghz->ignore_filter, SubGhzProtocolFlag_Alarms); variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, combobox_text[value_index]); diff --git a/lib/subghz/protocols/gangqi.c b/lib/subghz/protocols/gangqi.c index 720a4d54a..905c22982 100644 --- a/lib/subghz/protocols/gangqi.c +++ b/lib/subghz/protocols/gangqi.c @@ -62,7 +62,8 @@ const SubGhzProtocol subghz_protocol_gangqi = { .name = SUBGHZ_PROTOCOL_GANGQI_NAME, .type = SubGhzProtocolTypeStatic, .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | - SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send | + SubGhzProtocolFlag_Alarms, .decoder = &subghz_protocol_gangqi_decoder, .encoder = &subghz_protocol_gangqi_encoder, diff --git a/lib/subghz/protocols/hollarm.c b/lib/subghz/protocols/hollarm.c index fc76affa0..a4cb73352 100644 --- a/lib/subghz/protocols/hollarm.c +++ b/lib/subghz/protocols/hollarm.c @@ -62,7 +62,8 @@ const SubGhzProtocol subghz_protocol_hollarm = { .name = SUBGHZ_PROTOCOL_HOLLARM_NAME, .type = SubGhzProtocolTypeStatic, .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | - SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send | + SubGhzProtocolFlag_Alarms, .decoder = &subghz_protocol_hollarm_decoder, .encoder = &subghz_protocol_hollarm_encoder, diff --git a/lib/subghz/protocols/kia.c b/lib/subghz/protocols/kia.c index 6988a0ef2..1edd367c2 100644 --- a/lib/subghz/protocols/kia.c +++ b/lib/subghz/protocols/kia.c @@ -64,7 +64,7 @@ const SubGhzProtocol subghz_protocol_kia = { .name = SUBGHZ_PROTOCOL_KIA_NAME, .type = SubGhzProtocolTypeDynamic, .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | - SubGhzProtocolFlag_AutoAlarms, + SubGhzProtocolFlag_Alarms, .decoder = &subghz_protocol_kia_decoder, .encoder = &subghz_protocol_kia_encoder, diff --git a/lib/subghz/protocols/scher_khan.c b/lib/subghz/protocols/scher_khan.c index 53b7935d6..d1aad4ee6 100644 --- a/lib/subghz/protocols/scher_khan.c +++ b/lib/subghz/protocols/scher_khan.c @@ -70,7 +70,7 @@ const SubGhzProtocol subghz_protocol_scher_khan = { .name = SUBGHZ_PROTOCOL_SCHER_KHAN_NAME, .type = SubGhzProtocolTypeDynamic, .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | - SubGhzProtocolFlag_Save | SubGhzProtocolFlag_AutoAlarms, + SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Alarms, .decoder = &subghz_protocol_scher_khan_decoder, .encoder = &subghz_protocol_scher_khan_encoder, diff --git a/lib/subghz/types.h b/lib/subghz/types.h index b133a62bb..31264a0ad 100644 --- a/lib/subghz/types.h +++ b/lib/subghz/types.h @@ -126,7 +126,7 @@ typedef enum { SubGhzProtocolFlag_Send = (1 << 9), SubGhzProtocolFlag_BinRAW = (1 << 10), SubGhzProtocolFlag_StarLine = (1 << 11), - SubGhzProtocolFlag_AutoAlarms = (1 << 12), + SubGhzProtocolFlag_Alarms = (1 << 12), SubGhzProtocolFlag_Magellan = (1 << 13), SubGhzProtocolFlag_Princeton = (1 << 14), SubGhzProtocolFlag_NiceFlorS = (1 << 15), From 350dea6535441a8dd52871eb5accb7ca508f90cb Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 22 May 2025 01:09:33 +0300 Subject: [PATCH 38/75] show cnt value in phox --- lib/subghz/protocols/phoenix_v2.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/subghz/protocols/phoenix_v2.c b/lib/subghz/protocols/phoenix_v2.c index 2fabed73d..770f5dea5 100644 --- a/lib/subghz/protocols/phoenix_v2.c +++ b/lib/subghz/protocols/phoenix_v2.c @@ -321,11 +321,12 @@ void subghz_protocol_decoder_phoenix_v2_get_string(void* context, FuriString* ou "%s %dbit\r\n" "Key:%02lX%08lX\r\n" "Sn:0x%07lX \r\n" - "Btn:%X\r\n", + "Btn:%X Cnt: 0x%04lX\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)(instance->generic.data >> 32) & 0xFFFFFFFF, (uint32_t)(instance->generic.data & 0xFFFFFFFF), instance->generic.serial, - instance->generic.btn); + instance->generic.btn, + instance->generic.cnt); } From 92ca0cf1a866e0d4bf316e234b4ece21ce91f864 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 22 May 2025 01:14:40 +0300 Subject: [PATCH 39/75] upd changelog --- CHANGELOG.md | 2 ++ lib/subghz/protocols/phoenix_v2.c | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbf2738d2..bd219b010 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ ## Main changes - Current API: 86.0 * iButton: TM01x Dallas write support (PR #899 | by @Leptopt1los) +* SubGHz: Rename and extend Alarms ignore option (add Hollarm & GangQi) +* SubGHz: V2 Phoenix show counter value * SubGHz: Add keeloq ironlogic (aka il100) smart clone cloners support (thanks to Vitaly for RAWs) * SubGHz: Fix CAME 24bit decoder * SubGHz: Add 462.750 MHz to default subghz freqs list diff --git a/lib/subghz/protocols/phoenix_v2.c b/lib/subghz/protocols/phoenix_v2.c index 770f5dea5..9e88324c4 100644 --- a/lib/subghz/protocols/phoenix_v2.c +++ b/lib/subghz/protocols/phoenix_v2.c @@ -319,7 +319,7 @@ void subghz_protocol_decoder_phoenix_v2_get_string(void* context, FuriString* ou furi_string_cat_printf( output, "%s %dbit\r\n" - "Key:%02lX%08lX\r\n" + "Key:%05lX%08lX\r\n" "Sn:0x%07lX \r\n" "Btn:%X Cnt: 0x%04lX\r\n", instance->generic.protocol_name, From ff8c3540eb531bbe6fe96d245557499942172618 Mon Sep 17 00:00:00 2001 From: Luu <112649910+luu176@users.noreply.github.com> Date: Thu, 22 May 2025 19:12:22 +0200 Subject: [PATCH 40/75] Fix clipper date timestamp --- applications/main/nfc/plugins/supported_cards/clipper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/nfc/plugins/supported_cards/clipper.c b/applications/main/nfc/plugins/supported_cards/clipper.c index 7dc164a7a..51fbd92ce 100644 --- a/applications/main/nfc/plugins/supported_cards/clipper.c +++ b/applications/main/nfc/plugins/supported_cards/clipper.c @@ -548,7 +548,7 @@ static void furi_string_cat_timestamp( const char* time_hdr, uint32_t tmst_1900) { DateTime tm; - + tmst_1900 -= 2208988800; // Clipper uses epoch from 1900, not 1970. datetime_timestamp_to_datetime(tmst_1900, &tm); FuriString* date_str = furi_string_alloc(); From 5365cfcb049a5a30468fb4c3ae3adce4414584ca Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 23 May 2025 22:57:45 +0300 Subject: [PATCH 41/75] fix links --- .ci_files/devbuild_msg_discord.txt | 6 +++--- .ci_files/devbuild_msg_telegram.txt | 6 +++--- .ci_files/release_msg_discord.txt | 6 +++--- .ci_files/release_msg_telegram.txt | 6 +++--- .drone.yml | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.ci_files/devbuild_msg_discord.txt b/.ci_files/devbuild_msg_discord.txt index 41a70e45e..4b39a0b03 100644 --- a/.ci_files/devbuild_msg_discord.txt +++ b/.ci_files/devbuild_msg_discord.txt @@ -6,9 +6,9 @@ How to [install firmware](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/HowToInstall.md)? ### Install FW via Web Updater: -[Default](https://lab.flipper.net/?url=https://unleashedflip.com/fw/dev/flipper-z-f7-update-(buildnum).tgz&channel=dev-cfw&version=(buildnum)) > ` ` -[Extra apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)e.tgz&channel=dev-cfw&version=(buildnum)e) > `e` -[No apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)c.tgz&channel=dev-cfw&version=(buildnum)c) > `c` +[Default](https://lab.flipper.net/?url=https://unleashedflip.com/fw/dev/flipper-z-f7-update-(buildnum).tgz&target=f7&channel=dev-cfw&channel=(buildnum)) > ` ` +[Extra apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)e.tgz&target=f7&channel=dev-cfw&channel=(buildnum)e) > `e` +[No apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)c.tgz&target=f7&channel=dev-cfw&channel=(buildnum)c) > `c` What ` `, `e`, `c` means? -> [versions info](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/CHANGELOG.md#what-e---c-means-what-i-need-to-download-if-i-dont-want-to-use-web-updater) ### Direct tgz download links: [Default](https://unleashedflip.com/fw/dev/flipper-z-f7-update-(buildnum).tgz) > ` ` - [Extra apps](https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)e.tgz) > `e` - [No apps](https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)c.tgz) > `c` diff --git a/.ci_files/devbuild_msg_telegram.txt b/.ci_files/devbuild_msg_telegram.txt index 13d0545dd..73bd900fe 100644 --- a/.ci_files/devbuild_msg_telegram.txt +++ b/.ci_files/devbuild_msg_telegram.txt @@ -7,9 +7,9 @@ How to [install firmware](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/HowToInstall.md)? **Install FW via Web Updater:** -[Default](https://lab.flipper.net/?url=https://unleashedflip.com/fw/dev/flipper-z-f7-update-(buildnum).tgz&channel=dev-cfw&version=(buildnum)) > ` ` -[Extra apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)e.tgz&channel=dev-cfw&version=(buildnum)e) > `e` -[No apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)c.tgz&channel=dev-cfw&version=(buildnum)c) > `c` +[Default](https://lab.flipper.net/?url=https://unleashedflip.com/fw/dev/flipper-z-f7-update-(buildnum).tgz&target=f7&channel=dev-cfw&channel=(buildnum)) > ` ` +[Extra apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)e.tgz&target=f7&channel=dev-cfw&channel=(buildnum)e) > `e` +[No apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)c.tgz&target=f7&channel=dev-cfw&channel=(buildnum)c) > `c` What ` `, `e`, `c` means? -> [versions info](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/CHANGELOG.md#what-e---c-means-what-i-need-to-download-if-i-dont-want-to-use-web-updater) diff --git a/.ci_files/release_msg_discord.txt b/.ci_files/release_msg_discord.txt index 8eadaaf1f..a54913902 100644 --- a/.ci_files/release_msg_discord.txt +++ b/.ci_files/release_msg_discord.txt @@ -6,9 +6,9 @@ How to [install firmware](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/HowToInstall.md)? ### Install FW via Web Updater: -[Default](https://lab.flipper.net/?url=https://unleashedflip.com/fw/(releasever)/flipper-z-f7-update-(releasever).tgz&channel=release-cfw&version=(releasever)) > ` ` -[Extra apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)e.tgz&channel=release-cfw&version=(releasever)e) > `e` -[No apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)c.tgz&channel=release-cfw&version=(releasever)c) > `c` +[Default](https://lab.flipper.net/?url=https://unleashedflip.com/fw/(releasever)/flipper-z-f7-update-(releasever).tgz&target=f7&channel=release-cfw&channel=(releasever)) > ` ` +[Extra apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)e.tgz&target=f7&channel=release-cfw&channel=(releasever)e) > `e` +[No apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)c.tgz&target=f7&channel=release-cfw&channel=(releasever)c) > `c` What ` `, `e`, `c` means? -> [versions info](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/CHANGELOG.md#what-e---c-means-what-i-need-to-download-if-i-dont-want-to-use-web-updater) ### Direct tgz download links: [Default](https://unleashedflip.com/fw/(releasever)/flipper-z-f7-update-(releasever).tgz) > ` ` - [Extra apps](https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)e.tgz) > `e` - [No apps](https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)c.tgz) > `c` diff --git a/.ci_files/release_msg_telegram.txt b/.ci_files/release_msg_telegram.txt index 6d527970f..1c3d60ba4 100644 --- a/.ci_files/release_msg_telegram.txt +++ b/.ci_files/release_msg_telegram.txt @@ -7,9 +7,9 @@ How to [install firmware](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/HowToInstall.md)? **Install FW via Web Updater:** -[Default](https://lab.flipper.net/?url=https://unleashedflip.com/fw/(releasever)/flipper-z-f7-update-(releasever).tgz&channel=release-cfw&version=(releasever)) > ` ` -[Extra apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)e.tgz&channel=release-cfw&version=(releasever)e) > `e` -[No apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)c.tgz&channel=release-cfw&version=(releasever)c) > `c` +[Default](https://lab.flipper.net/?url=https://unleashedflip.com/fw/(releasever)/flipper-z-f7-update-(releasever).tgz&target=f7&channel=release-cfw&channel=(releasever)) > ` ` +[Extra apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)e.tgz&target=f7&channel=release-cfw&channel=(releasever)e) > `e` +[No apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)c.tgz&target=f7&channel=release-cfw&channel=(releasever)c) > `c` What ` `, `e`, `c` means? -> [versions info](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/CHANGELOG.md#what-e---c-means-what-i-need-to-download-if-i-dont-want-to-use-web-updater) diff --git a/.drone.yml b/.drone.yml index 347ab1ce1..49edcc4dd 100644 --- a/.drone.yml +++ b/.drone.yml @@ -83,11 +83,11 @@ steps: - ls -laS artifacts-extra-apps/f7-update-${DRONE_TAG}e - sed -i 's/(version)/'${DRONE_TAG}'/g' CHANGELOG.md - echo '# Install FW via Web Updater:' >> CHANGELOG.md - - echo '### [Default](https://lab.flipper.net/?url=https://unleashedflip.com/fw/${DRONE_TAG}/flipper-z-f7-update-'${DRONE_TAG}'.tgz&channel=release-cfw&version='${DRONE_TAG}') > ` `' >> CHANGELOG.md + - echo '### [Default](https://lab.flipper.net/?url=https://unleashedflip.com/fw/${DRONE_TAG}/flipper-z-f7-update-'${DRONE_TAG}'.tgz&target=f7&channel=release-cfw&channel='${DRONE_TAG}') > ` `' >> CHANGELOG.md - echo '' >> CHANGELOG.md - - echo '### [Extra apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-'${DRONE_TAG}'e.tgz&channel=release-cfw&version='${DRONE_TAG}'e) > `e`' >> CHANGELOG.md + - echo '### [Extra apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-'${DRONE_TAG}'e.tgz&target=f7&channel=release-cfw&channel='${DRONE_TAG}'e) > `e`' >> CHANGELOG.md - echo '' >> CHANGELOG.md - - echo '### [No apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-'${DRONE_TAG}'c.tgz&channel=release-cfw&version='${DRONE_TAG}'c) > `c`' >> CHANGELOG.md + - echo '### [No apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-'${DRONE_TAG}'c.tgz&target=f7&channel=release-cfw&channel='${DRONE_TAG}'c) > `c`' >> CHANGELOG.md environment: FBT_TOOLS_CUSTOM_LINK: from_secret: fbt_link From 4c00313157c2b244db8c7d8ceae97a03a9b0d1ea Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 24 May 2025 00:03:20 +0300 Subject: [PATCH 42/75] upd changelog --- CHANGELOG.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd219b010..12072c079 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,11 +11,12 @@ * Display: Backlight option "always on" and RGB bug removed (PR #900 | by @Dmitry422) * NFC: Ultralight C - Attempt of authentication with default key (PR #898 | by @mishamyte) * OFW PR 4210: Infrared: Add text scroll to remote buttons (by @956MB) -* OFW PR 4205: fix sample durations when using external CC1101 (by @Aerosnail) -* OFW PR 4206: Stop JS PWM on exit (by @portasynthinca3) -* OFW PR 4212: Remove stupid "!" that broke subghz chat cli (by @GameLord2011) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes +* OFW PR 4205: fix sample durations when using external CC1101 (by @Aerosnail) +* OFW PR 4206: Stop JS PWM on exit (by @portasynthinca3) +* OFW PR 4212: Fixed inverted logic condition in subghz chat cli (by @GameLord2011) +* NFC: Fix clipper date timestamp (PR #903 | by @luu176) * Desktop: DEBUG - fix desktop anim switch override by favourite apps * CLI: Various fixes (by @WillyJL) * BadUSB: Fix key combos main keys being case sensitive (by @WillyJL) From 3aa3098d47df47c1f7864881b0e138a1ec7f1b0f Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 24 May 2025 19:49:31 +0300 Subject: [PATCH 43/75] subghz better ignore lists --- .../scenes/subghz_scene_receiver_config.c | 28 +++++++++---------- lib/subghz/protocols/feron.c | 3 +- lib/subghz/protocols/honeywell.c | 2 +- lib/subghz/protocols/honeywell_wdb.c | 2 +- lib/subghz/protocols/kia.c | 2 +- lib/subghz/protocols/legrand.c | 3 +- lib/subghz/protocols/magellan.c | 2 +- lib/subghz/protocols/scher_khan.c | 2 +- lib/subghz/protocols/star_line.c | 2 +- lib/subghz/types.h | 4 +-- 10 files changed, 26 insertions(+), 24 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_config.c b/applications/main/subghz/scenes/subghz_scene_receiver_config.c index e3806eac6..23415474c 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_config.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_config.c @@ -8,9 +8,9 @@ enum SubGhzSettingIndex { SubGhzSettingIndexHopping, SubGhzSettingIndexModulation, SubGhzSettingIndexBinRAW, - SubGhzSettingIndexIgnoreStarline, + SubGhzSettingIndexIgnoreCars, SubGhzSettingIndexIgnoreAlarms, - SubGhzSettingIndexIgnoreMagellan, + SubGhzSettingIndexIgnoreSensors, SubGhzSettingIndexIgnorePrinceton, SubGhzSettingIndexIgnoreNiceFlorS, SubGhzSettingIndexDeleteOldSignals, @@ -301,16 +301,16 @@ static inline bool subghz_scene_receiver_config_ignore_filter_get_index( return READ_BIT(filter, flag) > 0; } -static void subghz_scene_receiver_config_set_starline(VariableItem* item) { - subghz_scene_receiver_config_set_ignore_filter(item, SubGhzProtocolFlag_StarLine); +static void subghz_scene_receiver_config_set_cars(VariableItem* item) { + subghz_scene_receiver_config_set_ignore_filter(item, SubGhzProtocolFlag_Cars); } -static void subghz_scene_receiver_config_set_auto_alarms(VariableItem* item) { +static void subghz_scene_receiver_config_set_alarms(VariableItem* item) { subghz_scene_receiver_config_set_ignore_filter(item, SubGhzProtocolFlag_Alarms); } -static void subghz_scene_receiver_config_set_magellan(VariableItem* item) { - subghz_scene_receiver_config_set_ignore_filter(item, SubGhzProtocolFlag_Magellan); +static void subghz_scene_receiver_config_set_sensors(VariableItem* item) { + subghz_scene_receiver_config_set_ignore_filter(item, SubGhzProtocolFlag_Sensors); } static void subghz_scene_receiver_config_set_princeton(VariableItem* item) { @@ -447,13 +447,13 @@ void subghz_scene_receiver_config_on_enter(void* context) { SubGhzCustomEventManagerSet) { item = variable_item_list_add( subghz->variable_item_list, - "Ignore Starline", + "Ignore Cars", COMBO_BOX_COUNT, - subghz_scene_receiver_config_set_starline, + subghz_scene_receiver_config_set_cars, subghz); value_index = subghz_scene_receiver_config_ignore_filter_get_index( - subghz->ignore_filter, SubGhzProtocolFlag_StarLine); + subghz->ignore_filter, SubGhzProtocolFlag_Cars); variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, combobox_text[value_index]); @@ -461,7 +461,7 @@ void subghz_scene_receiver_config_on_enter(void* context) { subghz->variable_item_list, "Ignore Alarms", COMBO_BOX_COUNT, - subghz_scene_receiver_config_set_auto_alarms, + subghz_scene_receiver_config_set_alarms, subghz); value_index = subghz_scene_receiver_config_ignore_filter_get_index( @@ -471,13 +471,13 @@ void subghz_scene_receiver_config_on_enter(void* context) { item = variable_item_list_add( subghz->variable_item_list, - "Ignore Magellan", + "Ignore Sensors", COMBO_BOX_COUNT, - subghz_scene_receiver_config_set_magellan, + subghz_scene_receiver_config_set_sensors, subghz); value_index = subghz_scene_receiver_config_ignore_filter_get_index( - subghz->ignore_filter, SubGhzProtocolFlag_Magellan); + subghz->ignore_filter, SubGhzProtocolFlag_Sensors); variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, combobox_text[value_index]); diff --git a/lib/subghz/protocols/feron.c b/lib/subghz/protocols/feron.c index 1096f07a7..0fcb14c1b 100644 --- a/lib/subghz/protocols/feron.c +++ b/lib/subghz/protocols/feron.c @@ -60,7 +60,8 @@ const SubGhzProtocol subghz_protocol_feron = { .name = SUBGHZ_PROTOCOL_FERON_NAME, .type = SubGhzProtocolTypeStatic, .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | - SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send | + SubGhzProtocolFlag_Sensors, .decoder = &subghz_protocol_feron_decoder, .encoder = &subghz_protocol_feron_encoder, diff --git a/lib/subghz/protocols/honeywell.c b/lib/subghz/protocols/honeywell.c index e76bb2822..8d8dc22d4 100644 --- a/lib/subghz/protocols/honeywell.c +++ b/lib/subghz/protocols/honeywell.c @@ -364,7 +364,7 @@ const SubGhzProtocol subghz_protocol_honeywell = { .type = SubGhzProtocolTypeStatic, .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | - SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send | SubGhzProtocolFlag_Sensors, .encoder = &subghz_protocol_honeywell_encoder, .decoder = &subghz_protocol_honeywell_decoder, diff --git a/lib/subghz/protocols/honeywell_wdb.c b/lib/subghz/protocols/honeywell_wdb.c index fcf282201..16545b8a0 100644 --- a/lib/subghz/protocols/honeywell_wdb.c +++ b/lib/subghz/protocols/honeywell_wdb.c @@ -74,7 +74,7 @@ const SubGhzProtocol subghz_protocol_honeywell_wdb = { .type = SubGhzProtocolTypeStatic, .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | - SubGhzProtocolFlag_Send, + SubGhzProtocolFlag_Send | SubGhzProtocolFlag_Sensors, .decoder = &subghz_protocol_honeywell_wdb_decoder, .encoder = &subghz_protocol_honeywell_wdb_encoder, diff --git a/lib/subghz/protocols/kia.c b/lib/subghz/protocols/kia.c index 1edd367c2..5b1c9887d 100644 --- a/lib/subghz/protocols/kia.c +++ b/lib/subghz/protocols/kia.c @@ -64,7 +64,7 @@ const SubGhzProtocol subghz_protocol_kia = { .name = SUBGHZ_PROTOCOL_KIA_NAME, .type = SubGhzProtocolTypeDynamic, .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | - SubGhzProtocolFlag_Alarms, + SubGhzProtocolFlag_Cars, .decoder = &subghz_protocol_kia_decoder, .encoder = &subghz_protocol_kia_encoder, diff --git a/lib/subghz/protocols/legrand.c b/lib/subghz/protocols/legrand.c index 9459fa2e7..94a45694c 100644 --- a/lib/subghz/protocols/legrand.c +++ b/lib/subghz/protocols/legrand.c @@ -67,7 +67,8 @@ const SubGhzProtocol subghz_protocol_legrand = { .name = SUBGHZ_PROTOCOL_LEGRAND_NAME, .type = SubGhzProtocolTypeStatic, .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | - SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send | + SubGhzProtocolFlag_Sensors, .decoder = &subghz_protocol_legrand_decoder, .encoder = &subghz_protocol_legrand_encoder, diff --git a/lib/subghz/protocols/magellan.c b/lib/subghz/protocols/magellan.c index 260b11e75..4382a3db5 100644 --- a/lib/subghz/protocols/magellan.c +++ b/lib/subghz/protocols/magellan.c @@ -65,7 +65,7 @@ const SubGhzProtocol subghz_protocol_magellan = { .type = SubGhzProtocolTypeStatic, .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send | - SubGhzProtocolFlag_Magellan, + SubGhzProtocolFlag_Sensors, .decoder = &subghz_protocol_magellan_decoder, .encoder = &subghz_protocol_magellan_encoder, diff --git a/lib/subghz/protocols/scher_khan.c b/lib/subghz/protocols/scher_khan.c index d1aad4ee6..e3bc4f079 100644 --- a/lib/subghz/protocols/scher_khan.c +++ b/lib/subghz/protocols/scher_khan.c @@ -70,7 +70,7 @@ const SubGhzProtocol subghz_protocol_scher_khan = { .name = SUBGHZ_PROTOCOL_SCHER_KHAN_NAME, .type = SubGhzProtocolTypeDynamic, .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | - SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Alarms, + SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Cars, .decoder = &subghz_protocol_scher_khan_decoder, .encoder = &subghz_protocol_scher_khan_encoder, diff --git a/lib/subghz/protocols/star_line.c b/lib/subghz/protocols/star_line.c index 0005ad5fc..991957abb 100644 --- a/lib/subghz/protocols/star_line.c +++ b/lib/subghz/protocols/star_line.c @@ -80,7 +80,7 @@ const SubGhzProtocol subghz_protocol_star_line = { .type = SubGhzProtocolTypeDynamic, .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send | - SubGhzProtocolFlag_StarLine, + SubGhzProtocolFlag_Cars, .decoder = &subghz_protocol_star_line_decoder, .encoder = &subghz_protocol_star_line_encoder, diff --git a/lib/subghz/types.h b/lib/subghz/types.h index 31264a0ad..cd7f74ba9 100644 --- a/lib/subghz/types.h +++ b/lib/subghz/types.h @@ -125,9 +125,9 @@ typedef enum { SubGhzProtocolFlag_Load = (1 << 8), SubGhzProtocolFlag_Send = (1 << 9), SubGhzProtocolFlag_BinRAW = (1 << 10), - SubGhzProtocolFlag_StarLine = (1 << 11), + SubGhzProtocolFlag_Cars = (1 << 11), SubGhzProtocolFlag_Alarms = (1 << 12), - SubGhzProtocolFlag_Magellan = (1 << 13), + SubGhzProtocolFlag_Sensors = (1 << 13), SubGhzProtocolFlag_Princeton = (1 << 14), SubGhzProtocolFlag_NiceFlorS = (1 << 15), } SubGhzProtocolFlag; From 54c3c6772891b8359646f14e42108693a9564b03 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 24 May 2025 19:58:10 +0300 Subject: [PATCH 44/75] upd changelog --- CHANGELOG.md | 2 +- ReadMe.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12072c079..d2f239aab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ ## Main changes - Current API: 86.0 * iButton: TM01x Dallas write support (PR #899 | by @Leptopt1los) -* SubGHz: Rename and extend Alarms ignore option (add Hollarm & GangQi) +* SubGHz: Rename and extend Alarms, Sensors, Cars ignore options (Alarms: Hollarm, GangQi | Cars: Kia, Starline, ScherKhan | Sensors: Magellan, Honeywell, Honeywell WDB (doorbells), Legrand (doorbells), Feron (RGB lights)) * SubGHz: V2 Phoenix show counter value * SubGHz: Add keeloq ironlogic (aka il100) smart clone cloners support (thanks to Vitaly for RAWs) * SubGHz: Fix CAME 24bit decoder diff --git a/ReadMe.md b/ReadMe.md index 2711ea794..395222fff 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -80,6 +80,7 @@ Before getting started: > - FAAC SLH, BFT Mitto / Somfy Telis / Nice Flor S / CAME Atomo, etc. manual creation with programming new remote into receiver (use button 0xF for BFT Mitto, 0x8 (Prog) on Somfy Telis, (right arrow button for other protocols)) > - Debug mode counter increase settings (+1 → +5, +10, default: +1) > - Debug PIN output settings for protocol development +> - Ignore options - Alarms: Hollarm, GangQi | Cars: Kia, Starline, ScherKhan | Sensors: Magellan, Honeywell, Honeywell WDB (doorbells), Legrand (doorbells), Feron (RGB lights) >
>
From f1f1a8920441de05275b28b120af13c24d61c715 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 2 Jun 2025 00:54:30 +0300 Subject: [PATCH 45/75] fix links [ci skip] --- .ci_files/devbuild_msg_discord.txt | 6 +++--- .ci_files/devbuild_msg_telegram.txt | 6 +++--- .ci_files/release_msg_discord.txt | 6 +++--- .ci_files/release_msg_telegram.txt | 6 +++--- .drone.yml | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.ci_files/devbuild_msg_discord.txt b/.ci_files/devbuild_msg_discord.txt index 4b39a0b03..f3f12461d 100644 --- a/.ci_files/devbuild_msg_discord.txt +++ b/.ci_files/devbuild_msg_discord.txt @@ -6,9 +6,9 @@ How to [install firmware](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/HowToInstall.md)? ### Install FW via Web Updater: -[Default](https://lab.flipper.net/?url=https://unleashedflip.com/fw/dev/flipper-z-f7-update-(buildnum).tgz&target=f7&channel=dev-cfw&channel=(buildnum)) > ` ` -[Extra apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)e.tgz&target=f7&channel=dev-cfw&channel=(buildnum)e) > `e` -[No apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)c.tgz&target=f7&channel=dev-cfw&channel=(buildnum)c) > `c` +[Default](https://lab.flipper.net/?url=https://unleashedflip.com/fw/dev/flipper-z-f7-update-(buildnum).tgz&target=f7&channel=dev-cfw&version=(buildnum)) > ` ` +[Extra apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)e.tgz&target=f7&channel=dev-cfw&version=(buildnum)e) > `e` +[No apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)c.tgz&target=f7&channel=dev-cfw&version=(buildnum)c) > `c` What ` `, `e`, `c` means? -> [versions info](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/CHANGELOG.md#what-e---c-means-what-i-need-to-download-if-i-dont-want-to-use-web-updater) ### Direct tgz download links: [Default](https://unleashedflip.com/fw/dev/flipper-z-f7-update-(buildnum).tgz) > ` ` - [Extra apps](https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)e.tgz) > `e` - [No apps](https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)c.tgz) > `c` diff --git a/.ci_files/devbuild_msg_telegram.txt b/.ci_files/devbuild_msg_telegram.txt index 73bd900fe..833a8158f 100644 --- a/.ci_files/devbuild_msg_telegram.txt +++ b/.ci_files/devbuild_msg_telegram.txt @@ -7,9 +7,9 @@ How to [install firmware](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/HowToInstall.md)? **Install FW via Web Updater:** -[Default](https://lab.flipper.net/?url=https://unleashedflip.com/fw/dev/flipper-z-f7-update-(buildnum).tgz&target=f7&channel=dev-cfw&channel=(buildnum)) > ` ` -[Extra apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)e.tgz&target=f7&channel=dev-cfw&channel=(buildnum)e) > `e` -[No apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)c.tgz&target=f7&channel=dev-cfw&channel=(buildnum)c) > `c` +[Default](https://lab.flipper.net/?url=https://unleashedflip.com/fw/dev/flipper-z-f7-update-(buildnum).tgz&target=f7&channel=dev-cfw&version=(buildnum)) > ` ` +[Extra apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)e.tgz&target=f7&channel=dev-cfw&version=(buildnum)e) > `e` +[No apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(buildnum)c.tgz&target=f7&channel=dev-cfw&version=(buildnum)c) > `c` What ` `, `e`, `c` means? -> [versions info](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/CHANGELOG.md#what-e---c-means-what-i-need-to-download-if-i-dont-want-to-use-web-updater) diff --git a/.ci_files/release_msg_discord.txt b/.ci_files/release_msg_discord.txt index a54913902..3536fbfb6 100644 --- a/.ci_files/release_msg_discord.txt +++ b/.ci_files/release_msg_discord.txt @@ -6,9 +6,9 @@ How to [install firmware](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/HowToInstall.md)? ### Install FW via Web Updater: -[Default](https://lab.flipper.net/?url=https://unleashedflip.com/fw/(releasever)/flipper-z-f7-update-(releasever).tgz&target=f7&channel=release-cfw&channel=(releasever)) > ` ` -[Extra apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)e.tgz&target=f7&channel=release-cfw&channel=(releasever)e) > `e` -[No apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)c.tgz&target=f7&channel=release-cfw&channel=(releasever)c) > `c` +[Default](https://lab.flipper.net/?url=https://unleashedflip.com/fw/(releasever)/flipper-z-f7-update-(releasever).tgz&target=f7&channel=release-cfw&version=(releasever)) > ` ` +[Extra apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)e.tgz&target=f7&channel=release-cfw&version=(releasever)e) > `e` +[No apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)c.tgz&target=f7&channel=release-cfw&version=(releasever)c) > `c` What ` `, `e`, `c` means? -> [versions info](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/CHANGELOG.md#what-e---c-means-what-i-need-to-download-if-i-dont-want-to-use-web-updater) ### Direct tgz download links: [Default](https://unleashedflip.com/fw/(releasever)/flipper-z-f7-update-(releasever).tgz) > ` ` - [Extra apps](https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)e.tgz) > `e` - [No apps](https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)c.tgz) > `c` diff --git a/.ci_files/release_msg_telegram.txt b/.ci_files/release_msg_telegram.txt index 1c3d60ba4..534aeb3ba 100644 --- a/.ci_files/release_msg_telegram.txt +++ b/.ci_files/release_msg_telegram.txt @@ -7,9 +7,9 @@ How to [install firmware](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/HowToInstall.md)? **Install FW via Web Updater:** -[Default](https://lab.flipper.net/?url=https://unleashedflip.com/fw/(releasever)/flipper-z-f7-update-(releasever).tgz&target=f7&channel=release-cfw&channel=(releasever)) > ` ` -[Extra apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)e.tgz&target=f7&channel=release-cfw&channel=(releasever)e) > `e` -[No apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)c.tgz&target=f7&channel=release-cfw&channel=(releasever)c) > `c` +[Default](https://lab.flipper.net/?url=https://unleashedflip.com/fw/(releasever)/flipper-z-f7-update-(releasever).tgz&target=f7&channel=release-cfw&version=(releasever)) > ` ` +[Extra apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)e.tgz&target=f7&channel=release-cfw&version=(releasever)e) > `e` +[No apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-(releasever)c.tgz&target=f7&channel=release-cfw&version=(releasever)c) > `c` What ` `, `e`, `c` means? -> [versions info](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/CHANGELOG.md#what-e---c-means-what-i-need-to-download-if-i-dont-want-to-use-web-updater) diff --git a/.drone.yml b/.drone.yml index 49edcc4dd..0878d455e 100644 --- a/.drone.yml +++ b/.drone.yml @@ -83,11 +83,11 @@ steps: - ls -laS artifacts-extra-apps/f7-update-${DRONE_TAG}e - sed -i 's/(version)/'${DRONE_TAG}'/g' CHANGELOG.md - echo '# Install FW via Web Updater:' >> CHANGELOG.md - - echo '### [Default](https://lab.flipper.net/?url=https://unleashedflip.com/fw/${DRONE_TAG}/flipper-z-f7-update-'${DRONE_TAG}'.tgz&target=f7&channel=release-cfw&channel='${DRONE_TAG}') > ` `' >> CHANGELOG.md + - echo '### [Default](https://lab.flipper.net/?url=https://unleashedflip.com/fw/${DRONE_TAG}/flipper-z-f7-update-'${DRONE_TAG}'.tgz&target=f7&channel=release-cfw&version='${DRONE_TAG}') > ` `' >> CHANGELOG.md - echo '' >> CHANGELOG.md - - echo '### [Extra apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-'${DRONE_TAG}'e.tgz&target=f7&channel=release-cfw&channel='${DRONE_TAG}'e) > `e`' >> CHANGELOG.md + - echo '### [Extra apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-'${DRONE_TAG}'e.tgz&target=f7&channel=release-cfw&version='${DRONE_TAG}'e) > `e`' >> CHANGELOG.md - echo '' >> CHANGELOG.md - - echo '### [No apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-'${DRONE_TAG}'c.tgz&target=f7&channel=release-cfw&channel='${DRONE_TAG}'c) > `c`' >> CHANGELOG.md + - echo '### [No apps](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-'${DRONE_TAG}'c.tgz&target=f7&channel=release-cfw&version='${DRONE_TAG}'c) > `c`' >> CHANGELOG.md environment: FBT_TOOLS_CUSTOM_LINK: from_secret: fbt_link From 407e482566597ec9d526d4db7a574c1a52206919 Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+WillyJL@users.noreply.github.com> Date: Wed, 11 Jun 2025 04:02:42 +0100 Subject: [PATCH 46/75] Loader: Fix misplaced ApplicationBeforeLoad events --- applications/services/loader/loader.c | 22 +++++++++---------- .../services/loader/loader_applications.c | 1 + 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 01de5af21..74dd4bc3e 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -654,10 +654,6 @@ static LoaderMessageLoaderStatusResult loader_do_start_by_name( status.value = loader_make_success_status(error_message); status.error = LoaderStatusErrorUnknown; - LoaderEvent event; - event.type = LoaderEventTypeApplicationBeforeLoad; - furi_pubsub_publish(loader->pubsub, &event); - do { // check lock if(loader_do_is_locked(loader)) { @@ -677,6 +673,17 @@ static LoaderMessageLoaderStatusResult loader_do_start_by_name( break; } + // check Applications + if(strcmp(name, LOADER_APPLICATIONS_NAME) == 0) { + loader_do_applications_show(loader); + status.value = loader_make_success_status(error_message); + break; + } + + LoaderEvent event; + event.type = LoaderEventTypeApplicationBeforeLoad; + furi_pubsub_publish(loader->pubsub, &event); + // check internal apps { const FlipperInternalApplication* app = loader_find_application_by_name(name); @@ -687,13 +694,6 @@ static LoaderMessageLoaderStatusResult loader_do_start_by_name( } } - // check Applications - if(strcmp(name, LOADER_APPLICATIONS_NAME) == 0) { - loader_do_applications_show(loader); - status.value = loader_make_success_status(error_message); - break; - } - // check External Applications { const char* path = loader_find_external_application_by_name(name); diff --git a/applications/services/loader/loader_applications.c b/applications/services/loader/loader_applications.c index b83083000..64b471cc9 100644 --- a/applications/services/loader/loader_applications.c +++ b/applications/services/loader/loader_applications.c @@ -141,6 +141,7 @@ static void } furi_pubsub_unsubscribe(loader_get_pubsub(app->loader), subscription); + furi_thread_flags_clear(APPLICATION_STOP_EVENT); } static int32_t loader_applications_thread(void* p) { From d0e5dbe63eb32b65ff9a747dd504c44acfe3505d Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+WillyJL@users.noreply.github.com> Date: Wed, 11 Jun 2025 04:03:17 +0100 Subject: [PATCH 47/75] Power: Arm auto-poweroff when app chaining finished --- applications/services/power/power_service/power.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/applications/services/power/power_service/power.c b/applications/services/power/power_service/power.c index 31fcfcbd2..44247394f 100644 --- a/applications/services/power/power_service/power.c +++ b/applications/services/power/power_service/power.c @@ -487,9 +487,7 @@ static void power_loader_callback(const void* message, void* context) { power->app_running = true; power_auto_poweroff_disarm(power); // arm timer if some apps was not loaded or was stoped - } else if( - event->type == LoaderEventTypeApplicationLoadFailed || - event->type == LoaderEventTypeApplicationStopped) { + } else if(event->type == LoaderEventTypeNoMoreAppsInQueue) { power->app_running = false; power_auto_poweroff_arm(power); } From 98a44779990cad77405cb1483c4d866f691f1aa6 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 11 Jun 2025 18:10:38 +0300 Subject: [PATCH 48/75] upd changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2f239aab..cc296bb35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * SubGHz: Fix Rename scene bug, that was replacing file name with random name when Rename is opened then closed then opened again * Display: Backlight option "always on" and RGB bug removed (PR #900 | by @Dmitry422) * NFC: Ultralight C - Attempt of authentication with default key (PR #898 | by @mishamyte) +* System: Loader - Fix misplaced ApplicationBeforeLoad events (PR #905 | by @WillyJL) * OFW PR 4210: Infrared: Add text scroll to remote buttons (by @956MB) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes From 0b53be5cbdfccabc2ec6e49f8976002ac3b2dff5 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 25 Jun 2025 02:50:18 +0300 Subject: [PATCH 49/75] Add DEZ10 by realcatgirly https://github.com/Next-Flip/Momentum-Firmware/pull/418/files --- applications/main/lfrfid/scenes/lfrfid_scene_read_success.c | 3 +-- lib/lfrfid/protocols/protocol_em4100.c | 6 ++++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_read_success.c b/applications/main/lfrfid/scenes/lfrfid_scene_read_success.c index b0e373ea5..354783ff6 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_read_success.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_read_success.c @@ -41,8 +41,7 @@ void lfrfid_scene_read_success_on_enter(void* context) { furi_string_cat_printf(display_text, "\n%s", furi_string_get_cstr(rendered_data)); furi_string_free(rendered_data); - widget_add_text_box_element( - widget, 0, 16, 128, 52, AlignLeft, AlignTop, furi_string_get_cstr(display_text), true); + widget_add_text_scroll_element(widget, 0, 16, 128, 35, furi_string_get_cstr(display_text)); 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/lib/lfrfid/protocols/protocol_em4100.c b/lib/lfrfid/protocols/protocol_em4100.c index ed18133dc..9ff6724cb 100644 --- a/lib/lfrfid/protocols/protocol_em4100.c +++ b/lib/lfrfid/protocols/protocol_em4100.c @@ -374,11 +374,13 @@ void protocol_em4100_render_data(ProtocolEM4100* protocol, FuriString* result) { furi_string_printf( result, "FC: %03u Card: %05hu CL:%hhu\n" - "DEZ 8: %08lu", + "DEZ 8: %08lu\n" + "DEZ 10: %010lu", data[2], (uint16_t)((data[3] << 8) | (data[4])), protocol->clock_per_bit, - (uint32_t)((data[2] << 16) | (data[3] << 8) | (data[4]))); + (uint32_t)((data[2] << 16) | (data[3] << 8) | (data[4])), + (uint32_t)((data[1] << 24) | (data[2] << 16) | (data[3] << 8) | (data[4]))); } const ProtocolBase protocol_em4100 = { From 1b754c1482fedb85235819ed34f5446ac3d56bb8 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 25 Jun 2025 02:57:05 +0300 Subject: [PATCH 50/75] Fix possible frequency analyzer deadlock when holding Ok by WillyJL --- .../scenes/subghz_scene_frequency_analyzer.c | 2 +- .../main/subghz/views/subghz_frequency_analyzer.c | 14 ++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c b/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c index 9e5289c54..7c9c5192d 100644 --- a/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c +++ b/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c @@ -71,7 +71,7 @@ bool subghz_scene_frequency_analyzer_on_event(void* context, SceneManagerEvent e } else if(event.event == SubGhzCustomEventViewFreqAnalOkLong) { // Don't need to save, we already saved on short event (and on exit event too) subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE); - scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneStart, 10); + scene_manager_previous_scene(subghz->scene_manager); // Stops the worker scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiver); return true; } diff --git a/applications/main/subghz/views/subghz_frequency_analyzer.c b/applications/main/subghz/views/subghz_frequency_analyzer.c index b070d6f83..49eabc94b 100644 --- a/applications/main/subghz/views/subghz_frequency_analyzer.c +++ b/applications/main/subghz/views/subghz_frequency_analyzer.c @@ -255,9 +255,9 @@ bool subghz_frequency_analyzer_input(InputEvent* event, void* context) { need_redraw = true; } } else if( - (event->type != InputTypeRelease && event->type != InputTypeRepeat) && + (event->type == InputTypeShort || event->type == InputTypeLong) && event->key == InputKeyOk) { - need_redraw = true; + need_redraw = false; bool updated = false; uint32_t frequency_to_save; with_view_model( @@ -286,21 +286,19 @@ bool subghz_frequency_analyzer_input(InputEvent* event, void* context) { instance->worker, frequency_candidate); if(frequency_candidate > 0 && frequency_candidate != model->frequency_to_save) { model->frequency_to_save = frequency_candidate; + frequency_to_save = frequency_candidate; updated = true; } }, - true); + false); if(updated) { instance->callback(SubGhzCustomEventViewFreqAnalOkShort, instance->context); } - // First the device receives short, then when user release button we get long + // If it was a long press also send a second event if(event->type == InputTypeLong && frequency_to_save > 0) { - // Stop worker - if(subghz_frequency_analyzer_worker_is_running(instance->worker)) { - subghz_frequency_analyzer_worker_stop(instance->worker); - } + // Worker stopped on app thread instead of GUI thread when switching scene in callback instance->callback(SubGhzCustomEventViewFreqAnalOkLong, instance->context); } From 9cfa400ff4e11048564ef6b45982f4b7847d1ddf Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 25 Jun 2025 02:57:45 +0300 Subject: [PATCH 51/75] upd changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc296bb35..dec3fd29b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ * OFW PR 4210: Infrared: Add text scroll to remote buttons (by @956MB) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes +* SubGHz: Fix possible frequency analyzer deadlock when holding Ok (by @WillyJL) +* RFID: Add DEZ10 representation to EM410X (by @realcatgirly) * OFW PR 4205: fix sample durations when using external CC1101 (by @Aerosnail) * OFW PR 4206: Stop JS PWM on exit (by @portasynthinca3) * OFW PR 4212: Fixed inverted logic condition in subghz chat cli (by @GameLord2011) From 5ee3f7c68d253f3b6cb16bb4e1b94d11dca72fe1 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 27 Jun 2025 21:17:13 +0300 Subject: [PATCH 52/75] fix lfrfid write very strange issue with lcd backlight --- lib/lfrfid/lfrfid_worker_modes.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/lfrfid/lfrfid_worker_modes.c b/lib/lfrfid/lfrfid_worker_modes.c index 645d2bd82..036e9cf24 100644 --- a/lib/lfrfid/lfrfid_worker_modes.c +++ b/lib/lfrfid/lfrfid_worker_modes.c @@ -523,6 +523,7 @@ static void lfrfid_worker_mode_write_process(LFRFIDWorker* worker) { while(!lfrfid_worker_check_for_stop(worker)) { FURI_LOG_D(TAG, "Data write"); + furi_delay_ms(5); // halt uint16_t skips = 0; for(size_t i = 0; i < LFRFIDWriteTypeMax; i++) { memset(request, 0, sizeof(LFRFIDWriteRequest)); @@ -626,6 +627,7 @@ static void lfrfid_worker_mode_write_and_set_pass_process(LFRFIDWorker* worker) if(can_be_written) { while(!lfrfid_worker_check_for_stop(worker)) { FURI_LOG_D(TAG, "Data write with pass"); + furi_delay_ms(5); // halt LfRfid* app = worker->cb_ctx; uint32_t pass = bit_lib_bytes_to_num_be(app->password, 4); From 91a235b395eb1ac0dab0c02d204d4b21b2f73afc Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 27 Jun 2025 21:22:52 +0300 Subject: [PATCH 53/75] upd changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dec3fd29b..a16db022e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,8 +14,9 @@ * OFW PR 4210: Infrared: Add text scroll to remote buttons (by @956MB) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes +* RFID 125khz: Fix strange bug with LCD backlight going off after doing "Write" * SubGHz: Fix possible frequency analyzer deadlock when holding Ok (by @WillyJL) -* RFID: Add DEZ10 representation to EM410X (by @realcatgirly) +* RFID 125khz: Add DEZ10 representation to EM410X (by @realcatgirly) * OFW PR 4205: fix sample durations when using external CC1101 (by @Aerosnail) * OFW PR 4206: Stop JS PWM on exit (by @portasynthinca3) * OFW PR 4212: Fixed inverted logic condition in subghz chat cli (by @GameLord2011) From 1382a6af4650ff00a92403990799b3b0ca7ff3b3 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 30 Jun 2025 19:48:34 +0300 Subject: [PATCH 54/75] submenu merge all changes by WillyJL --- applications/services/gui/modules/submenu.c | 98 +++++++++++---------- applications/services/gui/modules/submenu.h | 11 ++- targets/f7/api_symbols.csv | 3 +- 3 files changed, 64 insertions(+), 48 deletions(-) diff --git a/applications/services/gui/modules/submenu.c b/applications/services/gui/modules/submenu.c index 648e213c0..0668db531 100644 --- a/applications/services/gui/modules/submenu.c +++ b/applications/services/gui/modules/submenu.c @@ -7,6 +7,7 @@ struct Submenu { View* view; + FuriTimer* locked_timer; }; @@ -19,6 +20,7 @@ typedef struct { }; void* callback_context; bool has_extended_events; + bool locked; FuriString* locked_message; } SubmenuItem; @@ -68,6 +70,7 @@ typedef struct { FuriString* header; size_t position; size_t window_position; + bool locked_message_visible; bool is_vertical; } SubmenuModel; @@ -76,9 +79,9 @@ static void submenu_process_up(Submenu* submenu); static void submenu_process_down(Submenu* submenu); static void submenu_process_ok(Submenu* submenu, InputType input_type); -static size_t submenu_items_on_screen(bool header, bool vertical) { - size_t res = (vertical) ? 8 : 4; - return (header) ? res - 1 : res; +static size_t submenu_items_on_screen(SubmenuModel* model) { + size_t res = (model->is_vertical) ? 8 : 4; + return (furi_string_empty(model->header)) ? res : res - 1; } static void submenu_view_draw_callback(Canvas* canvas, void* _model) { @@ -101,9 +104,9 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) { for(SubmenuItemArray_it(it, model->items); !SubmenuItemArray_end_p(it); SubmenuItemArray_next(it)) { const size_t item_position = position - model->window_position; - const size_t items_on_screen = - submenu_items_on_screen(!furi_string_empty(model->header), model->is_vertical); - uint8_t y_offset = furi_string_empty(model->header) ? 0 : 16; + const size_t items_on_screen = submenu_items_on_screen(model); + uint8_t y_offset = furi_string_empty(model->header) ? 0 : item_height; + bool is_locked = SubmenuItemArray_cref(it)->locked; if(item_position < items_on_screen) { if(position == model->position) { @@ -119,7 +122,7 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) { canvas_set_color(canvas, ColorBlack); } - if(SubmenuItemArray_cref(it)->locked) { + if(is_locked) { canvas_draw_icon( canvas, item_width - 10, @@ -127,10 +130,8 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) { &I_Lock_7x8); } - FuriString* disp_str; - disp_str = furi_string_alloc_set(SubmenuItemArray_cref(it)->label); - elements_string_fit_width( - canvas, disp_str, item_width - (SubmenuItemArray_cref(it)->locked ? 21 : 11)); + FuriString* disp_str = furi_string_alloc_set(SubmenuItemArray_cref(it)->label); + elements_string_fit_width(canvas, disp_str, item_width - (is_locked ? 21 : 11)); canvas_draw_str( canvas, @@ -161,25 +162,14 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) { canvas_draw_rframe(canvas, frame_x, frame_y, frame_width, frame_height, 3); canvas_draw_rframe(canvas, frame_x + 1, frame_y + 1, frame_width - 2, frame_height - 2, 2); - if(model->is_vertical) { - elements_multiline_text_aligned( - canvas, - 32, - 42, - AlignCenter, - AlignCenter, - furi_string_get_cstr( - SubmenuItemArray_get(model->items, model->position)->locked_message)); - } else { - elements_multiline_text_aligned( - canvas, - 84, - 32, - AlignCenter, - AlignCenter, - furi_string_get_cstr( - SubmenuItemArray_get(model->items, model->position)->locked_message)); - } + elements_multiline_text_aligned( + canvas, + (model->is_vertical) ? 32 : 84, + (model->is_vertical) ? 42 : 32, + AlignCenter, + AlignCenter, + furi_string_get_cstr( + SubmenuItemArray_get(model->items, model->position)->locked_message)); } } @@ -195,8 +185,7 @@ static bool submenu_view_input_callback(InputEvent* event, void* context) { { locked_message_visible = model->locked_message_visible; }, false); - if((!(event->type == InputTypePress) && !(event->type == InputTypeRelease)) && - locked_message_visible) { + if(locked_message_visible && (event->type == InputTypeShort || event->type == InputTypeLong)) { with_view_model( submenu->view, SubmenuModel * model, { model->locked_message_visible = false; }, true); consumed = true; @@ -303,6 +292,9 @@ void submenu_add_lockable_item( SubmenuItem* item = NULL; furi_check(label); furi_check(submenu); + if(locked) { + furi_check(locked_message); + } with_view_model( submenu->view, @@ -366,6 +358,25 @@ void submenu_change_item_label(Submenu* submenu, uint32_t index, const char* lab true); } +void submenu_remove_item(Submenu* submenu, uint32_t index) { + furi_check(submenu); + + with_view_model( + submenu->view, + SubmenuModel * model, + { + SubmenuItemArray_it_t it; + for(SubmenuItemArray_it(it, model->items); !SubmenuItemArray_end_p(it); + SubmenuItemArray_next(it)) { + if(index == SubmenuItemArray_cref(it)->index) { + SubmenuItemArray_remove(model->items, it); + break; + } + } + }, + true); +} + void submenu_reset(Submenu* submenu) { furi_check(submenu); view_set_orientation(submenu->view, ViewOrientationHorizontal); @@ -431,8 +442,7 @@ void submenu_set_selected_item(Submenu* submenu, uint32_t index) { model->window_position -= 1; } - const size_t items_on_screen = - submenu_items_on_screen(!furi_string_empty(model->header), model->is_vertical); + const size_t items_on_screen = submenu_items_on_screen(model); if(items_size <= items_on_screen) { model->window_position = 0; @@ -451,8 +461,7 @@ void submenu_process_up(Submenu* submenu) { submenu->view, SubmenuModel * model, { - const size_t items_on_screen = - submenu_items_on_screen(!furi_string_empty(model->header), model->is_vertical); + const size_t items_on_screen = submenu_items_on_screen(model); const size_t items_size = SubmenuItemArray_size(model->items); if(model->position > 0) { @@ -475,8 +484,7 @@ void submenu_process_down(Submenu* submenu) { submenu->view, SubmenuModel * model, { - const size_t items_on_screen = - submenu_items_on_screen(!furi_string_empty(model->header), model->is_vertical); + const size_t items_on_screen = submenu_items_on_screen(model); const size_t items_size = SubmenuItemArray_size(model->items); if(model->position < items_size - 1) { @@ -504,7 +512,8 @@ void submenu_process_ok(Submenu* submenu, InputType input_type) { if(model->position < items_size) { item = SubmenuItemArray_get(model->items, model->position); } - if(item && item->locked) { + if(item && item->locked && + (input_type == InputTypeShort || input_type == InputTypeLong)) { model->locked_message_visible = true; furi_timer_start(submenu->locked_timer, furi_kernel_get_tick_frequency() * 3); } @@ -540,11 +549,9 @@ void submenu_set_header(Submenu* submenu, const char* header) { } void submenu_set_orientation(Submenu* submenu, ViewOrientation orientation) { - furi_assert(submenu); - const bool is_vertical = - (orientation == ViewOrientationVertical || orientation == ViewOrientationVerticalFlip) ? - true : - false; + furi_check(submenu); + const bool is_vertical = orientation == ViewOrientationVertical || + orientation == ViewOrientationVerticalFlip; view_set_orientation(submenu->view, orientation); @@ -558,8 +565,7 @@ void submenu_set_orientation(Submenu* submenu, ViewOrientation orientation) { // Need if _set_orientation is called after _set_selected_item size_t position = model->position; const size_t items_size = SubmenuItemArray_size(model->items); - const size_t items_on_screen = - submenu_items_on_screen(!furi_string_empty(model->header), model->is_vertical); + const size_t items_on_screen = submenu_items_on_screen(model); if(position >= items_size) { position = 0; diff --git a/applications/services/gui/modules/submenu.h b/applications/services/gui/modules/submenu.h index 750167543..64ba650ee 100644 --- a/applications/services/gui/modules/submenu.h +++ b/applications/services/gui/modules/submenu.h @@ -98,6 +98,14 @@ void submenu_add_item_ex( */ void submenu_change_item_label(Submenu* submenu, uint32_t index, const char* label); +/** Remove item from submenu + * + * @param submenu Submenu instance + * @param index menu item index, used for callback, may be + * the same with other items, first one is removed + */ +void submenu_remove_item(Submenu* submenu, uint32_t index); + /** Remove all items from submenu * * @param submenu Submenu instance @@ -120,13 +128,14 @@ uint32_t submenu_get_selected_item(Submenu* submenu); void submenu_set_selected_item(Submenu* submenu, uint32_t index); /** Set optional header for submenu + * Must be called before adding items OR after adding items and before set_selected_item() * * @param submenu Submenu instance * @param header header to set */ void submenu_set_header(Submenu* submenu, const char* header); -/** Set Orientation +/** Set submenu orientation * * @param submenu Submenu instance * @param orientation either vertical or horizontal diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index c5d417f3e..e2aa3d897 100755 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,86.1,, +Version,+,86.0,, 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,, @@ -3660,6 +3660,7 @@ Function,+,submenu_change_item_label,void,"Submenu*, uint32_t, const char*" Function,+,submenu_free,void,Submenu* Function,+,submenu_get_selected_item,uint32_t,Submenu* Function,+,submenu_get_view,View*,Submenu* +Function,+,submenu_remove_item,void,"Submenu*, uint32_t" Function,+,submenu_reset,void,Submenu* Function,+,submenu_set_header,void,"Submenu*, const char*" Function,+,submenu_set_orientation,void,"Submenu*, ViewOrientation" From fa6839d283e20fcc0a34e0eb63b52e9f5aa4ee25 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 30 Jun 2025 19:53:45 +0300 Subject: [PATCH 55/75] nfc lib --- lib/nfc/SConscript | 21 +- lib/nfc/helpers/iso14443_4_layer.c | 131 ++++- lib/nfc/helpers/iso14443_4_layer.h | 32 +- lib/nfc/helpers/nxp_native_command.c | 115 ++++ lib/nfc/helpers/nxp_native_command.h | 92 ++++ lib/nfc/helpers/nxp_native_command_mode.h | 11 + lib/nfc/nfc.c | 27 - lib/nfc/nfc.h | 24 - .../iso14443_3a/iso14443_3a_listener.h | 41 ++ .../iso14443_3a/iso14443_3a_listener_i.h | 11 - lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h | 3 +- .../iso14443_4a/iso14443_4a_listener.c | 47 +- .../iso14443_4a/iso14443_4a_listener.h | 12 + .../iso14443_4a/iso14443_4a_listener_i.c | 14 + .../iso14443_4a/iso14443_4a_listener_i.h | 3 + .../iso14443_4a/iso14443_4a_poller_i.c | 8 +- .../iso14443_4b/iso14443_4b_poller_i.c | 4 +- lib/nfc/protocols/mf_desfire/mf_desfire.h | 7 + lib/nfc/protocols/mf_desfire/mf_desfire_i.c | 37 +- lib/nfc/protocols/mf_desfire/mf_desfire_i.h | 50 +- .../protocols/mf_desfire/mf_desfire_poller.c | 3 +- .../protocols/mf_desfire/mf_desfire_poller.h | 56 +- .../mf_desfire/mf_desfire_poller_i.c | 211 +++++--- .../mf_desfire/mf_desfire_poller_i.h | 1 + lib/nfc/protocols/mf_plus/mf_plus_i.c | 82 ++- lib/nfc/protocols/mf_plus/mf_plus_i.h | 5 +- lib/nfc/protocols/mf_plus/mf_plus_poller_i.c | 38 +- lib/nfc/protocols/nfc_device_defs.c | 6 +- lib/nfc/protocols/nfc_listener_defs.c | 8 +- lib/nfc/protocols/nfc_poller_defs.c | 6 +- lib/nfc/protocols/nfc_protocol.c | 42 +- lib/nfc/protocols/nfc_protocol.h | 2 + lib/nfc/protocols/ntag4xx/ntag4xx.c | 192 +++++++ lib/nfc/protocols/ntag4xx/ntag4xx.h | 114 ++++ lib/nfc/protocols/ntag4xx/ntag4xx_i.c | 54 ++ lib/nfc/protocols/ntag4xx/ntag4xx_i.h | 25 + lib/nfc/protocols/ntag4xx/ntag4xx_poller.c | 165 ++++++ lib/nfc/protocols/ntag4xx/ntag4xx_poller.h | 43 ++ .../protocols/ntag4xx/ntag4xx_poller_defs.h | 5 + lib/nfc/protocols/ntag4xx/ntag4xx_poller_i.c | 52 ++ lib/nfc/protocols/ntag4xx/ntag4xx_poller_i.h | 40 ++ lib/nfc/protocols/type_4_tag/type_4_tag.c | 170 ++++++ lib/nfc/protocols/type_4_tag/type_4_tag.h | 84 +++ lib/nfc/protocols/type_4_tag/type_4_tag_i.c | 163 ++++++ lib/nfc/protocols/type_4_tag/type_4_tag_i.h | 110 ++++ .../type_4_tag/type_4_tag_listener.c | 88 +++ .../type_4_tag/type_4_tag_listener.h | 26 + .../type_4_tag/type_4_tag_listener_defs.h | 5 + .../type_4_tag/type_4_tag_listener_i.c | 382 +++++++++++++ .../type_4_tag/type_4_tag_listener_i.h | 38 ++ .../protocols/type_4_tag/type_4_tag_poller.c | 289 ++++++++++ .../protocols/type_4_tag/type_4_tag_poller.h | 63 +++ .../type_4_tag/type_4_tag_poller_defs.h | 5 + .../type_4_tag/type_4_tag_poller_i.c | 501 ++++++++++++++++++ .../type_4_tag/type_4_tag_poller_i.h | 61 +++ 55 files changed, 3521 insertions(+), 304 deletions(-) create mode 100644 lib/nfc/helpers/nxp_native_command.c create mode 100644 lib/nfc/helpers/nxp_native_command.h create mode 100644 lib/nfc/helpers/nxp_native_command_mode.h create mode 100644 lib/nfc/protocols/ntag4xx/ntag4xx.c create mode 100644 lib/nfc/protocols/ntag4xx/ntag4xx.h create mode 100644 lib/nfc/protocols/ntag4xx/ntag4xx_i.c create mode 100644 lib/nfc/protocols/ntag4xx/ntag4xx_i.h create mode 100644 lib/nfc/protocols/ntag4xx/ntag4xx_poller.c create mode 100644 lib/nfc/protocols/ntag4xx/ntag4xx_poller.h create mode 100644 lib/nfc/protocols/ntag4xx/ntag4xx_poller_defs.h create mode 100644 lib/nfc/protocols/ntag4xx/ntag4xx_poller_i.c create mode 100644 lib/nfc/protocols/ntag4xx/ntag4xx_poller_i.h create mode 100644 lib/nfc/protocols/type_4_tag/type_4_tag.c create mode 100644 lib/nfc/protocols/type_4_tag/type_4_tag.h create mode 100644 lib/nfc/protocols/type_4_tag/type_4_tag_i.c create mode 100644 lib/nfc/protocols/type_4_tag/type_4_tag_i.h create mode 100644 lib/nfc/protocols/type_4_tag/type_4_tag_listener.c create mode 100644 lib/nfc/protocols/type_4_tag/type_4_tag_listener.h create mode 100644 lib/nfc/protocols/type_4_tag/type_4_tag_listener_defs.h create mode 100644 lib/nfc/protocols/type_4_tag/type_4_tag_listener_i.c create mode 100644 lib/nfc/protocols/type_4_tag/type_4_tag_listener_i.h create mode 100644 lib/nfc/protocols/type_4_tag/type_4_tag_poller.c create mode 100644 lib/nfc/protocols/type_4_tag/type_4_tag_poller.h create mode 100644 lib/nfc/protocols/type_4_tag/type_4_tag_poller_defs.h create mode 100644 lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.c create mode 100644 lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.h diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index 02b1f09f8..cce1fe94a 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -19,39 +19,48 @@ env.Append( File("protocols/iso14443_3b/iso14443_3b.h"), File("protocols/iso14443_4a/iso14443_4a.h"), File("protocols/iso14443_4b/iso14443_4b.h"), + File("protocols/iso15693_3/iso15693_3.h"), + File("protocols/felica/felica.h"), File("protocols/mf_ultralight/mf_ultralight.h"), File("protocols/mf_classic/mf_classic.h"), File("protocols/mf_plus/mf_plus.h"), File("protocols/mf_desfire/mf_desfire.h"), - File("protocols/emv/emv.h"), File("protocols/slix/slix.h"), File("protocols/st25tb/st25tb.h"), - File("protocols/felica/felica.h"), + File("protocols/ntag4xx/ntag4xx.h"), + File("protocols/type_4_tag/type_4_tag.h"), + File("protocols/emv/emv.h"), # Pollers File("protocols/iso14443_3a/iso14443_3a_poller.h"), File("protocols/iso14443_3b/iso14443_3b_poller.h"), File("protocols/iso14443_4a/iso14443_4a_poller.h"), File("protocols/iso14443_4b/iso14443_4b_poller.h"), + File("protocols/iso15693_3/iso15693_3_poller.h"), + File("protocols/felica/felica_poller.h"), File("protocols/mf_ultralight/mf_ultralight_poller.h"), File("protocols/mf_classic/mf_classic_poller.h"), File("protocols/mf_plus/mf_plus_poller.h"), File("protocols/mf_desfire/mf_desfire_poller.h"), File("protocols/slix/slix_poller.h"), - File("protocols/emv/emv_poller.h"), File("protocols/st25tb/st25tb_poller.h"), - File("protocols/felica/felica_poller.h"), + File("protocols/ntag4xx/ntag4xx_poller.h"), + File("protocols/type_4_tag/type_4_tag_poller.h"), + File("protocols/emv/emv_poller.h"), # Listeners File("protocols/iso14443_3a/iso14443_3a_listener.h"), File("protocols/iso14443_4a/iso14443_4a_listener.h"), + File("protocols/iso15693_3/iso15693_3_listener.h"), + File("protocols/felica/felica_listener.h"), File("protocols/mf_ultralight/mf_ultralight_listener.h"), File("protocols/mf_classic/mf_classic_listener.h"), - File("protocols/felica/felica_listener.h"), + File("protocols/slix/slix_listener.h"), + File("protocols/type_4_tag/type_4_tag_listener.h"), # Sync API File("protocols/iso14443_3a/iso14443_3a_poller_sync.h"), + File("protocols/felica/felica_poller_sync.h"), File("protocols/mf_ultralight/mf_ultralight_poller_sync.h"), File("protocols/mf_classic/mf_classic_poller_sync.h"), File("protocols/st25tb/st25tb_poller_sync.h"), - File("protocols/felica/felica_poller_sync.h"), # Misc File("helpers/nfc_util.h"), File("helpers/iso14443_crc.h"), diff --git a/lib/nfc/helpers/iso14443_4_layer.c b/lib/nfc/helpers/iso14443_4_layer.c index ef9baaabc..1d76f4bf3 100644 --- a/lib/nfc/helpers/iso14443_4_layer.c +++ b/lib/nfc/helpers/iso14443_4_layer.c @@ -14,6 +14,8 @@ #define ISO14443_4_BLOCK_PCB_MASK (0x03) #define ISO14443_4_BLOCK_PCB_I (0U) +#define ISO14443_4_BLOCK_PCB_I_MASK (1U << 1) +#define ISO14443_4_BLOCK_PCB_I_ZERO_MASK (7U << 5) #define ISO14443_4_BLOCK_PCB_I_NAD_OFFSET (2) #define ISO14443_4_BLOCK_PCB_I_CID_OFFSET (3) #define ISO14443_4_BLOCK_PCB_I_CHAIN_OFFSET (4) @@ -33,8 +35,14 @@ #define ISO14443_4_BLOCK_PCB_S_CID_MASK (1U << ISO14443_4_BLOCK_PCB_R_CID_OFFSET) #define ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_MASK (3U << ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_OFFSET) +#define ISO14443_4_BLOCK_CID_MASK (0x0F) + #define ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, mask) (((pcb) & (mask)) == (mask)) +#define ISO14443_4_BLOCK_PCB_IS_I_BLOCK(pcb) \ + (ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, ISO14443_4_BLOCK_PCB_I_MASK) && \ + (((pcb) & ISO14443_4_BLOCK_PCB_I_ZERO_MASK) == 0)) + #define ISO14443_4_BLOCK_PCB_IS_R_BLOCK(pcb) \ ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, ISO14443_4_BLOCK_PCB_R_MASK) @@ -47,14 +55,23 @@ #define ISO14443_4_BLOCK_PCB_R_NACK_ACTIVE(pcb) \ ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, ISO14443_4_BLOCK_PCB_R_NACK_MASK) +#define ISO14443_4_LAYER_NAD_NOT_SUPPORTED ((uint8_t) - 1) +#define ISO14443_4_LAYER_NAD_NOT_SET ((uint8_t) - 2) + struct Iso14443_4Layer { uint8_t pcb; uint8_t pcb_prev; + + // Listener specific + uint8_t cid; + uint8_t nad; }; -static inline void iso14443_4_layer_update_pcb(Iso14443_4Layer* instance) { +static inline void iso14443_4_layer_update_pcb(Iso14443_4Layer* instance, bool toggle_num) { instance->pcb_prev = instance->pcb; - instance->pcb ^= (uint8_t)0x01; + if(toggle_num) { + instance->pcb ^= (uint8_t)0x01; + } } Iso14443_4Layer* iso14443_4_layer_alloc(void) { @@ -73,6 +90,9 @@ void iso14443_4_layer_reset(Iso14443_4Layer* instance) { furi_assert(instance); instance->pcb_prev = 0; instance->pcb = ISO14443_4_BLOCK_PCB_I | ISO14443_4_BLOCK_PCB; + + instance->cid = ISO14443_4_LAYER_CID_NOT_SUPPORTED; + instance->nad = ISO14443_4_LAYER_NAD_NOT_SUPPORTED; } void iso14443_4_layer_set_i_block(Iso14443_4Layer* instance, bool chaining, bool CID_present) { @@ -96,7 +116,7 @@ void iso14443_4_layer_set_s_block(Iso14443_4Layer* instance, bool deselect, bool (CID_present << ISO14443_4_BLOCK_PCB_S_CID_OFFSET) | ISO14443_4_BLOCK_PCB; } -void iso14443_4_layer_encode_block( +void iso14443_4_layer_encode_command( Iso14443_4Layer* instance, const BitBuffer* input_data, BitBuffer* block_data) { @@ -105,7 +125,7 @@ void iso14443_4_layer_encode_block( bit_buffer_append_byte(block_data, instance->pcb); bit_buffer_append(block_data, input_data); - iso14443_4_layer_update_pcb(instance); + iso14443_4_layer_update_pcb(instance, true); } static inline uint8_t iso14443_4_layer_get_response_pcb(const BitBuffer* block_data) { @@ -113,7 +133,7 @@ static inline uint8_t iso14443_4_layer_get_response_pcb(const BitBuffer* block_d return data[0]; } -bool iso14443_4_layer_decode_block( +bool iso14443_4_layer_decode_response( Iso14443_4Layer* instance, BitBuffer* output_data, const BitBuffer* block_data) { @@ -127,7 +147,7 @@ bool iso14443_4_layer_decode_block( ret = (ISO14443_4_BLOCK_PCB_IS_R_BLOCK(response_pcb)) && (!ISO14443_4_BLOCK_PCB_R_NACK_ACTIVE(response_pcb)); instance->pcb &= ISO14443_4_BLOCK_PCB_MASK; - iso14443_4_layer_update_pcb(instance); + iso14443_4_layer_update_pcb(instance, true); } else if(ISO14443_4_BLOCK_PCB_IS_CHAIN_ACTIVE(instance->pcb_prev)) { const uint8_t response_pcb = iso14443_4_layer_get_response_pcb(block_data); ret = (ISO14443_4_BLOCK_PCB_IS_R_BLOCK(response_pcb)) && @@ -147,7 +167,7 @@ bool iso14443_4_layer_decode_block( return ret; } -Iso14443_4aError iso14443_4_layer_decode_block_pwt_ext( +Iso14443_4aError iso14443_4_layer_decode_response_pwt_ext( Iso14443_4Layer* instance, BitBuffer* output_data, const BitBuffer* block_data) { @@ -199,3 +219,100 @@ Iso14443_4aError iso14443_4_layer_decode_block_pwt_ext( return ret; } + +void iso14443_4_layer_set_cid(Iso14443_4Layer* instance, uint8_t cid) { + instance->cid = cid; +} + +void iso14443_4_layer_set_nad_supported(Iso14443_4Layer* instance, bool nad) { + instance->nad = nad ? 0 : ISO14443_4_LAYER_NAD_NOT_SUPPORTED; +} + +Iso14443_4LayerResult iso14443_4_layer_decode_command( + Iso14443_4Layer* instance, + const BitBuffer* input_data, + BitBuffer* block_data) { + furi_assert(instance); + + uint8_t prologue_len = 0; + instance->pcb = bit_buffer_get_byte(input_data, prologue_len++); + + if(ISO14443_4_BLOCK_PCB_IS_I_BLOCK(instance->pcb)) { + if(instance->pcb & ISO14443_4_BLOCK_PCB_I_CID_MASK) { + const uint8_t cid = bit_buffer_get_byte(input_data, prologue_len++) & + ISO14443_4_BLOCK_CID_MASK; + if(instance->cid == ISO14443_4_LAYER_CID_NOT_SUPPORTED || cid != instance->cid) { + return Iso14443_4LayerResultSkip; + } + } else if(instance->cid != ISO14443_4_LAYER_CID_NOT_SUPPORTED && instance->cid != 0) { + return Iso14443_4LayerResultSkip; + } + // TODO: properly handle block chaining + if(instance->pcb & ISO14443_4_BLOCK_PCB_I_NAD_MASK) { + if(instance->nad == ISO14443_4_LAYER_NAD_NOT_SUPPORTED) { + return Iso14443_4LayerResultSkip; + } + instance->nad = bit_buffer_get_byte(input_data, prologue_len++); + } + bit_buffer_copy_right(block_data, input_data, prologue_len); + iso14443_4_layer_update_pcb(instance, false); + return Iso14443_4LayerResultData; + + } else if(ISO14443_4_BLOCK_PCB_IS_S_BLOCK(instance->pcb)) { + if(instance->pcb & ISO14443_4_BLOCK_PCB_S_CID_MASK) { + const uint8_t cid = bit_buffer_get_byte(input_data, prologue_len++) & + ISO14443_4_BLOCK_CID_MASK; + if(instance->cid == ISO14443_4_LAYER_CID_NOT_SUPPORTED || cid != instance->cid) { + return Iso14443_4LayerResultSkip; + } + } else if(instance->cid != ISO14443_4_LAYER_CID_NOT_SUPPORTED && instance->cid != 0) { + return Iso14443_4LayerResultSkip; + } + if((instance->pcb & ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_MASK) == 0) { + // DESELECT + bit_buffer_copy(block_data, input_data); + return Iso14443_4LayerResultSend | Iso14443_4LayerResultHalt; + } else { + // WTX ACK or wrong value + return Iso14443_4LayerResultSkip; + } + + } else if(ISO14443_4_BLOCK_PCB_IS_R_BLOCK(instance->pcb)) { + // TODO: properly handle R blocks while chaining + iso14443_4_layer_update_pcb(instance, true); + instance->pcb |= ISO14443_4_BLOCK_PCB_R_NACK_MASK; + bit_buffer_reset(block_data); + bit_buffer_append_byte(block_data, instance->pcb); + iso14443_4_layer_update_pcb(instance, false); + return Iso14443_4LayerResultSend; + } + return Iso14443_4LayerResultSkip; +} + +bool iso14443_4_layer_encode_response( + Iso14443_4Layer* instance, + const BitBuffer* input_data, + BitBuffer* block_data) { + furi_assert(instance); + + if(ISO14443_4_BLOCK_PCB_IS_I_BLOCK(instance->pcb_prev)) { + bit_buffer_append_byte(block_data, 0x00); + if(instance->pcb_prev & ISO14443_4_BLOCK_PCB_I_CID_MASK) { + bit_buffer_append_byte(block_data, instance->cid); + } + // TODO: properly handle block chaining and related R block responses + if(instance->pcb_prev & ISO14443_4_BLOCK_PCB_I_NAD_MASK && + instance->nad != ISO14443_4_LAYER_NAD_NOT_SET) { + bit_buffer_append_byte(block_data, instance->nad); + instance->nad = ISO14443_4_LAYER_NAD_NOT_SET; + } else { + instance->pcb &= ~ISO14443_4_BLOCK_PCB_I_NAD_MASK; + } + instance->pcb &= ~ISO14443_4_BLOCK_PCB_I_CHAIN_MASK; + bit_buffer_set_byte(block_data, 0, instance->pcb); + bit_buffer_append(block_data, input_data); + iso14443_4_layer_update_pcb(instance, false); + return true; + } + return false; +} diff --git a/lib/nfc/helpers/iso14443_4_layer.h b/lib/nfc/helpers/iso14443_4_layer.h index 9daf25775..1e9ac713f 100644 --- a/lib/nfc/helpers/iso14443_4_layer.h +++ b/lib/nfc/helpers/iso14443_4_layer.h @@ -19,21 +19,47 @@ void iso14443_4_layer_set_i_block(Iso14443_4Layer* instance, bool chaining, bool void iso14443_4_layer_set_r_block(Iso14443_4Layer* instance, bool acknowledged, bool CID_present); void iso14443_4_layer_set_s_block(Iso14443_4Layer* instance, bool deselect, bool CID_present); -void iso14443_4_layer_encode_block( +// Poller mode + +void iso14443_4_layer_encode_command( Iso14443_4Layer* instance, const BitBuffer* input_data, BitBuffer* block_data); -bool iso14443_4_layer_decode_block( +bool iso14443_4_layer_decode_response( Iso14443_4Layer* instance, BitBuffer* output_data, const BitBuffer* block_data); -Iso14443_4aError iso14443_4_layer_decode_block_pwt_ext( +Iso14443_4aError iso14443_4_layer_decode_response_pwt_ext( Iso14443_4Layer* instance, BitBuffer* output_data, const BitBuffer* block_data); +// Listener mode + +typedef enum { + Iso14443_4LayerResultSkip = (0), + Iso14443_4LayerResultData = (1 << 1), + Iso14443_4LayerResultSend = (1 << 2), + Iso14443_4LayerResultHalt = (1 << 3), +} Iso14443_4LayerResult; + +Iso14443_4LayerResult iso14443_4_layer_decode_command( + Iso14443_4Layer* instance, + const BitBuffer* input_data, + BitBuffer* block_data); + +bool iso14443_4_layer_encode_response( + Iso14443_4Layer* instance, + const BitBuffer* input_data, + BitBuffer* block_data); + +#define ISO14443_4_LAYER_CID_NOT_SUPPORTED ((uint8_t) - 1) +void iso14443_4_layer_set_cid(Iso14443_4Layer* instance, uint8_t cid); + +void iso14443_4_layer_set_nad_supported(Iso14443_4Layer* instance, bool nad); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/helpers/nxp_native_command.c b/lib/nfc/helpers/nxp_native_command.c new file mode 100644 index 000000000..d5d535891 --- /dev/null +++ b/lib/nfc/helpers/nxp_native_command.c @@ -0,0 +1,115 @@ +#include "nxp_native_command.h" + +#include + +#define TAG "NxpNativeCommand" + +Iso14443_4aError nxp_native_command_iso14443_4a_poller( + Iso14443_4aPoller* iso14443_4a_poller, + NxpNativeCommandStatus* status_code, + const BitBuffer* input_buffer, + BitBuffer* result_buffer, + NxpNativeCommandMode command_mode, + BitBuffer* tx_buffer, + BitBuffer* rx_buffer) { + furi_check(iso14443_4a_poller); + furi_check(tx_buffer); + furi_check(rx_buffer); + furi_check(input_buffer); + furi_check(result_buffer); + furi_check(command_mode < NxpNativeCommandModeMAX); + + Iso14443_4aError error = Iso14443_4aErrorNone; + *status_code = NXP_NATIVE_COMMAND_STATUS_OPERATION_OK; + + do { + bit_buffer_reset(tx_buffer); + if(command_mode == NxpNativeCommandModePlain) { + bit_buffer_append(tx_buffer, input_buffer); + } else if(command_mode == NxpNativeCommandModeIsoWrapped) { + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_CLA); + bit_buffer_append_byte(tx_buffer, bit_buffer_get_byte(input_buffer, 0)); + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_P1); + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_P2); + if(bit_buffer_get_size_bytes(input_buffer) > 1) { + bit_buffer_append_byte(tx_buffer, bit_buffer_get_size_bytes(input_buffer) - 1); + bit_buffer_append_right(tx_buffer, input_buffer, 1); + } + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_LE); + } + + bit_buffer_reset(rx_buffer); + error = iso14443_4a_poller_send_block(iso14443_4a_poller, tx_buffer, rx_buffer); + + if(error != Iso14443_4aErrorNone) { + break; + } + + bit_buffer_reset(tx_buffer); + if(command_mode == NxpNativeCommandModePlain) { + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_STATUS_ADDITIONAL_FRAME); + } else if(command_mode == NxpNativeCommandModeIsoWrapped) { + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_CLA); + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_STATUS_ADDITIONAL_FRAME); + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_P1); + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_P2); + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_LE); + } + + size_t response_len = bit_buffer_get_size_bytes(rx_buffer); + *status_code = NXP_NATIVE_COMMAND_STATUS_LENGTH_ERROR; + bit_buffer_reset(result_buffer); + if(command_mode == NxpNativeCommandModePlain && response_len >= sizeof(uint8_t)) { + *status_code = bit_buffer_get_byte(rx_buffer, 0); + if(response_len > sizeof(uint8_t)) { + bit_buffer_copy_right(result_buffer, rx_buffer, sizeof(uint8_t)); + } + } else if( + command_mode == NxpNativeCommandModeIsoWrapped && + response_len >= 2 * sizeof(uint8_t) && + bit_buffer_get_byte(rx_buffer, response_len - 2) == NXP_NATIVE_COMMAND_ISO_SW1) { + *status_code = bit_buffer_get_byte(rx_buffer, response_len - 1); + if(response_len > 2 * sizeof(uint8_t)) { + bit_buffer_copy_left(result_buffer, rx_buffer, response_len - 2 * sizeof(uint8_t)); + } + } + + while(*status_code == NXP_NATIVE_COMMAND_STATUS_ADDITIONAL_FRAME) { + bit_buffer_reset(rx_buffer); + error = iso14443_4a_poller_send_block(iso14443_4a_poller, tx_buffer, rx_buffer); + + if(error != Iso14443_4aErrorNone) { + break; + } + + const size_t rx_size = bit_buffer_get_size_bytes(rx_buffer); + const size_t rx_capacity_remaining = bit_buffer_get_capacity_bytes(result_buffer) - + bit_buffer_get_size_bytes(result_buffer); + + if(command_mode == NxpNativeCommandModePlain) { + *status_code = rx_size >= 1 ? bit_buffer_get_byte(rx_buffer, 0) : + NXP_NATIVE_COMMAND_STATUS_LENGTH_ERROR; + if(rx_size <= rx_capacity_remaining + 1) { + bit_buffer_append_right(result_buffer, rx_buffer, sizeof(uint8_t)); + } else { + FURI_LOG_W(TAG, "RX buffer overflow: ignoring %zu bytes", rx_size - 1); + } + } else if(command_mode == NxpNativeCommandModeIsoWrapped) { + if(rx_size >= 2 && + bit_buffer_get_byte(rx_buffer, rx_size - 2) == NXP_NATIVE_COMMAND_ISO_SW1) { + *status_code = bit_buffer_get_byte(rx_buffer, rx_size - 1); + } else { + *status_code = NXP_NATIVE_COMMAND_STATUS_LENGTH_ERROR; + } + if(rx_size <= rx_capacity_remaining + 2) { + bit_buffer_set_size_bytes(rx_buffer, rx_size - 2); + bit_buffer_append(result_buffer, rx_buffer); + } else { + FURI_LOG_W(TAG, "RX buffer overflow: ignoring %zu bytes", rx_size - 2); + } + } + } + } while(false); + + return error; +} diff --git a/lib/nfc/helpers/nxp_native_command.h b/lib/nfc/helpers/nxp_native_command.h new file mode 100644 index 000000000..61677a3f6 --- /dev/null +++ b/lib/nfc/helpers/nxp_native_command.h @@ -0,0 +1,92 @@ +#pragma once + +#include "nxp_native_command_mode.h" + +#include + +// ISO 7816 command wrapping +#define NXP_NATIVE_COMMAND_ISO_CLA (0x90) +#define NXP_NATIVE_COMMAND_ISO_P1 (0x00) +#define NXP_NATIVE_COMMAND_ISO_P2 (0x00) +#define NXP_NATIVE_COMMAND_ISO_LE (0x00) +// ISO 7816 status wrapping +#define NXP_NATIVE_COMMAND_ISO_SW1 (0x91) + +// Successful operation +#define NXP_NATIVE_COMMAND_STATUS_OPERATION_OK (0x00) +// No changes done to backup files, CommitTransaction / AbortTransaction not necessary +#define NXP_NATIVE_COMMAND_STATUS_NO_CHANGES (0x0C) +// Insufficient NV-Memory to complete command +#define NXP_NATIVE_COMMAND_STATUS_OUT_OF_EEPROM_ERROR (0x0E) +// Command code not supported +#define NXP_NATIVE_COMMAND_STATUS_ILLEGAL_COMMAND_CODE (0x1C) +// CRC or MAC does not match data Padding bytes not valid +#define NXP_NATIVE_COMMAND_STATUS_INTEGRITY_ERROR (0x1E) +// Invalid key number specified +#define NXP_NATIVE_COMMAND_STATUS_NO_SUCH_KEY (0x40) +// Length of command string invalid +#define NXP_NATIVE_COMMAND_STATUS_LENGTH_ERROR (0x7E) +// Current configuration / status does not allow the requested command +#define NXP_NATIVE_COMMAND_STATUS_PERMISSION_DENIED (0x9D) +// Value of the parameter(s) invalid +#define NXP_NATIVE_COMMAND_STATUS_PARAMETER_ERROR (0x9E) +// Requested AID not present on PICC +#define NXP_NATIVE_COMMAND_STATUS_APPLICATION_NOT_FOUND (0xA0) +// Unrecoverable error within application, application will be disabled +#define NXP_NATIVE_COMMAND_STATUS_APPL_INTEGRITY_ERROR (0xA1) +// Currently not allowed to authenticate. Keep trying until full delay is spent +#define NXP_NATIVE_COMMAND_STATUS_STATUS_AUTHENTICATION_DELAY (0xAD) +// Current authentication status does not allow the requested command +#define NXP_NATIVE_COMMAND_STATUS_AUTHENTICATION_ERROR (0xAE) +// Additional data frame is expected to be sent +#define NXP_NATIVE_COMMAND_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 NXP_NATIVE_COMMAND_STATUS_BOUNDARY_ERROR (0xBE) +// Unrecoverable error within PICC, PICC will be disabled +#define NXP_NATIVE_COMMAND_STATUS_PICC_INTEGRITY_ERROR (0xC1) +// Previous Command was not fully completed. Not all Frames were requested or provided by the PCD +#define NXP_NATIVE_COMMAND_STATUS_COMMAND_ABORTED (0xCA) +// PICC was disabled by an unrecoverable error +#define NXP_NATIVE_COMMAND_STATUS_PICC_DISABLED_ERROR (0xCD) +// Number of Applications limited to 28, no additional CreateApplication possible +#define NXP_NATIVE_COMMAND_STATUS_COUNT_ERROR (0xCE) +// Creation of file/application failed because file/application with same number already exists +#define NXP_NATIVE_COMMAND_STATUS_DUBLICATE_ERROR (0xDE) +// Could not complete NV-write operation due to loss of power, internal backup/rollback mechanism activated +#define NXP_NATIVE_COMMAND_STATUS_EEPROM_ERROR (0xEE) +// Specified file number does not exist +#define NXP_NATIVE_COMMAND_STATUS_FILE_NOT_FOUND (0xF0) +// Unrecoverable error within file, file will be disabled +#define NXP_NATIVE_COMMAND_STATUS_FILE_INTEGRITY_ERROR (0xF1) + +typedef uint8_t NxpNativeCommandStatus; + +/** + * @brief Transmit and receive NXP Native Command chunks in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The result_buffer will be filled with any data received as a response to data + * sent from input_buffer, with a timeout defined by the fwt parameter. + * + * The tx_buffer and rx_buffer are used as working areas to handle individual + * command chunks and responses. + * + * @param[in, out] iso14443_4a_poller pointer to the instance to be used in the transaction. + * @param[out] status_code pointer to a status variable to hold the result of the operation. + * @param[in] input_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] result_buffer pointer to the buffer to be filled with received data. + * @param[in] command_mode what command formatting mode to use for the transaction. + * @param[in, out] tx_buffer pointer to the buffer for command construction. + * @param[in, out] rx_buffer pointer to the buffer for response handling. + * @return Iso14443_4aErrorNone and STATUS_OPERATION_OK on success, an error code on failure. + */ +Iso14443_4aError nxp_native_command_iso14443_4a_poller( + Iso14443_4aPoller* iso14443_4a_poller, + NxpNativeCommandStatus* status_code, + const BitBuffer* input_buffer, + BitBuffer* result_buffer, + NxpNativeCommandMode command_mode, + BitBuffer* tx_buffer, + BitBuffer* rx_buffer); diff --git a/lib/nfc/helpers/nxp_native_command_mode.h b/lib/nfc/helpers/nxp_native_command_mode.h new file mode 100644 index 000000000..7684b4d48 --- /dev/null +++ b/lib/nfc/helpers/nxp_native_command_mode.h @@ -0,0 +1,11 @@ +#pragma once + +/** + * @brief Enumeration of possible command modes. + */ +typedef enum { + NxpNativeCommandModePlain, /**< Plain native commands. */ + NxpNativeCommandModeIsoWrapped, /**< ISO 7816-wrapped commands. */ + + NxpNativeCommandModeMAX, +} NxpNativeCommandMode; diff --git a/lib/nfc/nfc.c b/lib/nfc/nfc.c index 4f4358711..90e65b282 100644 --- a/lib/nfc/nfc.c +++ b/lib/nfc/nfc.c @@ -646,33 +646,6 @@ NfcError nfc_iso15693_listener_tx_sof(Nfc* instance) { return ret; } -NfcError nfc_iso15693_detect_mode(Nfc* instance) { - furi_check(instance); - - FuriHalNfcError error = furi_hal_nfc_iso15693_detect_mode(); - NfcError ret = nfc_process_hal_error(error); - - return ret; -} - -NfcError nfc_iso15693_force_1outof4(Nfc* instance) { - furi_check(instance); - - FuriHalNfcError error = furi_hal_nfc_iso15693_force_1outof4(); - NfcError ret = nfc_process_hal_error(error); - - return ret; -} - -NfcError nfc_iso15693_force_1outof256(Nfc* instance) { - furi_check(instance); - - FuriHalNfcError error = furi_hal_nfc_iso15693_force_1outof256(); - NfcError ret = nfc_process_hal_error(error); - - return ret; -} - NfcError nfc_felica_listener_set_sensf_res_data( Nfc* instance, const uint8_t* idm, diff --git a/lib/nfc/nfc.h b/lib/nfc/nfc.h index ebd29dc4b..8fbf90d1f 100644 --- a/lib/nfc/nfc.h +++ b/lib/nfc/nfc.h @@ -380,30 +380,6 @@ NfcError nfc_felica_listener_set_sensf_res_data( */ NfcError nfc_iso15693_listener_tx_sof(Nfc* instance); -/** - * @brief Set ISO15693 parser mode to autodetect - * -* @param[in,out] instance pointer to the instance to be configured. - * @returns NfcErrorNone on success, any other error code on failure. -*/ -NfcError nfc_iso15693_detect_mode(Nfc* instance); - -/** - * @brief Set ISO15693 parser mode to 1OutOf4, disables autodetection - * - * @param[in,out] instance pointer to the instance to be configured. - * @return NfcErrorNone on success, any other error code on failure. -*/ -NfcError nfc_iso15693_force_1outof4(Nfc* instance); - -/** - * @brief Set ISO15693 parser mode to 1OutOf256, disables autodetection - * - * @param[in,out] instance pointer to the instance to be configured. - * @return NfcErrorNone on success, any other error code on failure. -*/ -NfcError nfc_iso15693_force_1outof256(Nfc* instance); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h index 8a550ca0a..7b2a4424d 100644 --- a/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h @@ -26,6 +26,47 @@ typedef struct { Iso14443_3aListenerEventData* data; } Iso14443_3aListenerEvent; +/** + * @brief Transmit Iso14443_3a frames in listener mode. + * + * Must ONLY be used inside the callback function. + * + * + * @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. + * @return Iso14443_3aErrorNone on success, an error code on failure. + */ +Iso14443_3aError + iso14443_3a_listener_tx(Iso14443_3aListener* instance, const BitBuffer* tx_buffer); + +/** + * @brief Transmit Iso14443_3a frames with custom parity bits in listener mode. + * + * Must ONLY be used inside the callback function. + * + * Custom parity bits must be set in the tx_buffer. + * + * @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. + * @return Iso14443_3aErrorNone on success, an error code on failure. + */ +Iso14443_3aError iso14443_3a_listener_tx_with_custom_parity( + Iso14443_3aListener* instance, + const BitBuffer* tx_buffer); + +/** + * @brief Transmit Iso14443_3a standard frames in listener mode. + * + * Must ONLY be used inside the callback function. + * + * @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. + * @return Iso14443_3aErrorNone on success, an error code on failure. + */ +Iso14443_3aError iso14443_3a_listener_send_standard_frame( + Iso14443_3aListener* instance, + const BitBuffer* tx_buffer); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.h index 0113a1cb8..ef9edf18f 100644 --- a/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.h +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.h @@ -26,17 +26,6 @@ struct Iso14443_3aListener { void* context; }; -Iso14443_3aError - iso14443_3a_listener_tx(Iso14443_3aListener* instance, const BitBuffer* tx_buffer); - -Iso14443_3aError iso14443_3a_listener_tx_with_custom_parity( - Iso14443_3aListener* instance, - const BitBuffer* tx_buffer); - -Iso14443_3aError iso14443_3a_listener_send_standard_frame( - Iso14443_3aListener* instance, - const BitBuffer* tx_buffer); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h index 9b0230975..f28b26ddd 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h @@ -2,7 +2,8 @@ #include "iso14443_4a.h" -#define ISO14443_4A_CMD_READ_ATS (0xE0) +#define ISO14443_4A_CMD_READ_ATS (0xE0) +#define ISO14443_4A_READ_ATS_CID_MASK (0x0F) // ATS bit definitions #define ISO14443_4A_ATS_T0_TA1 (1U << 4) diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c index 2519fb90c..95bae45d4 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c @@ -14,7 +14,9 @@ static Iso14443_4aListener* Iso14443_4aListener* instance = malloc(sizeof(Iso14443_4aListener)); instance->iso14443_3a_listener = iso14443_3a_listener; instance->data = data; + instance->iso14443_4_layer = iso14443_4_layer_alloc(); + instance->rx_buffer = bit_buffer_alloc(ISO14443_4A_LISTENER_BUF_SIZE); instance->tx_buffer = bit_buffer_alloc(ISO14443_4A_LISTENER_BUF_SIZE); instance->iso14443_4a_event.data = &instance->iso14443_4a_event_data; @@ -27,13 +29,18 @@ static Iso14443_4aListener* static void iso14443_4a_listener_free(Iso14443_4aListener* instance) { furi_assert(instance); - furi_assert(instance->data); - furi_assert(instance->tx_buffer); + iso14443_4_layer_free(instance->iso14443_4_layer); + bit_buffer_free(instance->rx_buffer); bit_buffer_free(instance->tx_buffer); free(instance); } +static void iso14443_4a_listener_reset(Iso14443_4aListener* instance) { + instance->state = Iso14443_4aListenerStateIdle; + iso14443_4_layer_reset(instance->iso14443_4_layer); +} + static void iso14443_4a_listener_set_callback( Iso14443_4aListener* instance, NfcGenericCallback callback, @@ -68,20 +75,46 @@ static NfcCommand iso14443_4a_listener_run(NfcGenericEvent event, void* context) if(iso14443_4a_listener_send_ats(instance, &instance->data->ats_data) == Iso14443_4aErrorNone) { instance->state = Iso14443_4aListenerStateActive; + if(iso14443_4a_supports_frame_option( + instance->data, Iso14443_4aFrameOptionCid)) { + const uint8_t cid = bit_buffer_get_byte(rx_buffer, 1) & + ISO14443_4A_READ_ATS_CID_MASK; + iso14443_4_layer_set_cid(instance->iso14443_4_layer, cid); + } + iso14443_4_layer_set_nad_supported( + instance->iso14443_4_layer, + iso14443_4a_supports_frame_option( + instance->data, Iso14443_4aFrameOptionNad)); } } } else { - instance->iso14443_4a_event.type = Iso14443_4aListenerEventTypeReceivedData; - instance->iso14443_4a_event.data->buffer = rx_buffer; + Iso14443_4LayerResult status = iso14443_4_layer_decode_command( + instance->iso14443_4_layer, rx_buffer, instance->rx_buffer); + if(status & Iso14443_4LayerResultSend) { + iso14443_3a_listener_send_standard_frame( + instance->iso14443_3a_listener, instance->rx_buffer); + } + if(status & Iso14443_4LayerResultHalt) { + iso14443_4a_listener_reset(instance); + if(instance->callback) { + instance->iso14443_4a_event.type = Iso14443_4aListenerEventTypeHalted; + instance->callback(instance->generic_event, instance->context); + } + command = NfcCommandSleep; + } + if(status & Iso14443_4LayerResultData) { + instance->iso14443_4a_event.type = Iso14443_4aListenerEventTypeReceivedData; + instance->iso14443_4a_event.data->buffer = instance->rx_buffer; - if(instance->callback) { - command = instance->callback(instance->generic_event, instance->context); + if(instance->callback) { + command = instance->callback(instance->generic_event, instance->context); + } } } } else if( iso14443_3a_event->type == Iso14443_3aListenerEventTypeHalted || iso14443_3a_event->type == Iso14443_3aListenerEventTypeFieldOff) { - instance->state = Iso14443_4aListenerStateIdle; + iso14443_4a_listener_reset(instance); instance->iso14443_4a_event.type = iso14443_3a_event->type == Iso14443_3aListenerEventTypeHalted ? diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h index 04f0b197a..ff4bad7e4 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h @@ -25,6 +25,18 @@ typedef struct { Iso14443_4aListenerEventData* data; } Iso14443_4aListenerEvent; +/** + * @brief Transmit Iso14443_4a blocks in listener mode. + * + * Must ONLY be used inside the callback function. + * + * @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. + * @return Iso14443_4aErrorNone on success, an error code on failure. + */ +Iso14443_4aError + iso14443_4a_listener_send_block(Iso14443_4aListener* instance, const BitBuffer* tx_buffer); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.c index 8590c22ad..6ea5ae5e4 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.c +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.c @@ -30,3 +30,17 @@ Iso14443_4aError instance->iso14443_3a_listener, instance->tx_buffer); return iso14443_4a_process_error(error); } + +Iso14443_4aError + iso14443_4a_listener_send_block(Iso14443_4aListener* instance, const BitBuffer* tx_buffer) { + bit_buffer_reset(instance->tx_buffer); + + if(!iso14443_4_layer_encode_response( + instance->iso14443_4_layer, tx_buffer, instance->tx_buffer)) { + return Iso14443_4aErrorProtocol; + } + + const Iso14443_3aError error = iso14443_3a_listener_send_standard_frame( + instance->iso14443_3a_listener, instance->tx_buffer); + return iso14443_4a_process_error(error); +} diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.h index d4e884f6f..c0ed6d1cb 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include "iso14443_4a_listener.h" #include "iso14443_4a_i.h" @@ -17,8 +18,10 @@ typedef enum { struct Iso14443_4aListener { Iso14443_3aListener* iso14443_3a_listener; Iso14443_4aData* data; + Iso14443_4Layer* iso14443_4_layer; Iso14443_4aListenerState state; + BitBuffer* rx_buffer; BitBuffer* tx_buffer; NfcGenericEvent generic_event; diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c index a2342a92d..97c512205 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c @@ -65,7 +65,7 @@ Iso14443_4aError iso14443_4a_poller_send_block( furi_check(rx_buffer); bit_buffer_reset(instance->tx_buffer); - iso14443_4_layer_encode_block(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer); + iso14443_4_layer_encode_command(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer); Iso14443_4aError error = Iso14443_4aErrorNone; @@ -106,7 +106,7 @@ Iso14443_4aError iso14443_4a_poller_send_block( } while(bit_buffer_starts_with_byte(instance->rx_buffer, ISO14443_4A_SWTX)); } - if(!iso14443_4_layer_decode_block( + if(!iso14443_4_layer_decode_response( instance->iso14443_4_layer, rx_buffer, instance->rx_buffer)) { error = Iso14443_4aErrorProtocol; break; @@ -155,7 +155,7 @@ Iso14443_4aError iso14443_4a_poller_send_block_pwt_ext( uint8_t attempts_left = ISO14443_4A_SEND_BLOCK_MAX_ATTEMPTS; bit_buffer_reset(instance->tx_buffer); - iso14443_4_layer_encode_block(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer); + iso14443_4_layer_encode_command(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer); Iso14443_4aError error = Iso14443_4aErrorNone; @@ -180,7 +180,7 @@ Iso14443_4aError iso14443_4a_poller_send_block_pwt_ext( break; } else { - error = iso14443_4_layer_decode_block_pwt_ext( + error = iso14443_4_layer_decode_response_pwt_ext( instance->iso14443_4_layer, rx_buffer, instance->rx_buffer); if(error == Iso14443_4aErrorSendExtra) { if(--attempts_left == 0) break; diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.c b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.c index da82e1417..2db2da9c1 100644 --- a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.c +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.c @@ -24,7 +24,7 @@ Iso14443_4bError iso14443_4b_poller_send_block( furi_check(rx_buffer); bit_buffer_reset(instance->tx_buffer); - iso14443_4_layer_encode_block(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer); + iso14443_4_layer_encode_command(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer); Iso14443_4bError error = Iso14443_4bErrorNone; @@ -36,7 +36,7 @@ Iso14443_4bError iso14443_4b_poller_send_block( error = iso14443_4b_process_error(iso14443_3b_error); break; - } else if(!iso14443_4_layer_decode_block( + } else if(!iso14443_4_layer_decode_response( instance->iso14443_4_layer, rx_buffer, instance->rx_buffer)) { error = Iso14443_4bErrorProtocol; break; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire.h b/lib/nfc/protocols/mf_desfire/mf_desfire.h index ec60b336b..cb5c6cac8 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire.h @@ -17,6 +17,13 @@ extern "C" { #define MF_DESFIRE_CMD_GET_FILE_IDS (0x6F) #define MF_DESFIRE_CMD_GET_FILE_SETTINGS (0xF5) +#define MF_DESFIRE_CMD_CREATE_APPLICATION (0xCA) +#define MF_DESFIRE_CMD_CREATE_STD_DATA_FILE (0xCD) +#define MF_DESFIRE_CMD_CREATE_BACKUP_DATA_FILE (0xCB) +#define MF_DESFIRE_CMD_CREATE_VALUE_FILE (0xCC) +#define MF_DESFIRE_CMD_CREATE_LINEAR_RECORD_FILE (0xC1) +#define MF_DESFIRE_CMD_CREATE_CYCLIC_RECORD_FILE (0xC0) + #define MF_DESFIRE_CMD_READ_DATA (0xBD) #define MF_DESFIRE_CMD_GET_VALUE (0x6C) #define MF_DESFIRE_CMD_READ_RECORDS (0xBB) diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c index eba9c4312..b90ef8ccf 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c @@ -60,7 +60,7 @@ bool mf_desfire_version_parse(MfDesfireVersion* data, const BitBuffer* buf) { bit_buffer_write_bytes(buf, data, sizeof(MfDesfireVersion)); } - return can_parse; + return can_parse && (data->hw_type & 0x0F) == 0x01; } bool mf_desfire_free_memory_parse(MfDesfireFreeMemory* data, const BitBuffer* buf) { @@ -81,17 +81,17 @@ bool mf_desfire_free_memory_parse(MfDesfireFreeMemory* data, const BitBuffer* bu return can_parse; } -bool mf_desfire_key_settings_parse(MfDesfireKeySettings* data, const BitBuffer* buf) { - typedef struct FURI_PACKED { - bool is_master_key_changeable : 1; - bool is_free_directory_list : 1; - bool is_free_create_delete : 1; - bool is_config_changeable : 1; - uint8_t change_key_id : 4; - uint8_t max_keys : 4; - uint8_t flags : 4; - } MfDesfireKeySettingsLayout; +typedef struct FURI_PACKED { + bool is_master_key_changeable : 1; + bool is_free_directory_list : 1; + bool is_free_create_delete : 1; + bool is_config_changeable : 1; + uint8_t change_key_id : 4; + uint8_t max_keys : 4; + uint8_t flags : 4; +} MfDesfireKeySettingsLayout; +bool mf_desfire_key_settings_parse(MfDesfireKeySettings* data, const BitBuffer* buf) { const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireKeySettingsLayout); if(can_parse) { @@ -111,6 +111,21 @@ bool mf_desfire_key_settings_parse(MfDesfireKeySettings* data, const BitBuffer* return can_parse; } +void mf_desfire_key_settings_dump(const MfDesfireKeySettings* data, BitBuffer* buf) { + MfDesfireKeySettingsLayout layout; + + layout.is_master_key_changeable = data->is_master_key_changeable; + layout.is_free_directory_list = data->is_free_directory_list; + layout.is_free_create_delete = data->is_free_create_delete; + layout.is_config_changeable = data->is_config_changeable; + + layout.change_key_id = data->change_key_id; + layout.max_keys = data->max_keys; + layout.flags = data->flags; + + bit_buffer_append_bytes(buf, (uint8_t*)&layout, sizeof(MfDesfireKeySettingsLayout)); +} + bool mf_desfire_key_version_parse(MfDesfireKeyVersion* data, const BitBuffer* buf) { const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireKeyVersion); diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_i.h b/lib/nfc/protocols/mf_desfire/mf_desfire_i.h index 921bbb9de..ac572ae58 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_i.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_i.h @@ -2,55 +2,11 @@ #include "mf_desfire.h" +#include + #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; @@ -68,6 +24,8 @@ bool mf_desfire_free_memory_parse(MfDesfireFreeMemory* data, const BitBuffer* bu bool mf_desfire_key_settings_parse(MfDesfireKeySettings* data, const BitBuffer* buf); +void mf_desfire_key_settings_dump(const MfDesfireKeySettings* data, BitBuffer* buf); + bool mf_desfire_key_version_parse(MfDesfireKeyVersion* data, const BitBuffer* buf); bool mf_desfire_application_id_parse( diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c index 45e5a27f9..e9c5e3b6d 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c @@ -251,8 +251,7 @@ static bool mf_desfire_poller_detect(NfcGenericEvent event, void* context) { MfDesfireError error = mf_desfire_poller_read_key_version(instance, 0, &key_version); if(error != MfDesfireErrorNone) break; - MfDesfireVersion version = {}; - error = mf_desfire_poller_read_version(instance, &version); + error = mf_desfire_poller_read_version(instance, &instance->data->version); if(error != MfDesfireErrorNone) break; protocol_detected = true; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h index 707df42cd..4010938e5 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h @@ -3,6 +3,7 @@ #include "mf_desfire.h" #include +#include #ifdef __cplusplus extern "C" { @@ -38,6 +39,16 @@ typedef struct { MfDesfirePollerEventData* data; /**< Pointer to event specific data. */ } MfDesfirePollerEvent; +/** + * @brief Change command mode used in poller mode. + * + * @param[in, out] instance pointer to the instance to affect. + * @param[in] command_mode command mode to use in further communication with the card. + */ +void mf_desfire_poller_set_command_mode( + MfDesfirePoller* instance, + NxpNativeCommandMode command_mode); + /** * @brief Transmit and receive MfDesfire chunks in poller mode. * @@ -51,11 +62,16 @@ typedef struct { * @param[out] rx_buffer pointer to the buffer to be filled with received data. * @return MfDesfireErrorNone on success, an error code on failure. */ -MfDesfireError mf_desfire_send_chunks( +MfDesfireError mf_desfire_poller_send_chunks( MfDesfirePoller* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer); +/** + * @warning deprecated, use mf_desfire_poller_send_chunks instead + */ +#define mf_desfire_send_chunks mf_desfire_poller_send_chunks + /** * @brief Read MfDesfire card version. * @@ -187,6 +203,44 @@ MfDesfireError mf_desfire_poller_read_file_settings_multi( const SimpleArray* file_ids, SimpleArray* data); +/** + * @brief Create Application on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] id pointer to the application id for the new application. + * @param[in] key_settings pointer to the key settings for the new application. + * @param[in] iso_df_id optional iso identifier for the new application. + * @param[in] iso_df_name optional iso name for the new application. + * @param[in] iso_df_name_len length of the optional iso application name. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_poller_create_application( + MfDesfirePoller* instance, + const MfDesfireApplicationId* id, + const MfDesfireKeySettings* key_settings, + uint16_t iso_df_id, + const uint8_t* iso_df_name, + uint8_t iso_df_name_len); + +/** + * @brief Create File on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] id file id for the new file. + * @param[in] data pointer to the file settings for the new file. + * @param[in] iso_ef_id optional iso identifier for the new file. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_poller_create_file( + MfDesfirePoller* instance, + MfDesfireFileId id, + const MfDesfireFileSettings* data, + uint16_t iso_ef_id); + /** * @brief Read file data on MfDesfire card. * 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 8b57fcc4c..a44395c60 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c @@ -1,6 +1,7 @@ #include "mf_desfire_poller_i.h" #include +#include #include "mf_desfire_i.h" @@ -21,76 +22,48 @@ 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: + case NXP_NATIVE_COMMAND_STATUS_OPERATION_OK: return MfDesfireErrorNone; - case MF_DESFIRE_STATUS_AUTHENTICATION_ERROR: + case NXP_NATIVE_COMMAND_STATUS_AUTHENTICATION_ERROR: return MfDesfireErrorAuthentication; - case MF_DESFIRE_STATUS_ILLEGAL_COMMAND_CODE: + case NXP_NATIVE_COMMAND_STATUS_ILLEGAL_COMMAND_CODE: return MfDesfireErrorCommandNotSupported; default: return MfDesfireErrorProtocol; } } -MfDesfireError mf_desfire_send_chunks( +void mf_desfire_poller_set_command_mode( + MfDesfirePoller* instance, + NxpNativeCommandMode command_mode) { + furi_check(instance); + furi_check(instance->state == MfDesfirePollerStateIdle); + furi_check(command_mode < NxpNativeCommandModeMAX); + + instance->command_mode = command_mode; +} + +MfDesfireError mf_desfire_poller_send_chunks( MfDesfirePoller* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer) { furi_check(instance); - furi_check(instance->iso14443_4a_poller); - furi_check(instance->tx_buffer); - furi_check(instance->rx_buffer); - furi_check(tx_buffer); - furi_check(rx_buffer); - MfDesfireError error = MfDesfireErrorNone; + NxpNativeCommandStatus status_code = NXP_NATIVE_COMMAND_STATUS_OPERATION_OK; + Iso14443_4aError iso14443_4a_error = nxp_native_command_iso14443_4a_poller( + instance->iso14443_4a_poller, + &status_code, + tx_buffer, + rx_buffer, + instance->command_mode, + instance->tx_buffer, + instance->rx_buffer); - do { - Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block( - instance->iso14443_4a_poller, tx_buffer, instance->rx_buffer); - - if(iso14443_4a_error != Iso14443_4aErrorNone) { - error = mf_desfire_process_error(iso14443_4a_error); - break; - } - - bit_buffer_reset(instance->tx_buffer); - 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)); - } else { - bit_buffer_reset(rx_buffer); - } - - 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); - - if(iso14443_4a_error != Iso14443_4aErrorNone) { - error = mf_desfire_process_error(iso14443_4a_error); - break; - } - - const size_t rx_size = bit_buffer_get_size_bytes(instance->rx_buffer); - const size_t rx_capacity_remaining = - bit_buffer_get_capacity_bytes(rx_buffer) - bit_buffer_get_size_bytes(rx_buffer); - - if(rx_size <= rx_capacity_remaining + 1) { - bit_buffer_append_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t)); - } else { - FURI_LOG_W(TAG, "RX buffer overflow: ignoring %zu bytes", rx_size - 1); - } - } - } 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); + if(iso14443_4a_error != Iso14443_4aErrorNone) { + return mf_desfire_process_error(iso14443_4a_error); } - return error; + return mf_desfire_process_status_code(status_code); } MfDesfireError mf_desfire_poller_read_version(MfDesfirePoller* instance, MfDesfireVersion* data) { @@ -102,7 +75,8 @@ MfDesfireError mf_desfire_poller_read_version(MfDesfirePoller* instance, MfDesfi MfDesfireError error; do { - error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + error = mf_desfire_poller_send_chunks( + instance, instance->input_buffer, instance->result_buffer); if(error != MfDesfireErrorNone) break; @@ -124,7 +98,8 @@ MfDesfireError MfDesfireError error; do { - error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + error = mf_desfire_poller_send_chunks( + instance, instance->input_buffer, instance->result_buffer); if(error != MfDesfireErrorNone) break; @@ -146,7 +121,8 @@ MfDesfireError MfDesfireError error; do { - error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + error = mf_desfire_poller_send_chunks( + instance, instance->input_buffer, instance->result_buffer); if(error != MfDesfireErrorNone) break; @@ -170,7 +146,7 @@ MfDesfireError mf_desfire_poller_read_key_version( bit_buffer_set_byte(instance->input_buffer, 1, key_num); MfDesfireError error = - mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + mf_desfire_poller_send_chunks(instance, instance->input_buffer, instance->result_buffer); if(error == MfDesfireErrorNone) { if(!mf_desfire_key_version_parse(data, instance->result_buffer)) { error = MfDesfireErrorProtocol; @@ -210,7 +186,8 @@ MfDesfireError MfDesfireError error; do { - error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + error = mf_desfire_poller_send_chunks( + instance, instance->input_buffer, instance->result_buffer); if(error != MfDesfireErrorNone) break; @@ -243,7 +220,7 @@ MfDesfireError mf_desfire_poller_select_application( instance->input_buffer, (const uint8_t*)id, sizeof(MfDesfireApplicationId)); MfDesfireError error = - mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + mf_desfire_poller_send_chunks(instance, instance->input_buffer, instance->result_buffer); return error; } @@ -258,7 +235,8 @@ MfDesfireError mf_desfire_poller_read_file_ids(MfDesfirePoller* instance, Simple MfDesfireError error; do { - error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + error = mf_desfire_poller_send_chunks( + instance, instance->input_buffer, instance->result_buffer); if(error != MfDesfireErrorNone) break; @@ -293,7 +271,8 @@ MfDesfireError mf_desfire_poller_read_file_settings( MfDesfireError error; do { - error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + error = mf_desfire_poller_send_chunks( + instance, instance->input_buffer, instance->result_buffer); if(error != MfDesfireErrorNone) break; @@ -329,6 +308,108 @@ MfDesfireError mf_desfire_poller_read_file_settings_multi( return error; } +MfDesfireError mf_desfire_poller_create_application( + MfDesfirePoller* instance, + const MfDesfireApplicationId* id, + const MfDesfireKeySettings* key_settings, + uint16_t iso_df_id, + const uint8_t* iso_df_name, + uint8_t iso_df_name_len) { + furi_check(instance); + furi_check(key_settings); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_CREATE_APPLICATION); + bit_buffer_append_bytes( + instance->input_buffer, (const uint8_t*)id, sizeof(MfDesfireApplicationId)); + mf_desfire_key_settings_dump(key_settings, instance->input_buffer); + + if(iso_df_name && iso_df_name_len) { + uint8_t ks2_pos = bit_buffer_get_size_bytes(instance->input_buffer) - 1; + uint8_t ks2 = bit_buffer_get_byte(instance->input_buffer, ks2_pos); + ks2 |= (1 << 5); // Mark file id present + bit_buffer_set_byte(instance->input_buffer, ks2_pos, ks2); + + uint8_t iso_df_id_le[sizeof(iso_df_id)]; + bit_lib_num_to_bytes_le(iso_df_id, sizeof(iso_df_id_le), iso_df_id_le); + bit_buffer_append_bytes(instance->input_buffer, iso_df_id_le, sizeof(iso_df_id_le)); + + bit_buffer_append_bytes(instance->input_buffer, iso_df_name, iso_df_name_len); + } + + MfDesfireError error = + mf_desfire_poller_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + return error; +} + +MfDesfireError mf_desfire_poller_create_file( + MfDesfirePoller* instance, + MfDesfireFileId id, + const MfDesfireFileSettings* data, + uint16_t iso_ef_id) { + furi_check(instance); + furi_check(data); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte( + instance->input_buffer, + data->type == MfDesfireFileTypeStandard ? MF_DESFIRE_CMD_CREATE_STD_DATA_FILE : + data->type == MfDesfireFileTypeBackup ? MF_DESFIRE_CMD_CREATE_BACKUP_DATA_FILE : + data->type == MfDesfireFileTypeValue ? MF_DESFIRE_CMD_CREATE_VALUE_FILE : + data->type == MfDesfireFileTypeLinearRecord ? MF_DESFIRE_CMD_CREATE_LINEAR_RECORD_FILE : + data->type == MfDesfireFileTypeCyclicRecord ? MF_DESFIRE_CMD_CREATE_CYCLIC_RECORD_FILE : + 0x00); + bit_buffer_append_byte(instance->input_buffer, id); + if(iso_ef_id) { + uint8_t iso_ef_id_le[sizeof(iso_ef_id)]; + bit_lib_num_to_bytes_le(iso_ef_id, sizeof(iso_ef_id_le), iso_ef_id_le); + bit_buffer_append_bytes(instance->input_buffer, iso_ef_id_le, sizeof(iso_ef_id_le)); + } + bit_buffer_append_byte(instance->input_buffer, data->comm); + bit_buffer_append_bytes( + instance->input_buffer, + (const uint8_t*)data->access_rights, + sizeof(MfDesfireFileAccessRights) * data->access_rights_len); + + if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) { + uint8_t data_size_le[3 * sizeof(uint8_t)]; + bit_lib_num_to_bytes_le(data->data.size, sizeof(data_size_le), data_size_le); + bit_buffer_append_bytes(instance->input_buffer, data_size_le, sizeof(data_size_le)); + + } else if(data->type == MfDesfireFileTypeValue) { + uint8_t lo_limit_le[sizeof(data->value.lo_limit)]; + bit_lib_num_to_bytes_le(data->value.lo_limit, sizeof(lo_limit_le), lo_limit_le); + bit_buffer_append_bytes(instance->input_buffer, lo_limit_le, sizeof(lo_limit_le)); + + uint8_t hi_limit_le[sizeof(data->value.hi_limit)]; + bit_lib_num_to_bytes_le(data->value.hi_limit, sizeof(hi_limit_le), hi_limit_le); + bit_buffer_append_bytes(instance->input_buffer, hi_limit_le, sizeof(hi_limit_le)); + + uint8_t value_le[sizeof(data->value.limited_credit_value)]; + bit_lib_num_to_bytes_le(data->value.limited_credit_value, sizeof(value_le), value_le); + bit_buffer_append_bytes(instance->input_buffer, value_le, sizeof(value_le)); + + bit_buffer_append_byte(instance->input_buffer, data->value.limited_credit_enabled); + + } else if( + data->type == MfDesfireFileTypeLinearRecord || + data->type == MfDesfireFileTypeCyclicRecord) { + uint8_t record_size_le[3 * sizeof(uint8_t)]; + bit_lib_num_to_bytes_le(data->record.size, sizeof(record_size_le), record_size_le); + bit_buffer_append_bytes(instance->input_buffer, record_size_le, sizeof(record_size_le)); + + uint8_t record_max_le[3 * sizeof(uint8_t)]; + bit_lib_num_to_bytes_le(data->record.max, sizeof(record_max_le), record_max_le); + bit_buffer_append_bytes(instance->input_buffer, record_max_le, sizeof(record_max_le)); + } + + MfDesfireError error = + mf_desfire_poller_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + return error; +} + static MfDesfireError mf_desfire_poller_read_file( MfDesfirePoller* instance, MfDesfireFileId id, @@ -354,7 +435,8 @@ static MfDesfireError mf_desfire_poller_read_file( bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)¤t_offset, 3); bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&bytes_to_read, 3); - error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + error = mf_desfire_poller_send_chunks( + instance, instance->input_buffer, instance->result_buffer); if(error != MfDesfireErrorNone) break; size_t bytes_received = bit_buffer_get_size_bytes(instance->result_buffer); @@ -400,7 +482,8 @@ MfDesfireError mf_desfire_poller_read_file_value( MfDesfireError error; do { - error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + error = mf_desfire_poller_send_chunks( + instance, instance->input_buffer, instance->result_buffer); if(error != MfDesfireErrorNone) break; 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 19e38bebb..179fd44b5 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h @@ -30,6 +30,7 @@ typedef enum { struct MfDesfirePoller { Iso14443_4aPoller* iso14443_4a_poller; + NxpNativeCommandMode command_mode; MfDesfirePollerSessionState session_state; MfDesfirePollerState state; MfDesfireError error; diff --git a/lib/nfc/protocols/mf_plus/mf_plus_i.c b/lib/nfc/protocols/mf_plus/mf_plus_i.c index bd32956d6..b66cf5ea2 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_i.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_i.c @@ -15,8 +15,8 @@ const uint8_t mf_plus_ats_t1_tk_values[][MF_PLUS_T1_TK_VALUE_LEN] = { {0xC1, 0x05, 0x2F, 0x2F, 0x00, 0x35, 0xC7}, // Mifare Plus S {0xC1, 0x05, 0x2F, 0x2F, 0x01, 0xBC, 0xD6}, // Mifare Plus X - {0xC1, 0x05, 0x2F, 0x2F, 0x00, 0xF6, 0xD1}, // Mifare Plus SE - {0xC1, 0x05, 0x2F, 0x2F, 0x01, 0xF6, 0xD1}, // Mifare Plus SE + {0xC1, 0x05, 0x21, 0x30, 0x00, 0xF6, 0xD1}, // Mifare Plus SE + {0xC1, 0x05, 0x21, 0x30, 0x10, 0xF6, 0xD1}, // Mifare Plus SE }; MfPlusError mf_plus_get_type_from_version( @@ -27,7 +27,7 @@ MfPlusError mf_plus_get_type_from_version( MfPlusError error = MfPlusErrorProtocol; - if(mf_plus_data->version.hw_type == 0x02 || mf_plus_data->version.hw_type == 0x82) { + if((mf_plus_data->version.hw_type & 0x0F) == 0x02) { error = MfPlusErrorNone; // Mifare Plus EV1/EV2 @@ -85,16 +85,15 @@ MfPlusError MfPlusError error = MfPlusErrorProtocol; - if(simple_array_get_count(iso4_data->ats_data.t1_tk) != MF_PLUS_T1_TK_VALUE_LEN) { + const size_t historical_bytes_len = simple_array_get_count(iso4_data->ats_data.t1_tk); + if(historical_bytes_len != MF_PLUS_T1_TK_VALUE_LEN) { return MfPlusErrorProtocol; } + const uint8_t* historical_bytes = simple_array_cget_data(iso4_data->ats_data.t1_tk); switch(iso4_data->iso14443_3a_data->sak) { case 0x08: - if(memcmp( - simple_array_get_data(iso4_data->ats_data.t1_tk), - mf_plus_ats_t1_tk_values[0], - simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[0], historical_bytes_len) == 0) { // Mifare Plus S 2K SL1 mf_plus_data->type = MfPlusTypeS; mf_plus_data->size = MfPlusSize2K; @@ -102,11 +101,7 @@ MfPlusError FURI_LOG_D(TAG, "Mifare Plus S 2K SL1"); error = MfPlusErrorNone; - } else if( - memcmp( - simple_array_get_data(iso4_data->ats_data.t1_tk), - mf_plus_ats_t1_tk_values[1], - simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + } else if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[1], historical_bytes_len) == 0) { // Mifare Plus X 2K SL1 mf_plus_data->type = MfPlusTypeX; mf_plus_data->size = MfPlusSize2K; @@ -115,14 +110,8 @@ MfPlusError FURI_LOG_D(TAG, "Mifare Plus X 2K SL1"); error = MfPlusErrorNone; } else if( - memcmp( - simple_array_get_data(iso4_data->ats_data.t1_tk), - mf_plus_ats_t1_tk_values[2], - simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0 || - memcmp( - simple_array_get_data(iso4_data->ats_data.t1_tk), - mf_plus_ats_t1_tk_values[3], - simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + memcmp(historical_bytes, mf_plus_ats_t1_tk_values[2], historical_bytes_len) == 0 || + memcmp(historical_bytes, mf_plus_ats_t1_tk_values[3], historical_bytes_len) == 0) { // Mifare Plus SE 1K SL1 mf_plus_data->type = MfPlusTypeSE; mf_plus_data->size = MfPlusSize1K; @@ -154,10 +143,7 @@ MfPlusError break; case 0x18: - if(memcmp( - simple_array_get_data(iso4_data->ats_data.t1_tk), - mf_plus_ats_t1_tk_values[0], - simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[0], historical_bytes_len) == 0) { // Mifare Plus S 4K SL1 mf_plus_data->type = MfPlusTypeS; mf_plus_data->size = MfPlusSize4K; @@ -165,11 +151,7 @@ MfPlusError FURI_LOG_D(TAG, "Mifare Plus S 4K SL1"); error = MfPlusErrorNone; - } else if( - memcmp( - simple_array_get_data(iso4_data->ats_data.t1_tk), - mf_plus_ats_t1_tk_values[1], - simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + } else if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[1], historical_bytes_len) == 0) { // Mifare Plus X 4K SL1 mf_plus_data->type = MfPlusTypeX; mf_plus_data->size = MfPlusSize4K; @@ -183,10 +165,7 @@ MfPlusError break; case 0x20: - if(memcmp( - simple_array_get_data(iso4_data->ats_data.t1_tk), - mf_plus_ats_t1_tk_values[0], - simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[0], historical_bytes_len) == 0) { // Mifare Plus S 2/4K SL3 FURI_LOG_D(TAG, "Mifare Plus S SL3"); mf_plus_data->type = MfPlusTypeS; @@ -207,21 +186,20 @@ MfPlusError } else { FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type (S)"); } - } else if( - memcmp( - simple_array_get_data(iso4_data->ats_data.t1_tk), - mf_plus_ats_t1_tk_values[1], - simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + } else if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[1], historical_bytes_len) == 0) { + // Mifare Plus X 2/4K SL3 mf_plus_data->type = MfPlusTypeX; mf_plus_data->security_level = MfPlusSecurityLevel3; FURI_LOG_D(TAG, "Mifare Plus X SL3"); if((iso4_data->iso14443_3a_data->atqa[0] & 0x0F) == 0x04) { + // Mifare Plus X 2K SL3 mf_plus_data->size = MfPlusSize2K; FURI_LOG_D(TAG, "Mifare Plus X 2K SL3"); error = MfPlusErrorNone; } else if((iso4_data->iso14443_3a_data->atqa[0] & 0x0F) == 0x02) { + // Mifare Plus X 4K SL3 mf_plus_data->size = MfPlusSize4K; FURI_LOG_D(TAG, "Mifare Plus X 4K SL3"); @@ -229,6 +207,16 @@ MfPlusError } else { FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type (X)"); } + } else if( + memcmp(historical_bytes, mf_plus_ats_t1_tk_values[2], historical_bytes_len) == 0 || + memcmp(historical_bytes, mf_plus_ats_t1_tk_values[3], historical_bytes_len) == 0) { + // Mifare Plus SE 1K SL3 + mf_plus_data->type = MfPlusTypeSE; + mf_plus_data->size = MfPlusSize1K; + mf_plus_data->security_level = MfPlusSecurityLevel3; + + FURI_LOG_D(TAG, "Mifare Plus SE 1K SL3"); + error = MfPlusErrorNone; } else { FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type"); } @@ -238,22 +226,12 @@ MfPlusError } MfPlusError mf_plus_version_parse(MfPlusVersion* data, const BitBuffer* buf) { - bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusVersion); + const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusVersion); if(can_parse) { bit_buffer_write_bytes(buf, data, sizeof(MfPlusVersion)); - } else if( - bit_buffer_get_size_bytes(buf) == 8 && - bit_buffer_get_byte(buf, 0) == MF_PLUS_STATUS_ADDITIONAL_FRAME) { - // HACK(-nofl): There are supposed to be three parts to the GetVersion command, - // with the second and third parts fetched by sending the AdditionalFrame - // command. I don't know whether the entire MIFARE Plus line uses status as - // the first byte, so let's just assume we only have the first part of - // the response if it's size 8 and starts with the AF status. The second - // part of the response is the same size and status byte, but so far - // we're only reading one response. - can_parse = true; - bit_buffer_write_bytes_mid(buf, data, 1, bit_buffer_get_size_bytes(buf) - 1); + } else { + memset(data, 0, sizeof(MfPlusVersion)); } return can_parse ? MfPlusErrorNone : MfPlusErrorProtocol; diff --git a/lib/nfc/protocols/mf_plus/mf_plus_i.h b/lib/nfc/protocols/mf_plus/mf_plus_i.h index 302f5a178..cadc435b9 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_i.h +++ b/lib/nfc/protocols/mf_plus/mf_plus_i.h @@ -2,10 +2,9 @@ #include "mf_plus.h" -#define MF_PLUS_FFF_PICC_PREFIX "PICC" +#include -#define MF_PLUS_STATUS_OPERATION_OK (0x90) -#define MF_PLUS_STATUS_ADDITIONAL_FRAME (0xAF) +#define MF_PLUS_FFF_PICC_PREFIX "PICC" MfPlusError mf_plus_get_type_from_version( const Iso14443_4aData* iso14443_4a_data, diff --git a/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c b/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c index cab906f1d..b2e4231ff 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c @@ -19,28 +19,36 @@ MfPlusError mf_plus_process_error(Iso14443_4aError error) { } } -MfPlusError mf_plus_poller_send_chunk( +MfPlusError mf_plus_process_status_code(uint8_t status_code) { + switch(status_code) { + case NXP_NATIVE_COMMAND_STATUS_OPERATION_OK: + return MfPlusErrorNone; + default: + return MfPlusErrorProtocol; + } +} + +MfPlusError mf_plus_poller_send_chunks( MfPlusPoller* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer) { furi_assert(instance); - furi_assert(instance->iso14443_4a_poller); - furi_assert(instance->tx_buffer); - furi_assert(instance->rx_buffer); - furi_assert(tx_buffer); - furi_assert(rx_buffer); - Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block( - instance->iso14443_4a_poller, tx_buffer, instance->rx_buffer); - MfPlusError error = mf_plus_process_error(iso14443_4a_error); + NxpNativeCommandStatus status_code = NXP_NATIVE_COMMAND_STATUS_OPERATION_OK; + Iso14443_4aError iso14443_4a_error = nxp_native_command_iso14443_4a_poller( + instance->iso14443_4a_poller, + &status_code, + tx_buffer, + rx_buffer, + NxpNativeCommandModePlain, + instance->tx_buffer, + instance->rx_buffer); - if(error == MfPlusErrorNone) { - bit_buffer_copy(rx_buffer, instance->rx_buffer); + if(iso14443_4a_error != Iso14443_4aErrorNone) { + return mf_plus_process_error(iso14443_4a_error); } - bit_buffer_reset(instance->tx_buffer); - - return error; + return mf_plus_process_status_code(status_code); } MfPlusError mf_plus_poller_read_version(MfPlusPoller* instance, MfPlusVersion* data) { @@ -50,7 +58,7 @@ MfPlusError mf_plus_poller_read_version(MfPlusPoller* instance, MfPlusVersion* d bit_buffer_append_byte(instance->input_buffer, MF_PLUS_CMD_GET_VERSION); MfPlusError error = - mf_plus_poller_send_chunk(instance, instance->input_buffer, instance->result_buffer); + mf_plus_poller_send_chunks(instance, instance->input_buffer, instance->result_buffer); if(error == MfPlusErrorNone) { error = mf_plus_version_parse(data, instance->result_buffer); } diff --git a/lib/nfc/protocols/nfc_device_defs.c b/lib/nfc/protocols/nfc_device_defs.c index 2b9714613..0f1766029 100644 --- a/lib/nfc/protocols/nfc_device_defs.c +++ b/lib/nfc/protocols/nfc_device_defs.c @@ -22,9 +22,11 @@ #include #include #include -#include #include #include +#include +#include +#include /** * @brief List of registered NFC device implementations. @@ -45,6 +47,8 @@ const NfcDeviceBase* const nfc_devices[NfcProtocolNum] = { [NfcProtocolMfDesfire] = &nfc_device_mf_desfire, [NfcProtocolSlix] = &nfc_device_slix, [NfcProtocolSt25tb] = &nfc_device_st25tb, + [NfcProtocolNtag4xx] = &nfc_device_ntag4xx, + [NfcProtocolType4Tag] = &nfc_device_type_4_tag, [NfcProtocolEmv] = &nfc_device_emv, /* Add new protocols here */ }; diff --git a/lib/nfc/protocols/nfc_listener_defs.c b/lib/nfc/protocols/nfc_listener_defs.c index 5ad73f6fe..7efb274b6 100644 --- a/lib/nfc/protocols/nfc_listener_defs.c +++ b/lib/nfc/protocols/nfc_listener_defs.c @@ -3,10 +3,11 @@ #include #include #include +#include #include #include #include -#include +#include const NfcListenerBase* const nfc_listeners_api[NfcProtocolNum] = { [NfcProtocolIso14443_3a] = &nfc_listener_iso14443_3a, @@ -14,11 +15,14 @@ const NfcListenerBase* const nfc_listeners_api[NfcProtocolNum] = { [NfcProtocolIso14443_4a] = &nfc_listener_iso14443_4a, [NfcProtocolIso14443_4b] = NULL, [NfcProtocolIso15693_3] = &nfc_listener_iso15693_3, + [NfcProtocolFelica] = &nfc_listener_felica, [NfcProtocolMfUltralight] = &mf_ultralight_listener, [NfcProtocolMfClassic] = &mf_classic_listener, + [NfcProtocolMfPlus] = NULL, [NfcProtocolMfDesfire] = NULL, [NfcProtocolSlix] = &nfc_listener_slix, [NfcProtocolSt25tb] = NULL, - [NfcProtocolFelica] = &nfc_listener_felica, + [NfcProtocolNtag4xx] = NULL, + [NfcProtocolType4Tag] = &nfc_listener_type_4_tag, [NfcProtocolEmv] = NULL, }; diff --git a/lib/nfc/protocols/nfc_poller_defs.c b/lib/nfc/protocols/nfc_poller_defs.c index 21eef26ba..afcd9ab36 100644 --- a/lib/nfc/protocols/nfc_poller_defs.c +++ b/lib/nfc/protocols/nfc_poller_defs.c @@ -10,9 +10,11 @@ #include #include #include -#include #include #include +#include +#include +#include const NfcPollerBase* const nfc_pollers_api[NfcProtocolNum] = { [NfcProtocolIso14443_3a] = &nfc_poller_iso14443_3a, @@ -27,6 +29,8 @@ const NfcPollerBase* const nfc_pollers_api[NfcProtocolNum] = { [NfcProtocolMfDesfire] = &mf_desfire_poller, [NfcProtocolSlix] = &nfc_poller_slix, [NfcProtocolSt25tb] = &nfc_poller_st25tb, + [NfcProtocolNtag4xx] = &ntag4xx_poller, + [NfcProtocolType4Tag] = &type_4_tag_poller, [NfcProtocolEmv] = &emv_poller, /* Add new pollers here */ }; diff --git a/lib/nfc/protocols/nfc_protocol.c b/lib/nfc/protocols/nfc_protocol.c index 4106e689a..6767deb07 100644 --- a/lib/nfc/protocols/nfc_protocol.c +++ b/lib/nfc/protocols/nfc_protocol.c @@ -12,19 +12,19 @@ * ``` * **************************** Protocol tree structure *************************** * - * (Start) - * | - * +------------------------+-----------+---------+------------+ - * | | | | | - * ISO14443-3A ISO14443-3B Felica ISO15693-3 ST25TB - * | | | - * +---------------+-------------+ ISO14443-4B SLIX - * | | | - * ISO14443-4A Mf Ultralight Mf Classic - * | - * +-----+-----+ - * | | - * Mf Desfire EMV + * (Start) + * | + * +------------------------+-----------+---------+------------+ + * | | | | | + * ISO14443-3A ISO14443-3B Felica ISO15693-3 ST25TB + * | | | + * +---------------+-------------+ ISO14443-4B SLIX + * | | | + * ISO14443-4A Mf Ultralight Mf Classic + * | + * +-----+----+----------+----------+---------+ + * | | | | | + * Mf Desfire Mf Plus NTAG4xx Type 4 Tag EMV * ``` * * When implementing a new protocol, its place in the tree must be determined first. @@ -62,8 +62,10 @@ static const NfcProtocol nfc_protocol_iso14443_3b_children_protocol[] = { /** List of ISO14443-4A child protocols. */ static const NfcProtocol nfc_protocol_iso14443_4a_children_protocol[] = { - NfcProtocolMfDesfire, NfcProtocolMfPlus, + NfcProtocolMfDesfire, + NfcProtocolNtag4xx, + NfcProtocolType4Tag, NfcProtocolEmv, }; @@ -156,6 +158,18 @@ static const NfcProtocolTreeNode nfc_protocol_nodes[NfcProtocolNum] = { .children_num = 0, .children_protocol = NULL, }, + [NfcProtocolNtag4xx] = + { + .parent_protocol = NfcProtocolIso14443_4a, + .children_num = 0, + .children_protocol = NULL, + }, + [NfcProtocolType4Tag] = + { + .parent_protocol = NfcProtocolIso14443_4a, + .children_num = 0, + .children_protocol = NULL, + }, [NfcProtocolEmv] = { .parent_protocol = NfcProtocolIso14443_4a, diff --git a/lib/nfc/protocols/nfc_protocol.h b/lib/nfc/protocols/nfc_protocol.h index 12866528e..d33624d15 100644 --- a/lib/nfc/protocols/nfc_protocol.h +++ b/lib/nfc/protocols/nfc_protocol.h @@ -188,6 +188,8 @@ typedef enum { NfcProtocolMfDesfire, NfcProtocolSlix, NfcProtocolSt25tb, + NfcProtocolNtag4xx, + NfcProtocolType4Tag, NfcProtocolEmv, /* Add new protocols here */ diff --git a/lib/nfc/protocols/ntag4xx/ntag4xx.c b/lib/nfc/protocols/ntag4xx/ntag4xx.c new file mode 100644 index 000000000..7b0991903 --- /dev/null +++ b/lib/nfc/protocols/ntag4xx/ntag4xx.c @@ -0,0 +1,192 @@ +#include "ntag4xx_i.h" + +#include + +#define NTAG4XX_PROTOCOL_NAME "NTAG4xx" + +#define NTAG4XX_HW_MAJOR_TYPE_413_DNA (0x10) +#define NTAG4XX_HW_MAJOR_TYPE_424_DNA (0x30) + +#define NTAG4XX_HW_SUBTYPE_TAGTAMPER_FLAG (0x08) + +static const char* ntag4xx_type_strings[] = { + [Ntag4xxType413DNA] = "NTAG413 DNA", + [Ntag4xxType424DNA] = "NTAG424 DNA", + [Ntag4xxType424DNATT] = "NTAG424 DNA TagTamper", + [Ntag4xxType426QDNA] = "NTAG426Q DNA", + [Ntag4xxType426QDNATT] = "NTAG426Q DNA TagTamper", + [Ntag4xxTypeUnknown] = "UNK", +}; + +const NfcDeviceBase nfc_device_ntag4xx = { + .protocol_name = NTAG4XX_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)ntag4xx_alloc, + .free = (NfcDeviceFree)ntag4xx_free, + .reset = (NfcDeviceReset)ntag4xx_reset, + .copy = (NfcDeviceCopy)ntag4xx_copy, + .verify = (NfcDeviceVerify)ntag4xx_verify, + .load = (NfcDeviceLoad)ntag4xx_load, + .save = (NfcDeviceSave)ntag4xx_save, + .is_equal = (NfcDeviceEqual)ntag4xx_is_equal, + .get_name = (NfcDeviceGetName)ntag4xx_get_device_name, + .get_uid = (NfcDeviceGetUid)ntag4xx_get_uid, + .set_uid = (NfcDeviceSetUid)ntag4xx_set_uid, + .get_base_data = (NfcDeviceGetBaseData)ntag4xx_get_base_data, +}; + +Ntag4xxData* ntag4xx_alloc(void) { + Ntag4xxData* data = malloc(sizeof(Ntag4xxData)); + data->iso14443_4a_data = iso14443_4a_alloc(); + data->device_name = furi_string_alloc(); + return data; +} + +void ntag4xx_free(Ntag4xxData* data) { + furi_check(data); + + ntag4xx_reset(data); + iso14443_4a_free(data->iso14443_4a_data); + furi_string_free(data->device_name); + free(data); +} + +void ntag4xx_reset(Ntag4xxData* data) { + furi_check(data); + + iso14443_4a_reset(data->iso14443_4a_data); + + memset(&data->version, 0, sizeof(Ntag4xxVersion)); +} + +void ntag4xx_copy(Ntag4xxData* data, const Ntag4xxData* other) { + furi_check(data); + furi_check(other); + + ntag4xx_reset(data); + + iso14443_4a_copy(data->iso14443_4a_data, other->iso14443_4a_data); + + data->version = other->version; +} + +bool ntag4xx_verify(Ntag4xxData* data, const FuriString* device_type) { + UNUSED(data); + UNUSED(device_type); + + return false; +} + +bool ntag4xx_load(Ntag4xxData* data, FlipperFormat* ff, uint32_t version) { + furi_check(data); + furi_check(ff); + + FuriString* prefix = furi_string_alloc(); + + bool success = false; + + do { + if(!iso14443_4a_load(data->iso14443_4a_data, ff, version)) break; + + if(!ntag4xx_version_load(&data->version, ff)) break; + + success = true; + } while(false); + + furi_string_free(prefix); + return success; +} + +bool ntag4xx_save(const Ntag4xxData* data, FlipperFormat* ff) { + furi_check(data); + furi_check(ff); + + FuriString* prefix = furi_string_alloc(); + + bool success = false; + + do { + if(!iso14443_4a_save(data->iso14443_4a_data, ff)) break; + + if(!flipper_format_write_comment_cstr(ff, NTAG4XX_PROTOCOL_NAME " specific data")) break; + if(!ntag4xx_version_save(&data->version, ff)) break; + + success = true; + } while(false); + + furi_string_free(prefix); + return success; +} + +bool ntag4xx_is_equal(const Ntag4xxData* data, const Ntag4xxData* other) { + furi_check(data); + furi_check(other); + + return iso14443_4a_is_equal(data->iso14443_4a_data, other->iso14443_4a_data) && + memcmp(&data->version, &other->version, sizeof(Ntag4xxVersion)) == 0; +} + +Ntag4xxType ntag4xx_get_type_from_version(const Ntag4xxVersion* const version) { + Ntag4xxType type = Ntag4xxTypeUnknown; + + switch(version->hw_major) { + case NTAG4XX_HW_MAJOR_TYPE_413_DNA: + type = Ntag4xxType413DNA; + break; + case NTAG4XX_HW_MAJOR_TYPE_424_DNA: + if(version->hw_subtype & NTAG4XX_HW_SUBTYPE_TAGTAMPER_FLAG) { + type = Ntag4xxType424DNATT; + } else { + type = Ntag4xxType424DNA; + } + break; + // TODO: there is no info online or in other implementations (NXP TagInfo, NFC Tools, Proxmark3) + // about what the HWMajorVersion is supposed to be for NTAG426Q DNA, and they don't seem to be for sale + // case NTAG4XX_HW_MAJOR_TYPE_426Q_DNA: + // if(version->hw_subtype & NTAG4XX_HW_SUBTYPE_TAGTAMPER_FLAG) { + // type = Ntag4xxType426QDNATT; + // } else { + // type = Ntag4xxType426QDNA; + // } + // break; + default: + break; + } + + return type; +} + +const char* ntag4xx_get_device_name(const Ntag4xxData* data, NfcDeviceNameType name_type) { + furi_check(data); + + const Ntag4xxType type = ntag4xx_get_type_from_version(&data->version); + + if(type == Ntag4xxTypeUnknown) { + furi_string_printf(data->device_name, "Unknown %s", NTAG4XX_PROTOCOL_NAME); + } else { + furi_string_printf(data->device_name, "%s", ntag4xx_type_strings[type]); + if(name_type == NfcDeviceNameTypeShort) { + furi_string_replace(data->device_name, "TagTamper", "TT"); + } + } + + return furi_string_get_cstr(data->device_name); +} + +const uint8_t* ntag4xx_get_uid(const Ntag4xxData* data, size_t* uid_len) { + furi_check(data); + furi_check(uid_len); + + return iso14443_4a_get_uid(data->iso14443_4a_data, uid_len); +} + +bool ntag4xx_set_uid(Ntag4xxData* data, const uint8_t* uid, size_t uid_len) { + furi_check(data); + + return iso14443_4a_set_uid(data->iso14443_4a_data, uid, uid_len); +} + +Iso14443_4aData* ntag4xx_get_base_data(const Ntag4xxData* data) { + furi_check(data); + + return data->iso14443_4a_data; +} diff --git a/lib/nfc/protocols/ntag4xx/ntag4xx.h b/lib/nfc/protocols/ntag4xx/ntag4xx.h new file mode 100644 index 000000000..56e5fbd65 --- /dev/null +++ b/lib/nfc/protocols/ntag4xx/ntag4xx.h @@ -0,0 +1,114 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define NTAG4XX_UID_SIZE (7) +#define NTAG4XX_BATCH_SIZE (4) +#define NTAG4XX_BATCH_EXTRA_BITS 4 +#define NTAG4XX_FAB_KEY_SIZE_BITS_4 4 +#define NTAG4XX_FAB_KEY_SIZE_BITS_1 1 +#define NTAG4XX_PROD_WEEK_SIZE_BITS 7 + +#define NTAG4XX_CMD_GET_VERSION (0x60) + +typedef enum { + Ntag4xxErrorNone, + Ntag4xxErrorNotPresent, + Ntag4xxErrorProtocol, + Ntag4xxErrorTimeout, +} Ntag4xxError; + +typedef enum { + Ntag4xxType413DNA, + Ntag4xxType424DNA, + Ntag4xxType424DNATT, + Ntag4xxType426QDNA, + Ntag4xxType426QDNATT, + + Ntag4xxTypeUnknown, + Ntag4xxTypeNum, +} Ntag4xxType; + +#pragma pack(push, 1) +typedef struct { + uint8_t hw_vendor; + uint8_t hw_type; + uint8_t hw_subtype; + uint8_t hw_major; + uint8_t hw_minor; + uint8_t hw_storage; + uint8_t hw_proto; + + uint8_t sw_vendor; + uint8_t sw_type; + uint8_t sw_subtype; + uint8_t sw_major; + uint8_t sw_minor; + uint8_t sw_storage; + uint8_t sw_proto; + + uint8_t uid[NTAG4XX_UID_SIZE]; + // [36b batch][5b fab key][7b prod week] + // 5b fab key is split 4b in last byte of batch and 1b in prod week + // Due to endianness, they appear swapped in the struct definition + uint8_t batch[NTAG4XX_BATCH_SIZE]; + struct { + uint8_t fab_key_4b : NTAG4XX_FAB_KEY_SIZE_BITS_4; + uint8_t batch_extra : NTAG4XX_BATCH_EXTRA_BITS; + }; + struct { + uint8_t prod_week : NTAG4XX_PROD_WEEK_SIZE_BITS; + uint8_t fab_key_1b : NTAG4XX_FAB_KEY_SIZE_BITS_1; + }; + uint8_t prod_year; + struct { + uint8_t fab_key_id; + } optional; +} Ntag4xxVersion; +#pragma pack(pop) + +typedef struct { + Iso14443_4aData* iso14443_4a_data; + Ntag4xxVersion version; + FuriString* device_name; +} Ntag4xxData; + +extern const NfcDeviceBase nfc_device_ntag4xx; + +// Virtual methods + +Ntag4xxData* ntag4xx_alloc(void); + +void ntag4xx_free(Ntag4xxData* data); + +void ntag4xx_reset(Ntag4xxData* data); + +void ntag4xx_copy(Ntag4xxData* data, const Ntag4xxData* other); + +bool ntag4xx_verify(Ntag4xxData* data, const FuriString* device_type); + +bool ntag4xx_load(Ntag4xxData* data, FlipperFormat* ff, uint32_t version); + +bool ntag4xx_save(const Ntag4xxData* data, FlipperFormat* ff); + +bool ntag4xx_is_equal(const Ntag4xxData* data, const Ntag4xxData* other); + +const char* ntag4xx_get_device_name(const Ntag4xxData* data, NfcDeviceNameType name_type); + +const uint8_t* ntag4xx_get_uid(const Ntag4xxData* data, size_t* uid_len); + +bool ntag4xx_set_uid(Ntag4xxData* data, const uint8_t* uid, size_t uid_len); + +Iso14443_4aData* ntag4xx_get_base_data(const Ntag4xxData* data); + +// Helpers + +Ntag4xxType ntag4xx_get_type_from_version(const Ntag4xxVersion* const version); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/ntag4xx/ntag4xx_i.c b/lib/nfc/protocols/ntag4xx/ntag4xx_i.c new file mode 100644 index 000000000..b4ee3a9eb --- /dev/null +++ b/lib/nfc/protocols/ntag4xx/ntag4xx_i.c @@ -0,0 +1,54 @@ +#include "ntag4xx_i.h" + +#define TAG "Ntag4xx" + +#define NTAG4XX_FFF_VERSION_KEY \ + NTAG4XX_FFF_PICC_PREFIX " " \ + "Version" + +Ntag4xxError ntag4xx_process_error(Iso14443_4aError error) { + switch(error) { + case Iso14443_4aErrorNone: + return Ntag4xxErrorNone; + case Iso14443_4aErrorNotPresent: + return Ntag4xxErrorNotPresent; + case Iso14443_4aErrorTimeout: + return Ntag4xxErrorTimeout; + default: + return Ntag4xxErrorProtocol; + } +} + +Ntag4xxError ntag4xx_process_status_code(uint8_t status_code) { + switch(status_code) { + case NXP_NATIVE_COMMAND_STATUS_OPERATION_OK: + return Ntag4xxErrorNone; + default: + return Ntag4xxErrorProtocol; + } +} + +bool ntag4xx_version_parse(Ntag4xxVersion* data, const BitBuffer* buf) { + const size_t buf_size = bit_buffer_get_size_bytes(buf); + const bool can_parse = buf_size == sizeof(Ntag4xxVersion) || + buf_size == sizeof(Ntag4xxVersion) - sizeof(data->optional); + + if(can_parse) { + bit_buffer_write_bytes(buf, data, sizeof(Ntag4xxVersion)); + if(buf_size < sizeof(Ntag4xxVersion)) { + memset(&data->optional, 0, sizeof(data->optional)); + } + } + + return can_parse && (data->hw_type & 0x0F) == 0x04; +} + +bool ntag4xx_version_load(Ntag4xxVersion* data, FlipperFormat* ff) { + return flipper_format_read_hex( + ff, NTAG4XX_FFF_VERSION_KEY, (uint8_t*)data, sizeof(Ntag4xxVersion)); +} + +bool ntag4xx_version_save(const Ntag4xxVersion* data, FlipperFormat* ff) { + return flipper_format_write_hex( + ff, NTAG4XX_FFF_VERSION_KEY, (const uint8_t*)data, sizeof(Ntag4xxVersion)); +} diff --git a/lib/nfc/protocols/ntag4xx/ntag4xx_i.h b/lib/nfc/protocols/ntag4xx/ntag4xx_i.h new file mode 100644 index 000000000..5a71cf05a --- /dev/null +++ b/lib/nfc/protocols/ntag4xx/ntag4xx_i.h @@ -0,0 +1,25 @@ +#pragma once + +#include "ntag4xx.h" + +#include + +#define NTAG4XX_FFF_PICC_PREFIX "PICC" + +// Internal helpers + +Ntag4xxError ntag4xx_process_error(Iso14443_4aError error); + +Ntag4xxError ntag4xx_process_status_code(uint8_t status_code); + +// Parse internal Ntag4xx structures + +bool ntag4xx_version_parse(Ntag4xxVersion* data, const BitBuffer* buf); + +// Load internal Ntag4xx structures + +bool ntag4xx_version_load(Ntag4xxVersion* data, FlipperFormat* ff); + +// Save internal Ntag4xx structures + +bool ntag4xx_version_save(const Ntag4xxVersion* data, FlipperFormat* ff); diff --git a/lib/nfc/protocols/ntag4xx/ntag4xx_poller.c b/lib/nfc/protocols/ntag4xx/ntag4xx_poller.c new file mode 100644 index 000000000..38e6f19d4 --- /dev/null +++ b/lib/nfc/protocols/ntag4xx/ntag4xx_poller.c @@ -0,0 +1,165 @@ +#include "ntag4xx_poller_i.h" + +#include + +#include + +#define TAG "Ntag4xxPoller" + +#define NTAG4XX_BUF_SIZE (64U) +#define NTAG4XX_RESULT_BUF_SIZE (512U) + +typedef NfcCommand (*Ntag4xxPollerReadHandler)(Ntag4xxPoller* instance); + +static const Ntag4xxData* ntag4xx_poller_get_data(Ntag4xxPoller* instance) { + furi_assert(instance); + + return instance->data; +} + +static Ntag4xxPoller* ntag4xx_poller_alloc(Iso14443_4aPoller* iso14443_4a_poller) { + Ntag4xxPoller* instance = malloc(sizeof(Ntag4xxPoller)); + instance->iso14443_4a_poller = iso14443_4a_poller; + instance->data = ntag4xx_alloc(); + instance->tx_buffer = bit_buffer_alloc(NTAG4XX_BUF_SIZE); + instance->rx_buffer = bit_buffer_alloc(NTAG4XX_BUF_SIZE); + instance->input_buffer = bit_buffer_alloc(NTAG4XX_BUF_SIZE); + instance->result_buffer = bit_buffer_alloc(NTAG4XX_RESULT_BUF_SIZE); + + instance->ntag4xx_event.data = &instance->ntag4xx_event_data; + + instance->general_event.protocol = NfcProtocolNtag4xx; + instance->general_event.event_data = &instance->ntag4xx_event; + instance->general_event.instance = instance; + + return instance; +} + +static void ntag4xx_poller_free(Ntag4xxPoller* instance) { + furi_assert(instance); + + ntag4xx_free(instance->data); + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + bit_buffer_free(instance->input_buffer); + bit_buffer_free(instance->result_buffer); + free(instance); +} + +static NfcCommand ntag4xx_poller_handler_idle(Ntag4xxPoller* instance) { + bit_buffer_reset(instance->input_buffer); + bit_buffer_reset(instance->result_buffer); + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + iso14443_4a_copy( + instance->data->iso14443_4a_data, + iso14443_4a_poller_get_data(instance->iso14443_4a_poller)); + + instance->state = Ntag4xxPollerStateReadVersion; + return NfcCommandContinue; +} + +static NfcCommand ntag4xx_poller_handler_read_version(Ntag4xxPoller* instance) { + instance->error = ntag4xx_poller_read_version(instance, &instance->data->version); + if(instance->error == Ntag4xxErrorNone) { + FURI_LOG_D(TAG, "Read version success"); + instance->state = Ntag4xxPollerStateReadSuccess; + } else { + FURI_LOG_E(TAG, "Failed to read version"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->state = Ntag4xxPollerStateReadFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand ntag4xx_poller_handler_read_failed(Ntag4xxPoller* instance) { + FURI_LOG_D(TAG, "Read Failed"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->ntag4xx_event.type = Ntag4xxPollerEventTypeReadFailed; + instance->ntag4xx_event.data->error = instance->error; + NfcCommand command = instance->callback(instance->general_event, instance->context); + instance->state = Ntag4xxPollerStateIdle; + return command; +} + +static NfcCommand ntag4xx_poller_handler_read_success(Ntag4xxPoller* instance) { + FURI_LOG_D(TAG, "Read success"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->ntag4xx_event.type = Ntag4xxPollerEventTypeReadSuccess; + NfcCommand command = instance->callback(instance->general_event, instance->context); + return command; +} + +static const Ntag4xxPollerReadHandler ntag4xx_poller_read_handler[Ntag4xxPollerStateNum] = { + [Ntag4xxPollerStateIdle] = ntag4xx_poller_handler_idle, + [Ntag4xxPollerStateReadVersion] = ntag4xx_poller_handler_read_version, + [Ntag4xxPollerStateReadFailed] = ntag4xx_poller_handler_read_failed, + [Ntag4xxPollerStateReadSuccess] = ntag4xx_poller_handler_read_success, +}; + +static void ntag4xx_poller_set_callback( + Ntag4xxPoller* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static NfcCommand ntag4xx_poller_run(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_4a); + + Ntag4xxPoller* instance = context; + furi_assert(instance); + furi_assert(instance->callback); + + const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; + furi_assert(iso14443_4a_event); + + NfcCommand command = NfcCommandContinue; + + if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { + command = ntag4xx_poller_read_handler[instance->state](instance); + } else if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeError) { + instance->ntag4xx_event.type = Ntag4xxPollerEventTypeReadFailed; + command = instance->callback(instance->general_event, instance->context); + } + + return command; +} + +static bool ntag4xx_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_4a); + + Ntag4xxPoller* instance = context; + furi_assert(instance); + + const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; + furi_assert(iso14443_4a_event); + + bool protocol_detected = false; + + if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { + do { + Ntag4xxError error = ntag4xx_poller_read_version(instance, &instance->data->version); + if(error != Ntag4xxErrorNone) break; + + protocol_detected = true; + } while(false); + } + + return protocol_detected; +} + +const NfcPollerBase ntag4xx_poller = { + .alloc = (NfcPollerAlloc)ntag4xx_poller_alloc, + .free = (NfcPollerFree)ntag4xx_poller_free, + .set_callback = (NfcPollerSetCallback)ntag4xx_poller_set_callback, + .run = (NfcPollerRun)ntag4xx_poller_run, + .detect = (NfcPollerDetect)ntag4xx_poller_detect, + .get_data = (NfcPollerGetData)ntag4xx_poller_get_data, +}; diff --git a/lib/nfc/protocols/ntag4xx/ntag4xx_poller.h b/lib/nfc/protocols/ntag4xx/ntag4xx_poller.h new file mode 100644 index 000000000..ce7adc785 --- /dev/null +++ b/lib/nfc/protocols/ntag4xx/ntag4xx_poller.h @@ -0,0 +1,43 @@ +#pragma once + +#include "ntag4xx.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Ntag4xxPoller opaque type definition. + */ +typedef struct Ntag4xxPoller Ntag4xxPoller; + +/** + * @brief Enumeration of possible Ntag4xx poller event types. + */ +typedef enum { + Ntag4xxPollerEventTypeReadSuccess, /**< Card was read successfully. */ + Ntag4xxPollerEventTypeReadFailed, /**< Poller failed to read card. */ +} Ntag4xxPollerEventType; + +/** + * @brief Ntag4xx poller event data. + */ +typedef union { + Ntag4xxError error; /**< Error code indicating card reading fail reason. */ +} Ntag4xxPollerEventData; + +/** + * @brief Ntag4xx poller event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ +typedef struct { + Ntag4xxPollerEventType type; /**< Type of emmitted event. */ + Ntag4xxPollerEventData* data; /**< Pointer to event specific data. */ +} Ntag4xxPollerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/ntag4xx/ntag4xx_poller_defs.h b/lib/nfc/protocols/ntag4xx/ntag4xx_poller_defs.h new file mode 100644 index 000000000..ac0cdce9b --- /dev/null +++ b/lib/nfc/protocols/ntag4xx/ntag4xx_poller_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcPollerBase ntag4xx_poller; diff --git a/lib/nfc/protocols/ntag4xx/ntag4xx_poller_i.c b/lib/nfc/protocols/ntag4xx/ntag4xx_poller_i.c new file mode 100644 index 000000000..5b8b7191d --- /dev/null +++ b/lib/nfc/protocols/ntag4xx/ntag4xx_poller_i.c @@ -0,0 +1,52 @@ +#include "ntag4xx_poller_i.h" + +#include + +#include "ntag4xx_i.h" + +#define TAG "Ntag4xxPoller" + +Ntag4xxError ntag4xx_poller_send_chunks( + Ntag4xxPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer) { + furi_check(instance); + + NxpNativeCommandStatus status_code = NXP_NATIVE_COMMAND_STATUS_OPERATION_OK; + Iso14443_4aError iso14443_4a_error = nxp_native_command_iso14443_4a_poller( + instance->iso14443_4a_poller, + &status_code, + tx_buffer, + rx_buffer, + NxpNativeCommandModeIsoWrapped, + instance->tx_buffer, + instance->rx_buffer); + + if(iso14443_4a_error != Iso14443_4aErrorNone) { + return ntag4xx_process_error(iso14443_4a_error); + } + + return ntag4xx_process_status_code(status_code); +} + +Ntag4xxError ntag4xx_poller_read_version(Ntag4xxPoller* instance, Ntag4xxVersion* data) { + furi_check(instance); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, NTAG4XX_CMD_GET_VERSION); + + Ntag4xxError error; + + do { + error = + ntag4xx_poller_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + if(error != Ntag4xxErrorNone) break; + + if(!ntag4xx_version_parse(data, instance->result_buffer)) { + error = Ntag4xxErrorProtocol; + } + } while(false); + + return error; +} diff --git a/lib/nfc/protocols/ntag4xx/ntag4xx_poller_i.h b/lib/nfc/protocols/ntag4xx/ntag4xx_poller_i.h new file mode 100644 index 000000000..b77e694ef --- /dev/null +++ b/lib/nfc/protocols/ntag4xx/ntag4xx_poller_i.h @@ -0,0 +1,40 @@ +#pragma once + +#include "ntag4xx_poller.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + Ntag4xxPollerStateIdle, + Ntag4xxPollerStateReadVersion, + Ntag4xxPollerStateReadFailed, + Ntag4xxPollerStateReadSuccess, + + Ntag4xxPollerStateNum, +} Ntag4xxPollerState; + +struct Ntag4xxPoller { + Iso14443_4aPoller* iso14443_4a_poller; + Ntag4xxPollerState state; + Ntag4xxError error; + Ntag4xxData* data; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + BitBuffer* input_buffer; + BitBuffer* result_buffer; + Ntag4xxPollerEventData ntag4xx_event_data; + Ntag4xxPollerEvent ntag4xx_event; + NfcGenericEvent general_event; + NfcGenericCallback callback; + void* context; +}; + +Ntag4xxError ntag4xx_poller_read_version(Ntag4xxPoller* instance, Ntag4xxVersion* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag.c b/lib/nfc/protocols/type_4_tag/type_4_tag.c new file mode 100644 index 000000000..03527b844 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag.c @@ -0,0 +1,170 @@ +#include "type_4_tag_i.h" + +#define TYPE_4_TAG_PROTOCOL_NAME "Type 4 Tag" + +const NfcDeviceBase nfc_device_type_4_tag = { + .protocol_name = TYPE_4_TAG_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)type_4_tag_alloc, + .free = (NfcDeviceFree)type_4_tag_free, + .reset = (NfcDeviceReset)type_4_tag_reset, + .copy = (NfcDeviceCopy)type_4_tag_copy, + .verify = (NfcDeviceVerify)type_4_tag_verify, + .load = (NfcDeviceLoad)type_4_tag_load, + .save = (NfcDeviceSave)type_4_tag_save, + .is_equal = (NfcDeviceEqual)type_4_tag_is_equal, + .get_name = (NfcDeviceGetName)type_4_tag_get_device_name, + .get_uid = (NfcDeviceGetUid)type_4_tag_get_uid, + .set_uid = (NfcDeviceSetUid)type_4_tag_set_uid, + .get_base_data = (NfcDeviceGetBaseData)type_4_tag_get_base_data, +}; + +Type4TagData* type_4_tag_alloc(void) { + Type4TagData* data = malloc(sizeof(Type4TagData)); + data->iso14443_4a_data = iso14443_4a_alloc(); + data->device_name = furi_string_alloc(); + data->platform_name = furi_string_alloc(); + data->ndef_data = simple_array_alloc(&simple_array_config_uint8_t); + return data; +} + +void type_4_tag_free(Type4TagData* data) { + furi_check(data); + + type_4_tag_reset(data); + simple_array_free(data->ndef_data); + furi_string_free(data->platform_name); + furi_string_free(data->device_name); + iso14443_4a_free(data->iso14443_4a_data); + free(data); +} + +void type_4_tag_reset(Type4TagData* data) { + furi_check(data); + + iso14443_4a_reset(data->iso14443_4a_data); + + data->is_tag_specific = false; + furi_string_reset(data->device_name); + furi_string_reset(data->platform_name); + data->t4t_version.value = 0; + data->chunk_max_read = 0; + data->chunk_max_write = 0; + data->ndef_file_id = 0; + data->ndef_max_len = 0; + data->ndef_read_lock = 0; + data->ndef_write_lock = 0; + + simple_array_reset(data->ndef_data); +} + +void type_4_tag_copy(Type4TagData* data, const Type4TagData* other) { + furi_check(data); + furi_check(other); + + type_4_tag_reset(data); + + iso14443_4a_copy(data->iso14443_4a_data, other->iso14443_4a_data); + + data->is_tag_specific = other->is_tag_specific; + furi_string_set(data->device_name, other->device_name); + furi_string_set(data->platform_name, other->platform_name); + data->t4t_version.value = other->t4t_version.value; + data->chunk_max_read = other->chunk_max_read; + data->chunk_max_write = other->chunk_max_write; + data->ndef_file_id = other->ndef_file_id; + data->ndef_max_len = other->ndef_max_len; + data->ndef_read_lock = other->ndef_read_lock; + data->ndef_write_lock = other->ndef_write_lock; + + simple_array_copy(data->ndef_data, other->ndef_data); +} + +bool type_4_tag_verify(Type4TagData* data, const FuriString* device_type) { + UNUSED(data); + UNUSED(device_type); + + return false; +} + +bool type_4_tag_load(Type4TagData* data, FlipperFormat* ff, uint32_t version) { + furi_check(data); + furi_check(ff); + + FuriString* prefix = furi_string_alloc(); + + bool success = false; + + do { + if(!iso14443_4a_load(data->iso14443_4a_data, ff, version)) break; + + if(!type_4_tag_ndef_data_load(data, ff)) break; + + success = true; + } while(false); + + furi_string_free(prefix); + return success; +} + +bool type_4_tag_save(const Type4TagData* data, FlipperFormat* ff) { + furi_check(data); + furi_check(ff); + + FuriString* prefix = furi_string_alloc(); + + bool success = false; + + do { + if(!iso14443_4a_save(data->iso14443_4a_data, ff)) break; + + if(!flipper_format_write_comment_cstr(ff, TYPE_4_TAG_PROTOCOL_NAME " specific data")) + break; + if(!type_4_tag_ndef_data_save(data, ff)) break; + + success = true; + } while(false); + + furi_string_free(prefix); + return success; +} + +bool type_4_tag_is_equal(const Type4TagData* data, const Type4TagData* other) { + furi_check(data); + furi_check(other); + + return iso14443_4a_is_equal(data->iso14443_4a_data, other->iso14443_4a_data) && + data->is_tag_specific == other->is_tag_specific && + data->t4t_version.value == other->t4t_version.value && + data->chunk_max_read == other->chunk_max_read && + data->chunk_max_write == other->chunk_max_write && + data->ndef_file_id == other->ndef_file_id && + data->ndef_max_len == other->ndef_max_len && + data->ndef_read_lock == other->ndef_read_lock && + data->ndef_write_lock == other->ndef_write_lock && + simple_array_is_equal(data->ndef_data, other->ndef_data); +} + +const char* type_4_tag_get_device_name(const Type4TagData* data, NfcDeviceNameType name_type) { + UNUSED(data); + UNUSED(name_type); + return TYPE_4_TAG_PROTOCOL_NAME; +} + +const uint8_t* type_4_tag_get_uid(const Type4TagData* data, size_t* uid_len) { + furi_check(data); + furi_check(uid_len); + + return iso14443_4a_get_uid(data->iso14443_4a_data, uid_len); +} + +bool type_4_tag_set_uid(Type4TagData* data, const uint8_t* uid, size_t uid_len) { + furi_check(data); + + return iso14443_4a_set_uid(data->iso14443_4a_data, uid, uid_len); +} + +Iso14443_4aData* type_4_tag_get_base_data(const Type4TagData* data) { + furi_check(data); + + return data->iso14443_4a_data; +} diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag.h b/lib/nfc/protocols/type_4_tag/type_4_tag.h new file mode 100644 index 000000000..29c41a5be --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag.h @@ -0,0 +1,84 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define TYPE_4_TAG_MF_DESFIRE_NDEF_SIZE (2048U - sizeof(uint16_t)) + +typedef enum { + Type4TagErrorNone, + Type4TagErrorNotPresent, + Type4TagErrorProtocol, + Type4TagErrorTimeout, + Type4TagErrorWrongFormat, + Type4TagErrorNotSupported, + Type4TagErrorApduFailed, + Type4TagErrorCardUnformatted, + Type4TagErrorCardLocked, + Type4TagErrorCustomCommand, +} Type4TagError; + +typedef enum { + Type4TagPlatformUnknown, + Type4TagPlatformNtag4xx, + Type4TagPlatformMfDesfire, +} Type4TagPlatform; + +typedef struct { + Iso14443_4aData* iso14443_4a_data; + FuriString* device_name; + // Tag specific data + bool is_tag_specific; + Type4TagPlatform platform; + FuriString* platform_name; + union { + struct { + uint8_t minor : 4; + uint8_t major : 4; + }; + uint8_t value; + } t4t_version; + uint16_t chunk_max_read; + uint16_t chunk_max_write; + uint16_t ndef_file_id; + uint16_t ndef_max_len; + uint8_t ndef_read_lock; + uint8_t ndef_write_lock; + // Data contained, not tag specific + SimpleArray* ndef_data; +} Type4TagData; + +extern const NfcDeviceBase nfc_device_type_4_tag; + +// Virtual methods + +Type4TagData* type_4_tag_alloc(void); + +void type_4_tag_free(Type4TagData* data); + +void type_4_tag_reset(Type4TagData* data); + +void type_4_tag_copy(Type4TagData* data, const Type4TagData* other); + +bool type_4_tag_verify(Type4TagData* data, const FuriString* device_type); + +bool type_4_tag_load(Type4TagData* data, FlipperFormat* ff, uint32_t version); + +bool type_4_tag_save(const Type4TagData* data, FlipperFormat* ff); + +bool type_4_tag_is_equal(const Type4TagData* data, const Type4TagData* other); + +const char* type_4_tag_get_device_name(const Type4TagData* data, NfcDeviceNameType name_type); + +const uint8_t* type_4_tag_get_uid(const Type4TagData* data, size_t* uid_len); + +bool type_4_tag_set_uid(Type4TagData* data, const uint8_t* uid, size_t uid_len); + +Iso14443_4aData* type_4_tag_get_base_data(const Type4TagData* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_i.c b/lib/nfc/protocols/type_4_tag/type_4_tag_i.c new file mode 100644 index 000000000..69e9aadf9 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_i.c @@ -0,0 +1,163 @@ +#include "type_4_tag_i.h" + +#include + +#define TAG "Type4Tag" + +#define TYPE_4_TAG_FFF_NDEF_DATA_SIZE_KEY "NDEF Data Size" +#define TYPE_4_TAG_FFF_NDEF_DATA_KEY "NDEF Data" + +#define TYPE_4_TAG_FFF_NDEF_DATA_PER_LINE (16U) + +const uint8_t type_4_tag_iso_mf_name[TYPE_4_TAG_ISO_NAME_LEN] = {TYPE_4_TAG_ISO_MF_NAME}; +const uint8_t type_4_tag_iso_df_name[TYPE_4_TAG_ISO_NAME_LEN] = {TYPE_4_TAG_ISO_DF_NAME}; + +Type4TagError type_4_tag_process_error(Iso14443_4aError error) { + switch(error) { + case Iso14443_4aErrorNone: + return Type4TagErrorNone; + case Iso14443_4aErrorNotPresent: + return Type4TagErrorNotPresent; + case Iso14443_4aErrorTimeout: + return Type4TagErrorTimeout; + default: + return Type4TagErrorProtocol; + } +} + +void type_4_tag_cc_dump(const Type4TagData* data, uint8_t* buf, size_t len) { + furi_check(len >= TYPE_4_TAG_T4T_CC_MIN_SIZE); + Type4TagCc* cc = (Type4TagCc*)buf; + + bit_lib_num_to_bytes_be(TYPE_4_TAG_T4T_CC_MIN_SIZE, sizeof(cc->len), (void*)&cc->len); + cc->t4t_vno = TYPE_4_TAG_T4T_CC_VNO; + bit_lib_num_to_bytes_be( + data->is_tag_specific ? MIN(data->chunk_max_read, TYPE_4_TAG_CHUNK_LEN) : + TYPE_4_TAG_CHUNK_LEN, + sizeof(cc->mle), + (void*)&cc->mle); + bit_lib_num_to_bytes_be( + data->is_tag_specific ? MIN(data->chunk_max_write, TYPE_4_TAG_CHUNK_LEN) : + TYPE_4_TAG_CHUNK_LEN, + sizeof(cc->mlc), + (void*)&cc->mlc); + + cc->tlv[0].type = Type4TagCcTlvTypeNdefFileCtrl; + cc->tlv[0].len = sizeof(cc->tlv[0].value.ndef_file_ctrl); + + bit_lib_num_to_bytes_be( + data->is_tag_specific ? data->ndef_file_id : TYPE_4_TAG_T4T_NDEF_EF_ID, + sizeof(cc->tlv[0].value.ndef_file_ctrl.file_id), + (void*)&cc->tlv[0].value.ndef_file_ctrl.file_id); + bit_lib_num_to_bytes_be( + sizeof(uint16_t) + + (data->is_tag_specific ? data->ndef_max_len : TYPE_4_TAG_DEFAULT_NDEF_SIZE), + sizeof(cc->tlv[0].value.ndef_file_ctrl.max_len), + (void*)&cc->tlv[0].value.ndef_file_ctrl.max_len); + cc->tlv[0].value.ndef_file_ctrl.read_perm = + data->is_tag_specific ? data->ndef_read_lock : TYPE_4_TAG_T4T_CC_RW_LOCK_NONE; + cc->tlv[0].value.ndef_file_ctrl.write_perm = + data->is_tag_specific ? data->ndef_write_lock : TYPE_4_TAG_T4T_CC_RW_LOCK_NONE; +} + +Type4TagError type_4_tag_cc_parse(Type4TagData* data, const uint8_t* buf, size_t len) { + if(len < TYPE_4_TAG_T4T_CC_MIN_SIZE) { + FURI_LOG_E(TAG, "Unsupported T4T version"); + return Type4TagErrorWrongFormat; + } + + const Type4TagCc* cc = (const Type4TagCc*)buf; + if(cc->t4t_vno != TYPE_4_TAG_T4T_CC_VNO) { + FURI_LOG_E(TAG, "Unsupported T4T version"); + return Type4TagErrorNotSupported; + } + + const Type4TagCcTlv* tlv = cc->tlv; + const Type4TagCcTlvNdefFileCtrl* ndef_file_ctrl = NULL; + const void* end = MIN((void*)cc + cc->len, (void*)cc + len); + while((void*)tlv < end) { + if(tlv->type == Type4TagCcTlvTypeNdefFileCtrl) { + ndef_file_ctrl = &tlv->value.ndef_file_ctrl; + break; + } + + if(tlv->len < 0xFF) { + tlv = (void*)&tlv->value + tlv->len; + } else { + uint16_t len = bit_lib_bytes_to_num_be((void*)&tlv->len + 1, sizeof(uint16_t)); + tlv = (void*)&tlv->value + sizeof(len) + len; + } + } + if(!ndef_file_ctrl) { + FURI_LOG_E(TAG, "No NDEF file ctrl TLV"); + return Type4TagErrorWrongFormat; + } + + data->t4t_version.value = cc->t4t_vno; + data->chunk_max_read = bit_lib_bytes_to_num_be((void*)&cc->mle, sizeof(cc->mle)); + data->chunk_max_write = bit_lib_bytes_to_num_be((void*)&cc->mlc, sizeof(cc->mlc)); + data->ndef_file_id = + bit_lib_bytes_to_num_be((void*)&ndef_file_ctrl->file_id, sizeof(ndef_file_ctrl->file_id)); + data->ndef_max_len = + bit_lib_bytes_to_num_be((void*)&ndef_file_ctrl->max_len, sizeof(ndef_file_ctrl->max_len)) - + sizeof(uint16_t); + data->ndef_read_lock = ndef_file_ctrl->read_perm; + data->ndef_write_lock = ndef_file_ctrl->write_perm; + + return Type4TagErrorNone; +} + +bool type_4_tag_ndef_data_load(Type4TagData* data, FlipperFormat* ff) { + uint32_t ndef_data_size; + if(!flipper_format_read_uint32(ff, TYPE_4_TAG_FFF_NDEF_DATA_SIZE_KEY, &ndef_data_size, 1)) { + return false; + } + if(ndef_data_size == 0) { + return true; + } + + simple_array_init(data->ndef_data, ndef_data_size); + + uint32_t ndef_data_pos = 0; + uint8_t* ndef_data = simple_array_get_data(data->ndef_data); + while(ndef_data_size > 0) { + uint8_t ndef_line_size = MIN(ndef_data_size, TYPE_4_TAG_FFF_NDEF_DATA_PER_LINE); + + if(!flipper_format_read_hex( + ff, TYPE_4_TAG_FFF_NDEF_DATA_KEY, &ndef_data[ndef_data_pos], ndef_line_size)) { + simple_array_reset(data->ndef_data); + return false; + } + + ndef_data_pos += ndef_line_size; + ndef_data_size -= ndef_line_size; + } + + return true; +} + +bool type_4_tag_ndef_data_save(const Type4TagData* data, FlipperFormat* ff) { + uint32_t ndef_data_size = simple_array_get_count(data->ndef_data); + if(!flipper_format_write_uint32(ff, TYPE_4_TAG_FFF_NDEF_DATA_SIZE_KEY, &ndef_data_size, 1)) { + return false; + } + if(ndef_data_size == 0) { + return true; + } + + uint32_t ndef_data_pos = 0; + uint8_t* ndef_data = simple_array_get_data(data->ndef_data); + while(ndef_data_size > 0) { + uint8_t ndef_line_size = MIN(ndef_data_size, TYPE_4_TAG_FFF_NDEF_DATA_PER_LINE); + + if(!flipper_format_write_hex( + ff, TYPE_4_TAG_FFF_NDEF_DATA_KEY, &ndef_data[ndef_data_pos], ndef_line_size)) { + return false; + } + + ndef_data_pos += ndef_line_size; + ndef_data_size -= ndef_line_size; + } + + return true; +} diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_i.h b/lib/nfc/protocols/type_4_tag/type_4_tag_i.h new file mode 100644 index 000000000..f47d75236 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_i.h @@ -0,0 +1,110 @@ +#pragma once + +#include "type_4_tag.h" + +// ISO SELECT FILE command and parameters +#define TYPE_4_TAG_ISO_SELECT_CMD 0x00, 0xA4 +#define TYPE_4_TAG_ISO_SELECT_P1_BY_NAME (0x04) +#define TYPE_4_TAG_ISO_SELECT_P1_BY_EF_ID (0x02) +#define TYPE_4_TAG_ISO_SELECT_P1_BY_ID (0x00) +#define TYPE_4_TAG_ISO_SELECT_P2_EMPTY (0x0C) +#define TYPE_4_TAG_ISO_SELECT_LE_EMPTY (0x00) + +// ISO READ BINARY command and parameters +#define TYPE_4_TAG_ISO_READ_CMD 0x00, 0xB0 +#define TYPE_4_TAG_ISO_READ_P1_ID_MASK (1 << 7) +#define TYPE_4_TAG_ISO_READ_P_BEGINNING 0x00, 0x00 +#define TYPE_4_TAG_ISO_READ_P_OFFSET_MAX (32767U) +#define TYPE_4_TAG_ISO_READ_LE_FULL (0x00) + +// ISO UPDATE BINARY command and parameters +#define TYPE_4_TAG_ISO_WRITE_CMD 0x00, 0xD6 +#define TYPE_4_TAG_ISO_WRITE_P1_ID_MASK (1 << 7) +#define TYPE_4_TAG_ISO_WRITE_P_BEGINNING 0x00, 0x00 +#define TYPE_4_TAG_ISO_WRITE_LE_EMPTY (0x00) + +// Common APDU parameters and values +#define TYPE_4_TAG_ISO_STATUS_LEN (2U) +#define TYPE_4_TAG_ISO_STATUS_SUCCESS 0x90, 0x00 +#define TYPE_4_TAG_ISO_STATUS_OFFSET_ERR 0x6B, 0x00 +#define TYPE_4_TAG_ISO_STATUS_NOT_FOUND 0x6A, 0x82 +#define TYPE_4_TAG_ISO_STATUS_NO_SUPPORT 0x6A, 0x81 +#define TYPE_4_TAG_ISO_STATUS_BAD_PARAMS 0x6A, 0x86 +#define TYPE_4_TAG_ISO_STATUS_NO_CMD 0x68, 0x00 +#define TYPE_4_TAG_ISO_RW_CHUNK_LEN (255U) + +// Common IDs and Names, note: +// MF = Master File (PICC/Card Level) +// DF = Dedicated File (Application) +// EF = Elementary File (File) +#define TYPE_4_TAG_ISO_NAME_LEN (7U) +#define TYPE_4_TAG_ISO_MF_NAME 0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x00 +#define TYPE_4_TAG_ISO_DF_NAME 0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01 +#define TYPE_4_TAG_ISO_ID_LEN (2U) +#define TYPE_4_TAG_ISO_MF_ID (0x3F00) +#define TYPE_4_TAG_ISO_DF_ID (0xE110) +#define TYPE_4_TAG_T4T_CC_EF_ID (0xE103) +#define TYPE_4_TAG_T4T_NDEF_EF_ID (0xE104) + +// Capability Container parsing parameters +#define TYPE_4_TAG_T4T_CC_VNO (0x20) +#define TYPE_4_TAG_T4T_CC_RW_LOCK_NONE (0x00) +#define TYPE_4_TAG_T4T_CC_MIN_SIZE (sizeof(Type4TagCc) + sizeof(Type4TagCcTlv)) + +// Implementation-specific sizes and defaults +// 4a layer adds 1..3 byte prefix, 3a layer adds 2 byte suffix and has 256 byte buffer +#define TYPE_4_TAG_BUF_SIZE (256U - 3U - 2U) +// Read returns 2 byte status trailer, write sends 5 byte command header +#define TYPE_4_TAG_CHUNK_LEN MIN(TYPE_4_TAG_BUF_SIZE - 5U, TYPE_4_TAG_ISO_RW_CHUNK_LEN) +#define TYPE_4_TAG_DEFAULT_NDEF_SIZE TYPE_4_TAG_MF_DESFIRE_NDEF_SIZE + +extern const uint8_t type_4_tag_iso_mf_name[TYPE_4_TAG_ISO_NAME_LEN]; +extern const uint8_t type_4_tag_iso_df_name[TYPE_4_TAG_ISO_NAME_LEN]; + +// Capability Container parsing structures + +typedef enum FURI_PACKED { + Type4TagCcTlvTypeNdefFileCtrl = 0x04, + Type4TagCcTlvTypeProprietaryFileCtrl = 0x05, +} Type4TagCcTlvType; + +typedef struct FURI_PACKED { + uint16_t file_id; + uint16_t max_len; + uint8_t read_perm; + uint8_t write_perm; +} Type4TagCcTlvNdefFileCtrl; + +typedef union FURI_PACKED { + Type4TagCcTlvNdefFileCtrl ndef_file_ctrl; +} Type4TagCcTlvValue; + +typedef struct FURI_PACKED { + Type4TagCcTlvType type; + uint8_t len; + Type4TagCcTlvValue value; +} Type4TagCcTlv; + +typedef struct FURI_PACKED { + uint16_t len; + uint8_t t4t_vno; + uint16_t mle; + uint16_t mlc; + Type4TagCcTlv tlv[]; +} Type4TagCc; + +// Internal helpers + +Type4TagError type_4_tag_process_error(Iso14443_4aError error); + +void type_4_tag_cc_dump(const Type4TagData* data, uint8_t* buf, size_t len); + +Type4TagError type_4_tag_cc_parse(Type4TagData* data, const uint8_t* buf, size_t len); + +// Load internal Type4Tag structures + +bool type_4_tag_ndef_data_load(Type4TagData* data, FlipperFormat* ff); + +// Save internal Type4Tag structures + +bool type_4_tag_ndef_data_save(const Type4TagData* data, FlipperFormat* ff); diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_listener.c b/lib/nfc/protocols/type_4_tag/type_4_tag_listener.c new file mode 100644 index 000000000..58e94470d --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_listener.c @@ -0,0 +1,88 @@ +#include "type_4_tag_listener_i.h" +#include "type_4_tag_listener_defs.h" +#include "type_4_tag_i.h" + +#define TAG "Type4TagListener" + +static void type_4_tag_listener_reset_state(Type4TagListener* instance) { + instance->state = Type4TagListenerStateIdle; +} + +static Type4TagListener* + type_4_tag_listener_alloc(Iso14443_4aListener* iso14443_4a_listener, Type4TagData* data) { + furi_assert(iso14443_4a_listener); + + Type4TagListener* instance = malloc(sizeof(Type4TagListener)); + instance->iso14443_4a_listener = iso14443_4a_listener; + instance->data = data; + + instance->tx_buffer = bit_buffer_alloc(TYPE_4_TAG_BUF_SIZE); + + instance->type_4_tag_event.data = &instance->type_4_tag_event_data; + instance->generic_event.protocol = NfcProtocolType4Tag; + instance->generic_event.instance = instance; + instance->generic_event.event_data = &instance->type_4_tag_event; + + return instance; +} + +static void type_4_tag_listener_free(Type4TagListener* instance) { + furi_assert(instance); + furi_assert(instance->data); + furi_assert(instance->tx_buffer); + + bit_buffer_free(instance->tx_buffer); + free(instance); +} + +static void type_4_tag_listener_set_callback( + Type4TagListener* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + + instance->callback = callback; + instance->context = context; +} + +static const Type4TagData* type_4_tag_listener_get_data(Type4TagListener* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +static NfcCommand type_4_tag_listener_run(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolIso15693_3); + furi_assert(event.event_data); + + Type4TagListener* instance = context; + Iso14443_4aListenerEvent* iso14443_4a_event = event.event_data; + BitBuffer* rx_buffer = iso14443_4a_event->data->buffer; + NfcCommand command = NfcCommandContinue; + + if(iso14443_4a_event->type == Iso14443_4aListenerEventTypeFieldOff) { + type_4_tag_listener_reset_state(instance); + command = NfcCommandSleep; + } else if(iso14443_4a_event->type == Iso14443_4aListenerEventTypeHalted) { + type_4_tag_listener_reset_state(instance); + } else if(iso14443_4a_event->type == Iso14443_4aListenerEventTypeReceivedData) { + const Type4TagError error = type_4_tag_listener_handle_apdu(instance, rx_buffer); + if(error == Type4TagErrorCustomCommand && instance->callback) { + instance->type_4_tag_event.type = Type4TagListenerEventTypeCustomCommand; + instance->type_4_tag_event.data->buffer = rx_buffer; + command = instance->callback(instance->generic_event, instance->context); + } + } + + return command; +} + +const NfcListenerBase nfc_listener_type_4_tag = { + .alloc = (NfcListenerAlloc)type_4_tag_listener_alloc, + .free = (NfcListenerFree)type_4_tag_listener_free, + .set_callback = (NfcListenerSetCallback)type_4_tag_listener_set_callback, + .get_data = (NfcListenerGetData)type_4_tag_listener_get_data, + .run = (NfcListenerRun)type_4_tag_listener_run, +}; diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_listener.h b/lib/nfc/protocols/type_4_tag/type_4_tag_listener.h new file mode 100644 index 000000000..7e0301322 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_listener.h @@ -0,0 +1,26 @@ +#pragma once + +#include "type_4_tag.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Type4TagListener Type4TagListener; + +typedef enum { + Type4TagListenerEventTypeCustomCommand, +} Type4TagListenerEventType; + +typedef struct { + BitBuffer* buffer; +} Type4TagListenerEventData; + +typedef struct { + Type4TagListenerEventType type; + Type4TagListenerEventData* data; +} Type4TagListenerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_listener_defs.h b/lib/nfc/protocols/type_4_tag/type_4_tag_listener_defs.h new file mode 100644 index 000000000..87206fe6b --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_listener_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcListenerBase nfc_listener_type_4_tag; diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_listener_i.c b/lib/nfc/protocols/type_4_tag/type_4_tag_listener_i.c new file mode 100644 index 000000000..7da6fadc9 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_listener_i.c @@ -0,0 +1,382 @@ +#include "type_4_tag_listener_i.h" +#include "type_4_tag_i.h" + +#include + +#define TAG "Type4TagListener" + +typedef Type4TagError (*Type4TagListenerApduHandler)( + Type4TagListener* instance, + uint8_t p1, + uint8_t p2, + size_t lc, + const uint8_t* data, + size_t le); + +typedef struct { + uint8_t cla_ins[2]; + Type4TagListenerApduHandler handler; +} Type4TagListenerApduCommand; + +static const uint8_t type_4_tag_success_apdu[] = {TYPE_4_TAG_ISO_STATUS_SUCCESS}; +static const uint8_t type_4_tag_offset_error_apdu[] = {TYPE_4_TAG_ISO_STATUS_OFFSET_ERR}; +static const uint8_t type_4_tag_not_found_apdu[] = {TYPE_4_TAG_ISO_STATUS_NOT_FOUND}; +static const uint8_t type_4_tag_no_support_apdu[] = {TYPE_4_TAG_ISO_STATUS_NO_SUPPORT}; +static const uint8_t type_4_tag_bad_params_apdu[] = {TYPE_4_TAG_ISO_STATUS_BAD_PARAMS}; +static const uint8_t type_4_tag_no_cmd_apdu[] = {TYPE_4_TAG_ISO_STATUS_NO_CMD}; + +static Type4TagError type_4_tag_listener_iso_select( + Type4TagListener* instance, + uint8_t p1, + uint8_t p2, + size_t lc, + const uint8_t* data, + size_t le) { + UNUSED(p2); + UNUSED(le); + + if(p1 == TYPE_4_TAG_ISO_SELECT_P1_BY_NAME && lc == TYPE_4_TAG_ISO_NAME_LEN) { + if(memcmp(type_4_tag_iso_mf_name, data, sizeof(type_4_tag_iso_mf_name)) == 0) { + instance->state = Type4TagListenerStateSelectedPicc; + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu)); + return Type4TagErrorNone; + } + + if(memcmp(type_4_tag_iso_df_name, data, sizeof(type_4_tag_iso_df_name)) == 0) { + instance->state = Type4TagListenerStateSelectedApplication; + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu)); + return Type4TagErrorNone; + } + + } else if( + (p1 == TYPE_4_TAG_ISO_SELECT_P1_BY_ID || p1 == TYPE_4_TAG_ISO_SELECT_P1_BY_EF_ID) && + lc == TYPE_4_TAG_ISO_ID_LEN) { + uint16_t id = bit_lib_bytes_to_num_be(data, sizeof(uint16_t)); + + if(p1 == TYPE_4_TAG_ISO_SELECT_P1_BY_ID) { + if(id == TYPE_4_TAG_ISO_MF_ID) { + instance->state = Type4TagListenerStateSelectedPicc; + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu)); + return Type4TagErrorNone; + } + + if(id == TYPE_4_TAG_ISO_DF_ID) { + instance->state = Type4TagListenerStateSelectedApplication; + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu)); + return Type4TagErrorNone; + } + } + + if(instance->state >= Type4TagListenerStateSelectedApplication) { + if(id == TYPE_4_TAG_T4T_CC_EF_ID) { + instance->state = Type4TagListenerStateSelectedCapabilityContainer; + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu)); + return Type4TagErrorNone; + } + + if(id == (instance->data->is_tag_specific ? instance->data->ndef_file_id : + TYPE_4_TAG_T4T_NDEF_EF_ID)) { + instance->state = Type4TagListenerStateSelectedNdefMessage; + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu)); + return Type4TagErrorNone; + } + } + } + + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_not_found_apdu, sizeof(type_4_tag_not_found_apdu)); + return Type4TagErrorCustomCommand; +} + +static Type4TagError type_4_tag_listener_iso_read( + Type4TagListener* instance, + uint8_t p1, + uint8_t p2, + size_t lc, + const uint8_t* data, + size_t le) { + UNUSED(lc); + UNUSED(data); + + size_t offset; + if(p1 & TYPE_4_TAG_ISO_READ_P1_ID_MASK) { + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_no_support_apdu, sizeof(type_4_tag_no_support_apdu)); + return Type4TagErrorCustomCommand; + } else { + offset = (p1 << 8) + p2; + } + + if(instance->state == Type4TagListenerStateSelectedCapabilityContainer) { + uint8_t cc_buf[TYPE_4_TAG_T4T_CC_MIN_SIZE]; + if(offset >= sizeof(cc_buf)) { + bit_buffer_append_bytes( + instance->tx_buffer, + type_4_tag_offset_error_apdu, + sizeof(type_4_tag_offset_error_apdu)); + return Type4TagErrorWrongFormat; + } + type_4_tag_cc_dump(instance->data, cc_buf, sizeof(cc_buf)); + + bit_buffer_append_bytes( + instance->tx_buffer, cc_buf + offset, MIN(sizeof(cc_buf) - offset, le)); + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu)); + return Type4TagErrorNone; + } + + if(instance->state == Type4TagListenerStateSelectedNdefMessage) { + size_t ndef_file_len = simple_array_get_count(instance->data->ndef_data); + bool included_len = false; + if(offset < sizeof(uint16_t)) { + uint8_t ndef_file_len_be[sizeof(uint16_t)]; + bit_lib_num_to_bytes_be(ndef_file_len, sizeof(ndef_file_len_be), ndef_file_len_be); + uint8_t read_len = MIN(sizeof(ndef_file_len_be) - offset, le); + bit_buffer_append_bytes(instance->tx_buffer, &ndef_file_len_be[offset], read_len); + included_len = true; + offset = sizeof(uint16_t); + le -= read_len; + } + offset -= sizeof(uint16_t); + + if(offset >= ndef_file_len) { + if(included_len) { + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu)); + return Type4TagErrorNone; + } else { + bit_buffer_append_bytes( + instance->tx_buffer, + type_4_tag_offset_error_apdu, + sizeof(type_4_tag_offset_error_apdu)); + return Type4TagErrorWrongFormat; + } + } + const uint8_t* ndef_data = simple_array_cget_data(instance->data->ndef_data); + bit_buffer_append_bytes( + instance->tx_buffer, &ndef_data[offset], MIN(ndef_file_len - offset, le)); + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu)); + return Type4TagErrorNone; + } + + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_not_found_apdu, sizeof(type_4_tag_not_found_apdu)); + return Type4TagErrorCustomCommand; +} + +static Type4TagError type_4_tag_listener_iso_write( + Type4TagListener* instance, + uint8_t p1, + uint8_t p2, + size_t lc, + const uint8_t* data, + size_t le) { + UNUSED(le); + + size_t offset; + if(p1 & TYPE_4_TAG_ISO_WRITE_P1_ID_MASK) { + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_no_support_apdu, sizeof(type_4_tag_no_support_apdu)); + return Type4TagErrorCustomCommand; + } else { + offset = (p1 << 8) + p2; + } + + if(instance->state == Type4TagListenerStateSelectedCapabilityContainer) { + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_no_support_apdu, sizeof(type_4_tag_no_support_apdu)); + return Type4TagErrorNotSupported; + } + + if(instance->state == Type4TagListenerStateSelectedNdefMessage) { + if(offset + lc > sizeof(uint16_t) + (instance->data->is_tag_specific ? + instance->data->ndef_max_len : + TYPE_4_TAG_DEFAULT_NDEF_SIZE)) { + bit_buffer_append_bytes( + instance->tx_buffer, + type_4_tag_offset_error_apdu, + sizeof(type_4_tag_offset_error_apdu)); + return Type4TagErrorWrongFormat; + } + + const size_t ndef_file_len = simple_array_get_count(instance->data->ndef_data); + size_t ndef_file_len_new = ndef_file_len; + if(offset < sizeof(uint16_t)) { + const uint8_t write_len = sizeof(uint16_t) - offset; + ndef_file_len_new = bit_lib_bytes_to_num_be(data, write_len); + offset = sizeof(uint16_t); + data += offset; + lc -= write_len; + } + offset -= sizeof(uint16_t); + + ndef_file_len_new = MAX(ndef_file_len_new, offset + lc); + if(ndef_file_len_new != ndef_file_len) { + SimpleArray* ndef_data_temp = simple_array_alloc(&simple_array_config_uint8_t); + if(ndef_file_len_new > 0) { + simple_array_init(ndef_data_temp, ndef_file_len_new); + if(ndef_file_len > 0) { + memcpy( + simple_array_get_data(ndef_data_temp), + simple_array_get_data(instance->data->ndef_data), + MIN(ndef_file_len_new, ndef_file_len)); + } + } + simple_array_copy(instance->data->ndef_data, ndef_data_temp); + simple_array_free(ndef_data_temp); + } + + if(ndef_file_len_new > 0 && lc > 0) { + uint8_t* ndef_data = simple_array_get_data(instance->data->ndef_data); + memcpy(&ndef_data[offset], data, lc); + } + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu)); + return Type4TagErrorNone; + } + + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_not_found_apdu, sizeof(type_4_tag_not_found_apdu)); + return Type4TagErrorCustomCommand; +} + +static const Type4TagListenerApduCommand type_4_tag_listener_commands[] = { + { + .cla_ins = {TYPE_4_TAG_ISO_SELECT_CMD}, + .handler = type_4_tag_listener_iso_select, + }, + { + .cla_ins = {TYPE_4_TAG_ISO_READ_CMD}, + .handler = type_4_tag_listener_iso_read, + }, + { + .cla_ins = {TYPE_4_TAG_ISO_WRITE_CMD}, + .handler = type_4_tag_listener_iso_write, + }, +}; + +Type4TagError + type_4_tag_listener_handle_apdu(Type4TagListener* instance, const BitBuffer* rx_buffer) { + Type4TagError error = Type4TagErrorNone; + + bit_buffer_reset(instance->tx_buffer); + + do { + typedef struct { + uint8_t cla_ins[2]; + uint8_t p1; + uint8_t p2; + uint8_t body[]; + } Type4TagApdu; + + const size_t buf_size = bit_buffer_get_size_bytes(rx_buffer); + + if(buf_size < sizeof(Type4TagApdu)) { + error = Type4TagErrorWrongFormat; + break; + } + + const Type4TagApdu* apdu = (const Type4TagApdu*)bit_buffer_get_data(rx_buffer); + + Type4TagListenerApduHandler handler = NULL; + for(size_t i = 0; i < COUNT_OF(type_4_tag_listener_commands); i++) { + const Type4TagListenerApduCommand* command = &type_4_tag_listener_commands[i]; + if(memcmp(apdu->cla_ins, command->cla_ins, sizeof(apdu->cla_ins)) == 0) { + handler = command->handler; + break; + } + } + if(!handler) { + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_no_cmd_apdu, sizeof(type_4_tag_no_cmd_apdu)); + error = Type4TagErrorCustomCommand; + break; + } + + size_t body_size = buf_size - offsetof(Type4TagApdu, body); + size_t lc; + const uint8_t* data = apdu->body; + size_t le; + if(body_size == 0) { + lc = 0; + data = NULL; + le = 0; + } else if(body_size == 1) { + lc = 0; + data = NULL; + le = apdu->body[0]; + if(le == 0) { + le = 256; + } + } else if(body_size == 3 && apdu->body[0] == 0) { + lc = 0; + data = NULL; + le = bit_lib_bytes_to_num_be(&apdu->body[1], sizeof(uint16_t)); + if(le == 0) { + le = 65536; + } + } else { + bool extended_lc = false; + if(data[0] == 0) { + extended_lc = true; + lc = bit_lib_bytes_to_num_be(&data[1], sizeof(uint16_t)); + data += 1 + sizeof(uint16_t); + body_size -= 1 + sizeof(uint16_t); + } else { + lc = data[0]; + data++; + body_size--; + } + if(lc == 0 || body_size < lc) { + bit_buffer_append_bytes( + instance->tx_buffer, + type_4_tag_bad_params_apdu, + sizeof(type_4_tag_bad_params_apdu)); + error = Type4TagErrorWrongFormat; + break; + } + + if(body_size == lc) { + le = 0; + } else if(!extended_lc && body_size - lc == 1) { + le = data[lc]; + if(le == 0) { + le = 256; + } + } else if(extended_lc && body_size - lc == 2) { + le = bit_lib_bytes_to_num_be(&data[lc], sizeof(uint16_t)); + if(le == 0) { + le = 65536; + } + } else { + bit_buffer_append_bytes( + instance->tx_buffer, + type_4_tag_bad_params_apdu, + sizeof(type_4_tag_bad_params_apdu)); + error = Type4TagErrorWrongFormat; + break; + } + } + + error = handler(instance, apdu->p1, apdu->p2, lc, data, le); + } while(false); + + if(bit_buffer_get_size_bytes(instance->tx_buffer) > 0) { + const Iso14443_4aError iso14443_4a_error = + iso14443_4a_listener_send_block(instance->iso14443_4a_listener, instance->tx_buffer); + + // Keep error flag to show unknown command on screen + if(error != Type4TagErrorCustomCommand) { + error = type_4_tag_process_error(iso14443_4a_error); + } + } + + return error; +} diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_listener_i.h b/lib/nfc/protocols/type_4_tag/type_4_tag_listener_i.h new file mode 100644 index 000000000..fd602c974 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_listener_i.h @@ -0,0 +1,38 @@ +#pragma once + +#include "type_4_tag_listener.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + Type4TagListenerStateIdle, + Type4TagListenerStateSelectedPicc, + Type4TagListenerStateSelectedApplication, + Type4TagListenerStateSelectedCapabilityContainer, + Type4TagListenerStateSelectedNdefMessage, +} Type4TagListenerState; + +struct Type4TagListener { + Iso14443_4aListener* iso14443_4a_listener; + Type4TagData* data; + Type4TagListenerState state; + + BitBuffer* tx_buffer; + + NfcGenericEvent generic_event; + Type4TagListenerEvent type_4_tag_event; + Type4TagListenerEventData type_4_tag_event_data; + NfcGenericCallback callback; + void* context; +}; + +Type4TagError + type_4_tag_listener_handle_apdu(Type4TagListener* instance, const BitBuffer* rx_buffer); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_poller.c b/lib/nfc/protocols/type_4_tag/type_4_tag_poller.c new file mode 100644 index 000000000..0291d2df9 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_poller.c @@ -0,0 +1,289 @@ +#include "type_4_tag_poller_i.h" +#include "type_4_tag_poller_defs.h" +#include "type_4_tag_i.h" + +#define TAG "Type4TagPoller" + +typedef NfcCommand (*Type4TagPollerReadHandler)(Type4TagPoller* instance); + +static const Type4TagData* type_4_tag_poller_get_data(Type4TagPoller* instance) { + furi_assert(instance); + + return instance->data; +} + +static Type4TagPoller* type_4_tag_poller_alloc(Iso14443_4aPoller* iso14443_4a_poller) { + Type4TagPoller* instance = malloc(sizeof(Type4TagPoller)); + instance->iso14443_4a_poller = iso14443_4a_poller; + instance->data = type_4_tag_alloc(); + instance->tx_buffer = bit_buffer_alloc(TYPE_4_TAG_BUF_SIZE); + instance->rx_buffer = bit_buffer_alloc(TYPE_4_TAG_BUF_SIZE); + + instance->type_4_tag_event.data = &instance->type_4_tag_event_data; + + instance->general_event.protocol = NfcProtocolType4Tag; + instance->general_event.event_data = &instance->type_4_tag_event; + instance->general_event.instance = instance; + + return instance; +} + +static void type_4_tag_poller_free(Type4TagPoller* instance) { + furi_assert(instance); + + type_4_tag_free(instance->data); + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + free(instance); +} + +static NfcCommand type_4_tag_poller_handler_idle(Type4TagPoller* instance) { + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + iso14443_4a_copy( + instance->data->iso14443_4a_data, + iso14443_4a_poller_get_data(instance->iso14443_4a_poller)); + + instance->state = Type4TagPollerStateRequestMode; + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_request_mode(Type4TagPoller* instance) { + NfcCommand command = NfcCommandContinue; + + instance->type_4_tag_event.type = Type4TagPollerEventTypeRequestMode; + instance->type_4_tag_event.data->poller_mode.mode = Type4TagPollerModeRead; + instance->type_4_tag_event.data->poller_mode.data = NULL; + + command = instance->callback(instance->general_event, instance->context); + instance->mode = instance->type_4_tag_event.data->poller_mode.mode; + if(instance->mode == Type4TagPollerModeWrite) { + type_4_tag_copy(instance->data, instance->type_4_tag_event.data->poller_mode.data); + } + + instance->state = Type4TagPollerStateDetectPlatform; + return command; +} + +static NfcCommand type_4_tag_poller_handler_detect_platform(Type4TagPoller* instance) { + instance->error = type_4_tag_poller_detect_platform(instance); + if(instance->error == Type4TagErrorNone) { + FURI_LOG_D(TAG, "Detect platform success"); + } else { + FURI_LOG_W(TAG, "Failed to detect platform"); + } + instance->state = Type4TagPollerStateSelectApplication; + + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_select_app(Type4TagPoller* instance) { + instance->error = type_4_tag_poller_select_app(instance); + if(instance->error == Type4TagErrorNone) { + FURI_LOG_D(TAG, "Select application success"); + instance->state = Type4TagPollerStateReadCapabilityContainer; + } else { + FURI_LOG_E(TAG, "Failed to select application"); + if(instance->mode == Type4TagPollerModeWrite && + instance->error == Type4TagErrorCardUnformatted) { + instance->state = Type4TagPollerStateCreateApplication; + } else { + instance->state = Type4TagPollerStateFailed; + } + } + + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_read_cc(Type4TagPoller* instance) { + instance->error = type_4_tag_poller_read_cc(instance); + if(instance->error == Type4TagErrorNone) { + FURI_LOG_D(TAG, "Read CC success"); + instance->state = instance->mode == Type4TagPollerModeRead ? + Type4TagPollerStateReadNdefMessage : + Type4TagPollerStateWriteNdefMessage; + } else { + FURI_LOG_E(TAG, "Failed to read CC"); + if(instance->mode == Type4TagPollerModeWrite && + instance->error == Type4TagErrorCardUnformatted) { + instance->state = Type4TagPollerStateCreateCapabilityContainer; + } else { + instance->state = Type4TagPollerStateFailed; + } + } + + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_read_ndef(Type4TagPoller* instance) { + instance->error = type_4_tag_poller_read_ndef(instance); + if(instance->error == Type4TagErrorNone) { + FURI_LOG_D(TAG, "Read NDEF success"); + instance->state = Type4TagPollerStateSuccess; + } else { + FURI_LOG_E(TAG, "Failed to read NDEF"); + instance->state = Type4TagPollerStateFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_create_app(Type4TagPoller* instance) { + instance->error = type_4_tag_poller_create_app(instance); + if(instance->error == Type4TagErrorNone) { + FURI_LOG_D(TAG, "Create application success"); + instance->state = Type4TagPollerStateSelectApplication; + } else { + FURI_LOG_E(TAG, "Failed to create application"); + instance->state = Type4TagPollerStateFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_create_cc(Type4TagPoller* instance) { + instance->error = type_4_tag_poller_create_cc(instance); + if(instance->error == Type4TagErrorNone) { + FURI_LOG_D(TAG, "Create CC success"); + instance->state = Type4TagPollerStateReadCapabilityContainer; + } else { + FURI_LOG_E(TAG, "Failed to create CC"); + instance->state = Type4TagPollerStateFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_create_ndef(Type4TagPoller* instance) { + instance->error = type_4_tag_poller_create_ndef(instance); + if(instance->error == Type4TagErrorNone) { + FURI_LOG_D(TAG, "Create NDEF success"); + instance->state = Type4TagPollerStateWriteNdefMessage; + } else { + FURI_LOG_E(TAG, "Failed to create NDEF"); + instance->state = Type4TagPollerStateFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_write_ndef(Type4TagPoller* instance) { + instance->error = type_4_tag_poller_write_ndef(instance); + if(instance->error == Type4TagErrorNone) { + FURI_LOG_D(TAG, "Write NDEF success"); + instance->state = Type4TagPollerStateSuccess; + } else { + FURI_LOG_E(TAG, "Failed to write NDEF"); + if(instance->mode == Type4TagPollerModeWrite && + instance->error == Type4TagErrorCardUnformatted) { + instance->state = Type4TagPollerStateCreateNdefMessage; + } else { + instance->state = Type4TagPollerStateFailed; + } + } + + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_failed(Type4TagPoller* instance) { + FURI_LOG_D(TAG, "Operation Failed"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->type_4_tag_event.type = instance->mode == Type4TagPollerModeRead ? + Type4TagPollerEventTypeReadFailed : + Type4TagPollerEventTypeWriteFail; + instance->type_4_tag_event.data->error = instance->error; + NfcCommand command = instance->callback(instance->general_event, instance->context); + instance->state = Type4TagPollerStateIdle; + return command; +} + +static NfcCommand type_4_tag_poller_handler_success(Type4TagPoller* instance) { + FURI_LOG_D(TAG, "Operation succeeded"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->type_4_tag_event.type = instance->mode == Type4TagPollerModeRead ? + Type4TagPollerEventTypeReadSuccess : + Type4TagPollerEventTypeWriteSuccess; + NfcCommand command = instance->callback(instance->general_event, instance->context); + return command; +} + +static const Type4TagPollerReadHandler type_4_tag_poller_read_handler[Type4TagPollerStateNum] = { + [Type4TagPollerStateIdle] = type_4_tag_poller_handler_idle, + [Type4TagPollerStateRequestMode] = type_4_tag_poller_handler_request_mode, + [Type4TagPollerStateDetectPlatform] = type_4_tag_poller_handler_detect_platform, + [Type4TagPollerStateSelectApplication] = type_4_tag_poller_handler_select_app, + [Type4TagPollerStateReadCapabilityContainer] = type_4_tag_poller_handler_read_cc, + [Type4TagPollerStateReadNdefMessage] = type_4_tag_poller_handler_read_ndef, + [Type4TagPollerStateCreateApplication] = type_4_tag_poller_handler_create_app, + [Type4TagPollerStateCreateCapabilityContainer] = type_4_tag_poller_handler_create_cc, + [Type4TagPollerStateCreateNdefMessage] = type_4_tag_poller_handler_create_ndef, + [Type4TagPollerStateWriteNdefMessage] = type_4_tag_poller_handler_write_ndef, + [Type4TagPollerStateFailed] = type_4_tag_poller_handler_failed, + [Type4TagPollerStateSuccess] = type_4_tag_poller_handler_success, +}; + +static void type_4_tag_poller_set_callback( + Type4TagPoller* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static NfcCommand type_4_tag_poller_run(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_4a); + + Type4TagPoller* instance = context; + furi_assert(instance); + furi_assert(instance->callback); + + const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; + furi_assert(iso14443_4a_event); + + NfcCommand command = NfcCommandContinue; + + if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { + command = type_4_tag_poller_read_handler[instance->state](instance); + } else if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeError) { + instance->type_4_tag_event.type = Type4TagPollerEventTypeReadFailed; + instance->type_4_tag_event.data->error = + type_4_tag_process_error(iso14443_4a_event->data->error); + command = instance->callback(instance->general_event, instance->context); + } + + return command; +} + +static bool type_4_tag_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_4a); + + Type4TagPoller* instance = context; + furi_assert(instance); + + const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; + furi_assert(iso14443_4a_event); + + bool protocol_detected = false; + + if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { + Type4TagError error = type_4_tag_poller_select_app(instance); + if(error == Type4TagErrorNone) { + protocol_detected = true; + } + } + + return protocol_detected; +} + +const NfcPollerBase type_4_tag_poller = { + .alloc = (NfcPollerAlloc)type_4_tag_poller_alloc, + .free = (NfcPollerFree)type_4_tag_poller_free, + .set_callback = (NfcPollerSetCallback)type_4_tag_poller_set_callback, + .run = (NfcPollerRun)type_4_tag_poller_run, + .detect = (NfcPollerDetect)type_4_tag_poller_detect, + .get_data = (NfcPollerGetData)type_4_tag_poller_get_data, +}; diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_poller.h b/lib/nfc/protocols/type_4_tag/type_4_tag_poller.h new file mode 100644 index 000000000..5ac4fd0f2 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_poller.h @@ -0,0 +1,63 @@ +#pragma once + +#include "type_4_tag.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Type4TagPoller opaque type definition. + */ +typedef struct Type4TagPoller Type4TagPoller; + +/** + * @brief Enumeration of possible Type4Tag poller event types. + */ +typedef enum { + Type4TagPollerEventTypeRequestMode, /**< Poller requests for operating mode. */ + Type4TagPollerEventTypeReadSuccess, /**< Card was read successfully. */ + Type4TagPollerEventTypeReadFailed, /**< Poller failed to read card. */ + Type4TagPollerEventTypeWriteSuccess, /**< Poller wrote card successfully. */ + Type4TagPollerEventTypeWriteFail, /**< Poller failed to write card. */ +} Type4TagPollerEventType; + +/** + * @brief Enumeration of possible Type4Tag poller operating modes. + */ +typedef enum { + Type4TagPollerModeRead, /**< Poller will only read card. It's a default mode. */ + Type4TagPollerModeWrite, /**< Poller will write already saved card to another presented card. */ +} Type4TagPollerMode; + +/** + * @brief Type4Tag poller request mode event data. + * + * This instance of this structure must be filled on Type4TagPollerEventTypeRequestMode event. + */ +typedef struct { + Type4TagPollerMode mode; /**< Mode to be used by poller. */ + const Type4TagData* data; /**< Data to be used by poller. */ +} Type4TagPollerEventDataRequestMode; + +/** + * @brief Type4Tag poller event data. + */ +typedef union { + Type4TagError error; /**< Error code indicating card reading fail reason. */ + Type4TagPollerEventDataRequestMode poller_mode; /**< Poller mode context. */ +} Type4TagPollerEventData; + +/** + * @brief Type4Tag poller event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ +typedef struct { + Type4TagPollerEventType type; /**< Type of emmitted event. */ + Type4TagPollerEventData* data; /**< Pointer to event specific data. */ +} Type4TagPollerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_poller_defs.h b/lib/nfc/protocols/type_4_tag/type_4_tag_poller_defs.h new file mode 100644 index 000000000..fe71b560a --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_poller_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcPollerBase type_4_tag_poller; diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.c b/lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.c new file mode 100644 index 000000000..52f3d6220 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.c @@ -0,0 +1,501 @@ +#include "type_4_tag_poller_i.h" +#include "type_4_tag_i.h" + +#include + +#include +#include +#include +#include +#include + +#define TAG "Type4TagPoller" + +static const MfDesfireApplicationId mf_des_picc_app_id = {.data = {0x00, 0x00, 0x00}}; +static const MfDesfireApplicationId mf_des_t4t_app_id = {.data = {0x10, 0xEE, 0xEE}}; +static const MfDesfireKeySettings mf_des_t4t_app_key_settings = { + .is_master_key_changeable = true, + .is_free_directory_list = true, + .is_free_create_delete = true, + .is_config_changeable = true, + .change_key_id = 0, + .max_keys = 1, + .flags = 0, +}; +#define MF_DES_T4T_CC_FILE_ID (0x01) +static const MfDesfireFileSettings mf_des_t4t_cc_file = { + .type = MfDesfireFileTypeStandard, + .comm = MfDesfireFileCommunicationSettingsPlaintext, + .access_rights[0] = 0xEEEE, + .access_rights_len = 1, + .data.size = TYPE_4_TAG_T4T_CC_MIN_SIZE, +}; +#define MF_DES_T4T_NDEF_FILE_ID (0x02) +static const MfDesfireFileSettings mf_des_t4t_ndef_file_default = { + .type = MfDesfireFileTypeStandard, + .comm = MfDesfireFileCommunicationSettingsPlaintext, + .access_rights[0] = 0xEEE0, + .access_rights_len = 1, +}; + +Type4TagError type_4_tag_apdu_trx(Type4TagPoller* instance, BitBuffer* tx_buf, BitBuffer* rx_buf) { + furi_check(instance); + + bit_buffer_reset(rx_buf); + + Iso14443_4aError iso14443_4a_error = + iso14443_4a_poller_send_block(instance->iso14443_4a_poller, tx_buf, rx_buf); + + bit_buffer_reset(tx_buf); + + if(iso14443_4a_error != Iso14443_4aErrorNone) { + return type_4_tag_process_error(iso14443_4a_error); + } + + size_t response_len = bit_buffer_get_size_bytes(rx_buf); + if(response_len < TYPE_4_TAG_ISO_STATUS_LEN) { + return Type4TagErrorWrongFormat; + } + + static const uint8_t success[TYPE_4_TAG_ISO_STATUS_LEN] = {TYPE_4_TAG_ISO_STATUS_SUCCESS}; + uint8_t status[TYPE_4_TAG_ISO_STATUS_LEN] = { + bit_buffer_get_byte(rx_buf, response_len - 2), + bit_buffer_get_byte(rx_buf, response_len - 1), + }; + bit_buffer_set_size_bytes(rx_buf, response_len - 2); + + if(memcmp(status, success, sizeof(status)) == 0) { + return Type4TagErrorNone; + } else { + FURI_LOG_E(TAG, "APDU failed: %02X%02X", status[0], status[1]); + return Type4TagErrorApduFailed; + } +} + +static Type4TagError type_4_tag_poller_iso_select_name( + Type4TagPoller* instance, + const uint8_t* name, + uint8_t name_len) { + static const uint8_t type_4_tag_iso_select_name_apdu[] = { + TYPE_4_TAG_ISO_SELECT_CMD, + TYPE_4_TAG_ISO_SELECT_P1_BY_NAME, + TYPE_4_TAG_ISO_SELECT_P2_EMPTY, + }; + + bit_buffer_append_bytes( + instance->tx_buffer, + type_4_tag_iso_select_name_apdu, + sizeof(type_4_tag_iso_select_name_apdu)); + bit_buffer_append_byte(instance->tx_buffer, name_len); + bit_buffer_append_bytes(instance->tx_buffer, name, name_len); + + Type4TagError error = type_4_tag_apdu_trx(instance, instance->tx_buffer, instance->rx_buffer); + if(error == Type4TagErrorApduFailed) error = Type4TagErrorCardUnformatted; + + return error; +} + +static Type4TagError + type_4_tag_poller_iso_select_file(Type4TagPoller* instance, uint16_t file_id) { + static const uint8_t type_4_tag_iso_select_file_apdu[] = { + TYPE_4_TAG_ISO_SELECT_CMD, + TYPE_4_TAG_ISO_SELECT_P1_BY_EF_ID, + TYPE_4_TAG_ISO_SELECT_P2_EMPTY, + sizeof(file_id), + }; + uint8_t file_id_be[sizeof(file_id)]; + bit_lib_num_to_bytes_be(file_id, sizeof(file_id), file_id_be); + + bit_buffer_append_bytes( + instance->tx_buffer, + type_4_tag_iso_select_file_apdu, + sizeof(type_4_tag_iso_select_file_apdu)); + bit_buffer_append_bytes(instance->tx_buffer, file_id_be, sizeof(file_id_be)); + + Type4TagError error = type_4_tag_apdu_trx(instance, instance->tx_buffer, instance->rx_buffer); + if(error == Type4TagErrorApduFailed) error = Type4TagErrorCardUnformatted; + + return error; +} + +static Type4TagError type_4_tag_poller_iso_read( + Type4TagPoller* instance, + uint16_t offset, + uint16_t length, + uint8_t* buffer) { + const uint8_t chunk_max = instance->data->is_tag_specific ? + MIN(instance->data->chunk_max_read, TYPE_4_TAG_CHUNK_LEN) : + TYPE_4_TAG_CHUNK_LEN; + if(offset + length > TYPE_4_TAG_ISO_READ_P_OFFSET_MAX + chunk_max - sizeof(length)) { + FURI_LOG_E(TAG, "File too large: %zu bytes", length); + return Type4TagErrorNotSupported; + } + + static const uint8_t type_4_tag_iso_read_apdu[] = { + TYPE_4_TAG_ISO_READ_CMD, + }; + + while(length > 0) { + uint8_t chunk_len = MIN(length, chunk_max); + uint8_t offset_be[sizeof(offset)]; + bit_lib_num_to_bytes_be(offset, sizeof(offset_be), offset_be); + + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_iso_read_apdu, sizeof(type_4_tag_iso_read_apdu)); + bit_buffer_append_bytes(instance->tx_buffer, offset_be, sizeof(offset_be)); + bit_buffer_append_byte(instance->tx_buffer, chunk_len); + + Type4TagError error = + type_4_tag_apdu_trx(instance, instance->tx_buffer, instance->rx_buffer); + if(error != Type4TagErrorNone) { + return error; + } + if(bit_buffer_get_size_bytes(instance->rx_buffer) != chunk_len) { + FURI_LOG_E( + TAG, + "Wrong chunk len: %zu != %zu", + bit_buffer_get_size_bytes(instance->rx_buffer), + chunk_len); + return Type4TagErrorWrongFormat; + } + + memcpy(buffer, bit_buffer_get_data(instance->rx_buffer), chunk_len); + buffer += chunk_len; + offset += chunk_len; + length -= chunk_len; + } + + return Type4TagErrorNone; +} + +static Type4TagError type_4_tag_poller_iso_write( + Type4TagPoller* instance, + uint16_t offset, + uint16_t length, + uint8_t* buffer) { + const uint8_t chunk_max = instance->data->is_tag_specific ? + MIN(instance->data->chunk_max_write, TYPE_4_TAG_CHUNK_LEN) : + TYPE_4_TAG_CHUNK_LEN; + if(offset + length > TYPE_4_TAG_ISO_READ_P_OFFSET_MAX + chunk_max - sizeof(length)) { + FURI_LOG_E(TAG, "File too large: %zu bytes", length); + return Type4TagErrorNotSupported; + } + + static const uint8_t type_4_tag_iso_write_apdu[] = { + TYPE_4_TAG_ISO_WRITE_CMD, + }; + + while(length > 0) { + uint8_t chunk_len = MIN(length, chunk_max); + uint8_t offset_be[sizeof(offset)]; + bit_lib_num_to_bytes_be(offset, sizeof(offset_be), offset_be); + + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_iso_write_apdu, sizeof(type_4_tag_iso_write_apdu)); + bit_buffer_append_bytes(instance->tx_buffer, offset_be, sizeof(offset_be)); + bit_buffer_append_byte(instance->tx_buffer, chunk_len); + bit_buffer_append_bytes(instance->tx_buffer, buffer, chunk_len); + + Type4TagError error = + type_4_tag_apdu_trx(instance, instance->tx_buffer, instance->rx_buffer); + if(error == Type4TagErrorApduFailed) error = Type4TagErrorCardLocked; + if(error != Type4TagErrorNone) { + return error; + } + + buffer += chunk_len; + offset += chunk_len; + length -= chunk_len; + } + + return Type4TagErrorNone; +} + +Type4TagError type_4_tag_poller_detect_platform(Type4TagPoller* instance) { + furi_check(instance); + + Iso14443_4aPollerEvent iso14443_4a_event = { + .type = Iso14443_4aPollerEventTypeReady, + .data = NULL, + }; + NfcGenericEvent event = { + .protocol = NfcProtocolIso14443_4a, + .instance = instance->iso14443_4a_poller, + .event_data = &iso14443_4a_event, + }; + + Type4TagPlatform platform = Type4TagPlatformUnknown; + NfcDevice* device = nfc_device_alloc(); + + do { + FURI_LOG_D(TAG, "Detect NTAG4xx"); + Ntag4xxPoller* ntag4xx = ntag4xx_poller.alloc(instance->iso14443_4a_poller); + if(ntag4xx_poller.detect(event, ntag4xx)) { + platform = Type4TagPlatformNtag4xx; + nfc_device_set_data(device, NfcProtocolNtag4xx, ntag4xx_poller.get_data(ntag4xx)); + } + ntag4xx_poller.free(ntag4xx); + if(platform != Type4TagPlatformUnknown) break; + + FURI_LOG_D(TAG, "Detect DESFire"); + MfDesfirePoller* mf_desfire = mf_desfire_poller.alloc(instance->iso14443_4a_poller); + mf_desfire_poller_set_command_mode(mf_desfire, NxpNativeCommandModeIsoWrapped); + if(mf_desfire_poller.detect(event, mf_desfire)) { + platform = Type4TagPlatformMfDesfire; + nfc_device_set_data( + device, NfcProtocolMfDesfire, mf_desfire_poller.get_data(mf_desfire)); + } + mf_desfire_poller.free(mf_desfire); + if(platform != Type4TagPlatformUnknown) break; + } while(false); + + Type4TagError error; + if(platform != Type4TagPlatformUnknown) { + furi_string_set( + instance->data->platform_name, nfc_device_get_name(device, NfcDeviceNameTypeFull)); + error = Type4TagErrorNone; + } else { + furi_string_reset(instance->data->platform_name); + error = Type4TagErrorNotSupported; + } + instance->data->platform = platform; + nfc_device_free(device); + + return error; +} + +Type4TagError type_4_tag_poller_select_app(Type4TagPoller* instance) { + furi_check(instance); + + FURI_LOG_D(TAG, "Select application"); + return type_4_tag_poller_iso_select_name( + instance, type_4_tag_iso_df_name, sizeof(type_4_tag_iso_df_name)); +} + +Type4TagError type_4_tag_poller_read_cc(Type4TagPoller* instance) { + furi_check(instance); + + Type4TagError error; + + do { + FURI_LOG_D(TAG, "Select CC"); + error = type_4_tag_poller_iso_select_file(instance, TYPE_4_TAG_T4T_CC_EF_ID); + if(error != Type4TagErrorNone) break; + + FURI_LOG_D(TAG, "Read CC len"); + uint16_t cc_len; + uint8_t cc_len_be[sizeof(cc_len)]; + error = type_4_tag_poller_iso_read(instance, 0, sizeof(cc_len_be), cc_len_be); + if(error != Type4TagErrorNone) break; + cc_len = bit_lib_bytes_to_num_be(cc_len_be, sizeof(cc_len_be)); + + FURI_LOG_D(TAG, "Read CC"); + uint8_t cc_buf[cc_len]; + error = type_4_tag_poller_iso_read(instance, 0, sizeof(cc_buf), cc_buf); + if(error != Type4TagErrorNone) break; + + error = type_4_tag_cc_parse(instance->data, cc_buf, sizeof(cc_buf)); + if(error != Type4TagErrorNone) break; + instance->data->is_tag_specific = true; + + FURI_LOG_D(TAG, "Detected NDEF file ID %04X", instance->data->ndef_file_id); + } while(false); + + return error; +} + +Type4TagError type_4_tag_poller_read_ndef(Type4TagPoller* instance) { + furi_check(instance); + + Type4TagError error; + + do { + FURI_LOG_D(TAG, "Select NDEF"); + error = type_4_tag_poller_iso_select_file(instance, instance->data->ndef_file_id); + if(error != Type4TagErrorNone) break; + + FURI_LOG_D(TAG, "Read NDEF len"); + uint16_t ndef_len; + uint8_t ndef_len_be[sizeof(ndef_len)]; + error = type_4_tag_poller_iso_read(instance, 0, sizeof(ndef_len_be), ndef_len_be); + if(error != Type4TagErrorNone) break; + ndef_len = bit_lib_bytes_to_num_be(ndef_len_be, sizeof(ndef_len_be)); + + if(ndef_len == 0) { + FURI_LOG_D(TAG, "NDEF file is empty"); + break; + } + + FURI_LOG_D(TAG, "Read NDEF"); + simple_array_init(instance->data->ndef_data, ndef_len); + uint8_t* ndef_buf = simple_array_get_data(instance->data->ndef_data); + error = type_4_tag_poller_iso_read(instance, sizeof(ndef_len), ndef_len, ndef_buf); + if(error != Type4TagErrorNone) break; + + FURI_LOG_D( + TAG, "Read %hu bytes from NDEF file %04X", ndef_len, instance->data->ndef_file_id); + } while(false); + + return error; +} + +Type4TagError type_4_tag_poller_create_app(Type4TagPoller* instance) { + Type4TagError error = Type4TagErrorNotSupported; + + if(instance->data->platform == Type4TagPlatformMfDesfire) { + MfDesfirePoller* mf_des = mf_desfire_poller.alloc(instance->iso14443_4a_poller); + mf_desfire_poller_set_command_mode(mf_des, NxpNativeCommandModeIsoWrapped); + MfDesfireError mf_des_error; + + do { + FURI_LOG_D(TAG, "Select DESFire PICC"); + mf_des_error = mf_desfire_poller_select_application(mf_des, &mf_des_picc_app_id); + if(mf_des_error != MfDesfireErrorNone) { + error = Type4TagErrorProtocol; + break; + } + + FURI_LOG_D(TAG, "Create DESFire T4T app"); + mf_des_error = mf_desfire_poller_create_application( + mf_des, + &mf_des_t4t_app_id, + &mf_des_t4t_app_key_settings, + TYPE_4_TAG_ISO_DF_ID, + type_4_tag_iso_df_name, + sizeof(type_4_tag_iso_df_name)); + if(mf_des_error != MfDesfireErrorNone) { + if(mf_des_error != MfDesfireErrorNotPresent && + mf_des_error != MfDesfireErrorTimeout) { + error = Type4TagErrorCardLocked; + } else { + error = Type4TagErrorProtocol; + } + break; + } + + error = Type4TagErrorNone; + } while(false); + + mf_desfire_poller.free(mf_des); + } + + return error; +} + +Type4TagError type_4_tag_poller_create_cc(Type4TagPoller* instance) { + Type4TagError error = Type4TagErrorNotSupported; + + if(instance->data->platform == Type4TagPlatformMfDesfire) { + MfDesfirePoller* mf_des = mf_desfire_poller.alloc(instance->iso14443_4a_poller); + mf_desfire_poller_set_command_mode(mf_des, NxpNativeCommandModeIsoWrapped); + MfDesfireError mf_des_error; + + do { + FURI_LOG_D(TAG, "Create DESFire CC"); + mf_des_error = mf_desfire_poller_create_file( + mf_des, MF_DES_T4T_CC_FILE_ID, &mf_des_t4t_cc_file, TYPE_4_TAG_T4T_CC_EF_ID); + if(mf_des_error != MfDesfireErrorNone) { + if(mf_des_error != MfDesfireErrorNotPresent && + mf_des_error != MfDesfireErrorTimeout) { + error = Type4TagErrorCardLocked; + } else { + error = Type4TagErrorProtocol; + } + break; + } + + FURI_LOG_D(TAG, "Select CC"); + error = type_4_tag_poller_iso_select_file(instance, TYPE_4_TAG_T4T_CC_EF_ID); + if(error != Type4TagErrorNone) break; + + FURI_LOG_D(TAG, "Write DESFire CC"); + instance->data->t4t_version.value = TYPE_4_TAG_T4T_CC_VNO; + instance->data->chunk_max_read = 0x3A; + instance->data->chunk_max_write = 0x34; + instance->data->ndef_file_id = TYPE_4_TAG_T4T_NDEF_EF_ID; + instance->data->ndef_max_len = TYPE_4_TAG_MF_DESFIRE_NDEF_SIZE; + instance->data->ndef_read_lock = TYPE_4_TAG_T4T_CC_RW_LOCK_NONE; + instance->data->ndef_write_lock = TYPE_4_TAG_T4T_CC_RW_LOCK_NONE; + instance->data->is_tag_specific = true; + uint8_t cc_buf[TYPE_4_TAG_T4T_CC_MIN_SIZE]; + type_4_tag_cc_dump(instance->data, cc_buf, sizeof(cc_buf)); + error = type_4_tag_poller_iso_write(instance, 0, sizeof(cc_buf), cc_buf); + if(error != Type4TagErrorNone) break; + + error = Type4TagErrorNone; + } while(false); + + mf_desfire_poller.free(mf_des); + } + + return error; +} + +Type4TagError type_4_tag_poller_create_ndef(Type4TagPoller* instance) { + Type4TagError error = Type4TagErrorNotSupported; + + if(instance->data->platform == Type4TagPlatformMfDesfire) { + MfDesfirePoller* mf_des = mf_desfire_poller.alloc(instance->iso14443_4a_poller); + mf_desfire_poller_set_command_mode(mf_des, NxpNativeCommandModeIsoWrapped); + MfDesfireError mf_des_error; + + do { + FURI_LOG_D(TAG, "Create DESFire NDEF"); + MfDesfireFileSettings mf_des_t4t_ndef_file = mf_des_t4t_ndef_file_default; + mf_des_t4t_ndef_file.data.size = sizeof(uint16_t) + (instance->data->is_tag_specific ? + instance->data->ndef_max_len : + TYPE_4_TAG_DEFAULT_NDEF_SIZE); + mf_des_error = mf_desfire_poller_create_file( + mf_des, MF_DES_T4T_NDEF_FILE_ID, &mf_des_t4t_ndef_file, TYPE_4_TAG_T4T_NDEF_EF_ID); + if(mf_des_error != MfDesfireErrorNone) { + if(mf_des_error != MfDesfireErrorNotPresent && + mf_des_error != MfDesfireErrorTimeout) { + error = Type4TagErrorCardLocked; + } else { + error = Type4TagErrorProtocol; + } + break; + } + + error = Type4TagErrorNone; + } while(false); + + mf_desfire_poller.free(mf_des); + } + + return error; +} + +Type4TagError type_4_tag_poller_write_ndef(Type4TagPoller* instance) { + furi_check(instance); + + Type4TagError error; + + do { + FURI_LOG_D(TAG, "Select NDEF"); + error = type_4_tag_poller_iso_select_file(instance, instance->data->ndef_file_id); + if(error != Type4TagErrorNone) break; + + FURI_LOG_D(TAG, "Write NDEF len"); + uint16_t ndef_len = simple_array_get_count(instance->data->ndef_data); + uint8_t ndef_len_be[sizeof(ndef_len)]; + bit_lib_num_to_bytes_be(ndef_len, sizeof(ndef_len_be), ndef_len_be); + error = type_4_tag_poller_iso_write(instance, 0, sizeof(ndef_len_be), ndef_len_be); + if(error != Type4TagErrorNone) break; + + if(ndef_len == 0) { + FURI_LOG_D(TAG, "NDEF file is empty"); + break; + } + + FURI_LOG_D(TAG, "Write NDEF"); + uint8_t* ndef_buf = simple_array_get_data(instance->data->ndef_data); + error = type_4_tag_poller_iso_write(instance, sizeof(ndef_len), ndef_len, ndef_buf); + if(error != Type4TagErrorNone) break; + + FURI_LOG_D( + TAG, "Wrote %hu bytes to NDEF file %04X", ndef_len, instance->data->ndef_file_id); + } while(false); + + return error; +} diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.h b/lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.h new file mode 100644 index 000000000..1f9142e6e --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.h @@ -0,0 +1,61 @@ +#pragma once + +#include "type_4_tag_poller.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + Type4TagPollerStateIdle, + Type4TagPollerStateRequestMode, + Type4TagPollerStateDetectPlatform, + Type4TagPollerStateSelectApplication, + Type4TagPollerStateReadCapabilityContainer, + Type4TagPollerStateReadNdefMessage, + Type4TagPollerStateCreateApplication, + Type4TagPollerStateCreateCapabilityContainer, + Type4TagPollerStateCreateNdefMessage, + Type4TagPollerStateWriteNdefMessage, + Type4TagPollerStateFailed, + Type4TagPollerStateSuccess, + + Type4TagPollerStateNum, +} Type4TagPollerState; + +struct Type4TagPoller { + Iso14443_4aPoller* iso14443_4a_poller; + Type4TagPollerState state; + Type4TagPollerMode mode; + Type4TagError error; + Type4TagData* data; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + Type4TagPollerEventData type_4_tag_event_data; + Type4TagPollerEvent type_4_tag_event; + NfcGenericEvent general_event; + NfcGenericCallback callback; + void* context; +}; + +Type4TagError type_4_tag_poller_detect_platform(Type4TagPoller* instance); + +Type4TagError type_4_tag_poller_select_app(Type4TagPoller* instance); + +Type4TagError type_4_tag_poller_read_cc(Type4TagPoller* instance); + +Type4TagError type_4_tag_poller_read_ndef(Type4TagPoller* instance); + +Type4TagError type_4_tag_poller_create_app(Type4TagPoller* instance); + +Type4TagError type_4_tag_poller_create_cc(Type4TagPoller* instance); + +Type4TagError type_4_tag_poller_create_ndef(Type4TagPoller* instance); + +Type4TagError type_4_tag_poller_write_ndef(Type4TagPoller* instance); + +#ifdef __cplusplus +} +#endif From c81c1361216dd6eb45bff311416c177d147c1b21 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 30 Jun 2025 20:39:27 +0300 Subject: [PATCH 56/75] merge all nfc app changes by WillyJL --- .../main/nfc/api/nfc_app_api_table_i.h | 21 +- applications/main/nfc/application.fam | 246 ++++++++++ .../main/nfc/helpers/nfc_custom_event.h | 3 + .../main/nfc/helpers/nfc_supported_cards.c | 10 +- .../main/nfc/helpers/nfc_supported_cards.h | 3 +- .../nfc/helpers/protocol_support/emv/emv.c | 7 + .../helpers/protocol_support/felica/felica.c | 18 +- .../iso14443_3a/iso14443_3a.c | 31 +- .../iso14443_3b/iso14443_3b.c | 22 +- .../iso14443_3b/iso14443_3b_i.h | 7 - .../iso14443_4a/iso14443_4a.c | 31 +- .../iso14443_4b/iso14443_4b.c | 31 +- .../protocol_support/iso15693_3/iso15693_3.c | 31 +- .../protocol_support/mf_classic/mf_classic.c | 98 +++- .../protocol_support/mf_desfire/mf_desfire.c | 7 + .../protocol_support/mf_plus/mf_plus.c | 25 +- .../protocol_support/mf_plus/mf_plus_render.c | 16 +- .../mf_ultralight/mf_ultralight.c | 80 +++- .../protocol_support/nfc_protocol_support.c | 448 ++++++++++++++++-- .../protocol_support/nfc_protocol_support.h | 20 +- .../nfc_protocol_support_base.h | 45 ++ .../nfc_protocol_support_common.h | 2 + .../nfc_protocol_support_defs.c | 49 -- .../nfc_protocol_support_defs.h | 12 - .../nfc_protocol_support_gui_common.c | 4 +- .../nfc_protocol_support_gui_common.h | 9 + .../nfc_protocol_support_unlock_helper.h | 8 + .../protocol_support/ntag4xx/ntag4xx.c | 140 ++++++ .../protocol_support/ntag4xx/ntag4xx.h | 5 + .../protocol_support/ntag4xx/ntag4xx_render.c | 110 +++++ .../protocol_support/ntag4xx/ntag4xx_render.h | 14 + .../nfc/helpers/protocol_support/slix/slix.c | 31 +- .../helpers/protocol_support/st25tb/st25tb.c | 18 +- .../protocol_support/type_4_tag/type_4_tag.c | 260 ++++++++++ .../protocol_support/type_4_tag/type_4_tag.h | 5 + .../type_4_tag/type_4_tag_render.c | 59 +++ .../type_4_tag/type_4_tag_render.h | 14 + applications/main/nfc/nfc_app.c | 23 +- applications/main/nfc/nfc_app_i.h | 12 + .../main/nfc/plugins/supported_cards/csc.c | 149 ++++++ .../main/nfc/plugins/supported_cards/emv.c | 2 +- .../main/nfc/plugins/supported_cards/kazan.c | 8 +- .../nfc/plugins/supported_cards/metromoney.c | 2 +- .../main/nfc/plugins/supported_cards/mizip.c | 2 +- .../main/nfc/plugins/supported_cards/ndef.c | 65 ++- .../nfc/plugins/supported_cards/plantain.c | 2 +- .../nfc/plugins/supported_cards/smartrider.c | 334 +++++++++++++ .../nfc/plugins/supported_cards/sonicare.c | 111 +++++ .../nfc/plugins/supported_cards/two_cities.c | 2 +- .../main/nfc/plugins/supported_cards/ventra.c | 292 ++++++++++++ .../plugins/supported_cards/zolotaya_korona.c | 2 +- .../supported_cards/zolotaya_korona_online.c | 2 +- .../main/nfc/scenes/nfc_scene_config.h | 9 +- .../main/nfc/scenes/nfc_scene_detect.c | 4 + .../scenes/nfc_scene_mf_classic_dict_attack.c | 2 - ...ene_mf_classic_update_initial_wrong_card.c | 3 + .../nfc_scene_mf_classic_write_initial.c | 148 ------ .../nfc_scene_mf_classic_write_initial_fail.c | 62 --- ...c_scene_mf_classic_write_initial_success.c | 43 -- ...cene_mf_classic_write_initial_wrong_card.c | 57 --- .../scenes/nfc_scene_mf_ultralight_write.c | 120 ----- .../nfc_scene_mf_ultralight_write_fail.c | 67 --- .../nfc_scene_mf_ultralight_write_success.c | 46 -- .../nfc_scene_mf_ultralight_wrong_card.c | 58 --- applications/main/nfc/scenes/nfc_scene_read.c | 6 + .../main/nfc/scenes/nfc_scene_start.c | 24 +- .../main/nfc/scenes/nfc_scene_write.c | 13 + targets/f7/api_symbols.csv | 55 ++- 68 files changed, 2738 insertions(+), 927 deletions(-) delete mode 100644 applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_i.h delete mode 100644 applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c delete mode 100644 applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.h create mode 100644 applications/main/nfc/helpers/protocol_support/ntag4xx/ntag4xx.c create mode 100644 applications/main/nfc/helpers/protocol_support/ntag4xx/ntag4xx.h create mode 100644 applications/main/nfc/helpers/protocol_support/ntag4xx/ntag4xx_render.c create mode 100644 applications/main/nfc/helpers/protocol_support/ntag4xx/ntag4xx_render.h create mode 100644 applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag.c create mode 100644 applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag.h create mode 100644 applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag_render.c create mode 100644 applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag_render.h create mode 100644 applications/main/nfc/plugins/supported_cards/csc.c create mode 100644 applications/main/nfc/plugins/supported_cards/smartrider.c create mode 100644 applications/main/nfc/plugins/supported_cards/sonicare.c create mode 100644 applications/main/nfc/plugins/supported_cards/ventra.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_fail.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_success.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_wrong_card.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_write.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_fail.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_success.c delete mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_wrong_card.c create mode 100644 applications/main/nfc/scenes/nfc_scene_write.c diff --git a/applications/main/nfc/api/nfc_app_api_table_i.h b/applications/main/nfc/api/nfc_app_api_table_i.h index 790fa5766..d31857b09 100644 --- a/applications/main/nfc/api/nfc_app_api_table_i.h +++ b/applications/main/nfc/api/nfc_app_api_table_i.h @@ -1,5 +1,8 @@ #include "gallagher/gallagher_util.h" #include "mosgortrans/mosgortrans_util.h" +#include "../nfc_app_i.h" +#include "../helpers/protocol_support/nfc_protocol_support_gui_common.h" +#include "../helpers/protocol_support/nfc_protocol_support_unlock_helper.h" /* * A list of app's private functions and objects to expose for plugins. @@ -22,4 +25,20 @@ static constexpr auto nfc_app_api_table = sort(create_array_t( (FuriString * str, const char* name, uint8_t prefix_separator_cnt, - uint8_t suffix_separator_cnt)))); + uint8_t suffix_separator_cnt)), + API_METHOD( + nfc_append_filename_string_when_present, + void, + (NfcApp * instance, FuriString* string)), + API_METHOD(nfc_protocol_support_common_submenu_callback, void, (void* context, uint32_t index)), + API_METHOD( + nfc_protocol_support_common_widget_callback, + void, + (GuiButtonType result, InputType type, void* context)), + API_METHOD(nfc_protocol_support_common_on_enter_empty, void, (NfcApp * instance)), + API_METHOD( + nfc_protocol_support_common_on_event_empty, + bool, + (NfcApp * instance, SceneManagerEvent event)), + API_METHOD(nfc_unlock_helper_setup_from_state, void, (NfcApp * instance)), + API_METHOD(nfc_unlock_helper_card_detected_handler, void, (NfcApp * instance)))); diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 96ac4c3b7..5699685d3 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -18,6 +18,205 @@ App( fap_category="NFC", ) +# Protocol support plugins + +App( + appid="nfc_iso14443_3a", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_iso14443_3a_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/iso14443_3a/*.c", + ], + fal_embedded=True, +) + +App( + appid="nfc_iso14443_3b", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_iso14443_3b_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/iso14443_3b/*.c", + ], + fal_embedded=True, +) + +App( + appid="nfc_iso14443_4a", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_iso14443_4a_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/iso14443_4a/*.c", + "helpers/protocol_support/iso14443_3a/*.c", + ], + fal_embedded=True, +) + +App( + appid="nfc_iso14443_4b", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_iso14443_4b_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/iso14443_4b/*.c", + "helpers/protocol_support/iso14443_3b/*.c", + ], + fal_embedded=True, +) + +App( + appid="nfc_iso15693_3", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_iso15693_3_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/iso15693_3/*.c", + ], + fal_embedded=True, +) + +App( + appid="nfc_felica", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_felica_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/felica/*.c", + "helpers/felica_*.c", + ], + fal_embedded=True, +) + +App( + appid="nfc_mf_ultralight", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_mf_ultralight_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/mf_ultralight/*.c", + "helpers/protocol_support/iso14443_3a/*.c", + "helpers/mf_ultralight_*.c", + ], + fap_libs=["mbedtls"], + fal_embedded=True, +) + +App( + appid="nfc_mf_classic", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_mf_classic_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/mf_classic/*.c", + "helpers/protocol_support/iso14443_3a/*.c", + "helpers/mf_classic_*.c", + ], + fal_embedded=True, +) + +App( + appid="nfc_mf_plus", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_mf_plus_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/mf_plus/*.c", + "helpers/protocol_support/iso14443_4a/*.c", + "helpers/protocol_support/iso14443_3a/*.c", + ], + fal_embedded=True, +) + +App( + appid="nfc_mf_desfire", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_mf_desfire_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/mf_desfire/*.c", + "helpers/protocol_support/iso14443_4a/*.c", + "helpers/protocol_support/iso14443_3a/*.c", + ], + fal_embedded=True, +) + +App( + appid="nfc_slix", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_slix_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/slix/*.c", + "helpers/protocol_support/iso15693_3/*.c", + ], + fal_embedded=True, +) + +App( + appid="nfc_st25tb", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_st25tb_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/st25tb/*.c", + ], + fal_embedded=True, +) + + +App( + appid="nfc_ntag4xx", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_ntag4xx_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/ntag4xx/*.c", + "helpers/protocol_support/iso14443_4a/*.c", + "helpers/protocol_support/iso14443_3a/*.c", + ], + fal_embedded=True, +) +App( + appid="nfc_type_4_tag", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_type_4_tag_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/type_4_tag/*.c", + "helpers/protocol_support/iso14443_4a/*.c", + "helpers/protocol_support/iso14443_3a/*.c", + ], + fal_embedded=True, +) + +App( + appid="nfc_emv", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_emv_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/emv/*.c", + ], + fal_embedded=True, +) + # Parser plugins App( @@ -29,6 +228,15 @@ App( sources=["plugins/supported_cards/all_in_one.c"], ) +App( + appid="smartrider_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="smartrider_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/smartrider.c"], +) + App( appid="microel_parser", apptype=FlipperAppType.PLUGIN, @@ -254,6 +462,7 @@ App( requires=["nfc"], sources=["plugins/supported_cards/ndef.c"], ) + App( appid="ndef_mfc_parser", apptype=FlipperAppType.PLUGIN, @@ -274,6 +483,16 @@ App( sources=["plugins/supported_cards/ndef.c"], ) +App( + appid="ndef_t4t_parser", + apptype=FlipperAppType.PLUGIN, + cdefines=[("NDEF_PROTO", "NDEF_PROTO_T4T")], + entry_point="ndef_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/ndef.c"], +) + App( appid="itso_parser", apptype=FlipperAppType.PLUGIN, @@ -320,6 +539,33 @@ App( sources=["plugins/supported_cards/disney_infinity.c"], ) +App( + appid="sonicare_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="sonicare_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/sonicare.c"], +) + +App( + appid="csc_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="csc_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/csc.c"], +) + +App( + appid="ventra_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="ventra_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/ventra.c"], +) + App( appid="cli_nfc", targets=["f7"], diff --git a/applications/main/nfc/helpers/nfc_custom_event.h b/applications/main/nfc/helpers/nfc_custom_event.h index 16fbc4749..de8cf23bb 100644 --- a/applications/main/nfc/helpers/nfc_custom_event.h +++ b/applications/main/nfc/helpers/nfc_custom_event.h @@ -13,6 +13,7 @@ typedef enum { NfcCustomEventCardLost, NfcCustomEventViewExit, + NfcCustomEventRetry, NfcCustomEventWorkerExit, NfcCustomEventWorkerUpdate, NfcCustomEventWrongCard, @@ -30,4 +31,6 @@ typedef enum { NfcCustomEventPollerFailure, NfcCustomEventListenerUpdate, + + NfcCustomEventEmulationTimeExpired, } NfcCustomEvent; diff --git a/applications/main/nfc/helpers/nfc_supported_cards.c b/applications/main/nfc/helpers/nfc_supported_cards.c index 6513eef5f..5c974f463 100644 --- a/applications/main/nfc/helpers/nfc_supported_cards.c +++ b/applications/main/nfc/helpers/nfc_supported_cards.c @@ -1,11 +1,9 @@ #include "nfc_supported_cards.h" -#include "../api/nfc_app_api_interface.h" #include "../plugins/supported_cards/nfc_supported_card_plugin.h" #include #include -#include #include #include @@ -52,12 +50,9 @@ struct NfcSupportedCards { NfcSupportedCardsLoadContext* load_context; }; -NfcSupportedCards* nfc_supported_cards_alloc(void) { +NfcSupportedCards* nfc_supported_cards_alloc(CompositeApiResolver* api_resolver) { NfcSupportedCards* instance = malloc(sizeof(NfcSupportedCards)); - - instance->api_resolver = composite_api_resolver_alloc(); - composite_api_resolver_add(instance->api_resolver, firmware_api_interface); - composite_api_resolver_add(instance->api_resolver, nfc_application_api_interface); + instance->api_resolver = api_resolver; NfcSupportedCardsPluginCache_init(instance->plugins_cache_arr); @@ -76,7 +71,6 @@ void nfc_supported_cards_free(NfcSupportedCards* instance) { } NfcSupportedCardsPluginCache_clear(instance->plugins_cache_arr); - composite_api_resolver_free(instance->api_resolver); free(instance); } diff --git a/applications/main/nfc/helpers/nfc_supported_cards.h b/applications/main/nfc/helpers/nfc_supported_cards.h index 5eaee360a..363abe30b 100644 --- a/applications/main/nfc/helpers/nfc_supported_cards.h +++ b/applications/main/nfc/helpers/nfc_supported_cards.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include #include @@ -25,7 +26,7 @@ typedef struct NfcSupportedCards NfcSupportedCards; * * @return pointer to allocated NfcSupportedCards instance. */ -NfcSupportedCards* nfc_supported_cards_alloc(void); +NfcSupportedCards* nfc_supported_cards_alloc(CompositeApiResolver* api_resolver); /** * @brief Delete an NfcSupportedCards instance diff --git a/applications/main/nfc/helpers/protocol_support/emv/emv.c b/applications/main/nfc/helpers/protocol_support/emv/emv.c index 06e2ca624..a2232b00e 100644 --- a/applications/main/nfc/helpers/protocol_support/emv/emv.c +++ b/applications/main/nfc/helpers/protocol_support/emv/emv.c @@ -128,4 +128,11 @@ const NfcProtocolSupportBase nfc_protocol_support_emv = { .on_enter = nfc_protocol_support_common_on_enter_empty, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_write = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, }; + +NFC_PROTOCOL_SUPPORT_PLUGIN(emv, NfcProtocolEmv); diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica.c b/applications/main/nfc/helpers/protocol_support/felica/felica.c index 561cd4d2e..0c4234bad 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica.c +++ b/applications/main/nfc/helpers/protocol_support/felica/felica.c @@ -133,15 +133,6 @@ static void nfc_scene_read_success_on_enter_felica(NfcApp* instance) { furi_string_free(temp_str); } -static bool nfc_scene_saved_menu_on_event_felica(NfcApp* instance, SceneManagerEvent event) { - if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) { - scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); - return true; - } - - return false; -} - static void nfc_scene_emulate_on_enter_felica(NfcApp* instance) { const FelicaData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolFelica); instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolFelica, data); @@ -201,7 +192,7 @@ const NfcProtocolSupportBase nfc_protocol_support_felica = { .scene_saved_menu = { .on_enter = nfc_protocol_support_common_on_enter_empty, - .on_event = nfc_scene_saved_menu_on_event_felica, + .on_event = nfc_protocol_support_common_on_event_empty, }, .scene_save_name = { @@ -213,4 +204,11 @@ const NfcProtocolSupportBase nfc_protocol_support_felica = { .on_enter = nfc_scene_emulate_on_enter_felica, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_write = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, }; + +NFC_PROTOCOL_SUPPORT_PLUGIN(felica, NfcProtocolFelica); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.c b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.c index 99e211301..adcaaf776 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.c +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.c @@ -67,21 +67,22 @@ static NfcCommand furi_assert(event.protocol == NfcProtocolIso14443_3a); furi_assert(event.event_data); - NfcApp* nfc = context; + NfcApp* instance = context; Iso14443_3aListenerEvent* iso14443_3a_event = event.event_data; if(iso14443_3a_event->type == Iso14443_3aListenerEventTypeReceivedStandardFrame) { - if(furi_string_size(nfc->text_box_store) < NFC_LOG_SIZE_MAX) { - furi_string_cat_printf(nfc->text_box_store, "R:"); + if(furi_string_size(instance->text_box_store) < NFC_LOG_SIZE_MAX) { + furi_string_cat_printf(instance->text_box_store, "R:"); for(size_t i = 0; i < bit_buffer_get_size_bytes(iso14443_3a_event->data->buffer); i++) { furi_string_cat_printf( - nfc->text_box_store, + instance->text_box_store, " %02X", bit_buffer_get_byte(iso14443_3a_event->data->buffer, i)); } - furi_string_push_back(nfc->text_box_store, '\n'); - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventListenerUpdate); + furi_string_push_back(instance->text_box_store, '\n'); + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventListenerUpdate); } } @@ -97,15 +98,6 @@ static void nfc_scene_emulate_on_enter_iso14443_3a(NfcApp* instance) { instance->listener, nfc_scene_emulate_listener_callback_iso14443_3a, instance); } -static bool nfc_scene_read_menu_on_event_iso14443_3a(NfcApp* instance, SceneManagerEvent event) { - if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEmulate) { - scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate); - return true; - } - - return false; -} - const NfcProtocolSupportBase nfc_protocol_support_iso14443_3a = { .features = NfcProtocolFeatureEmulateUid | NfcProtocolFeatureEditUid, @@ -122,7 +114,7 @@ const NfcProtocolSupportBase nfc_protocol_support_iso14443_3a = { .scene_read_menu = { .on_enter = nfc_protocol_support_common_on_enter_empty, - .on_event = nfc_scene_read_menu_on_event_iso14443_3a, + .on_event = nfc_protocol_support_common_on_event_empty, }, .scene_read_success = { @@ -144,4 +136,11 @@ const NfcProtocolSupportBase nfc_protocol_support_iso14443_3a = { .on_enter = nfc_scene_emulate_on_enter_iso14443_3a, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_write = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, }; + +NFC_PROTOCOL_SUPPORT_PLUGIN(iso14443_3a, NfcProtocolIso14443_3a); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.c b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.c index 43b541111..1a9931b28 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.c +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.c @@ -60,19 +60,6 @@ static void nfc_scene_read_success_on_enter_iso14443_3b(NfcApp* instance) { furi_string_free(temp_str); } -bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, SceneManagerEvent event) { - if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) { - scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); - return true; - } - - return false; -} - -static bool nfc_scene_saved_menu_on_event_iso14443_3b(NfcApp* instance, SceneManagerEvent event) { - return nfc_scene_saved_menu_on_event_iso14443_3b_common(instance, event); -} - const NfcProtocolSupportBase nfc_protocol_support_iso14443_3b = { .features = NfcProtocolFeatureNone, @@ -99,7 +86,7 @@ const NfcProtocolSupportBase nfc_protocol_support_iso14443_3b = { .scene_saved_menu = { .on_enter = nfc_protocol_support_common_on_enter_empty, - .on_event = nfc_scene_saved_menu_on_event_iso14443_3b, + .on_event = nfc_protocol_support_common_on_event_empty, }, .scene_save_name = { @@ -111,4 +98,11 @@ const NfcProtocolSupportBase nfc_protocol_support_iso14443_3b = { .on_enter = nfc_protocol_support_common_on_enter_empty, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_write = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, }; + +NFC_PROTOCOL_SUPPORT_PLUGIN(iso14443_3b, NfcProtocolIso14443_3b); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_i.h b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_i.h deleted file mode 100644 index 6c7c2a0bc..000000000 --- a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_i.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include - -#include "iso14443_3b.h" - -bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, SceneManagerEvent event); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.c b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.c index 17435ccd4..03f5b5ffd 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.c +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.c @@ -70,21 +70,22 @@ NfcCommand nfc_scene_emulate_listener_callback_iso14443_4a(NfcGenericEvent event furi_assert(event.protocol == NfcProtocolIso14443_4a); furi_assert(event.event_data); - NfcApp* nfc = context; + NfcApp* instance = context; Iso14443_4aListenerEvent* iso14443_4a_event = event.event_data; if(iso14443_4a_event->type == Iso14443_4aListenerEventTypeReceivedData) { - if(furi_string_size(nfc->text_box_store) < NFC_LOG_SIZE_MAX) { - furi_string_cat_printf(nfc->text_box_store, "R:"); + if(furi_string_size(instance->text_box_store) < NFC_LOG_SIZE_MAX) { + furi_string_cat_printf(instance->text_box_store, "R:"); for(size_t i = 0; i < bit_buffer_get_size_bytes(iso14443_4a_event->data->buffer); i++) { furi_string_cat_printf( - nfc->text_box_store, + instance->text_box_store, " %02X", bit_buffer_get_byte(iso14443_4a_event->data->buffer, i)); } - furi_string_push_back(nfc->text_box_store, '\n'); - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventListenerUpdate); + furi_string_push_back(instance->text_box_store, '\n'); + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventListenerUpdate); } } @@ -100,15 +101,6 @@ static void nfc_scene_emulate_on_enter_iso14443_4a(NfcApp* instance) { instance->listener, nfc_scene_emulate_listener_callback_iso14443_4a, instance); } -static bool nfc_scene_read_menu_on_event_iso14443_4a(NfcApp* instance, SceneManagerEvent event) { - if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEmulate) { - scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate); - return true; - } - - return false; -} - const NfcProtocolSupportBase nfc_protocol_support_iso14443_4a = { .features = NfcProtocolFeatureEmulateUid | NfcProtocolFeatureEditUid, @@ -125,7 +117,7 @@ const NfcProtocolSupportBase nfc_protocol_support_iso14443_4a = { .scene_read_menu = { .on_enter = nfc_protocol_support_common_on_enter_empty, - .on_event = nfc_scene_read_menu_on_event_iso14443_4a, + .on_event = nfc_protocol_support_common_on_event_empty, }, .scene_read_success = { @@ -147,4 +139,11 @@ const NfcProtocolSupportBase nfc_protocol_support_iso14443_4a = { .on_enter = nfc_scene_emulate_on_enter_iso14443_4a, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_write = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, }; + +NFC_PROTOCOL_SUPPORT_PLUGIN(iso14443_4a, NfcProtocolIso14443_4a); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.c b/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.c index 8038e0491..9cd3829f9 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.c +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.c @@ -7,7 +7,6 @@ #include "../nfc_protocol_support_common.h" #include "../nfc_protocol_support_gui_common.h" -#include "../iso14443_3b/iso14443_3b_i.h" static void nfc_scene_info_on_enter_iso14443_4b(NfcApp* instance) { const NfcDevice* device = instance->nfc_device; @@ -61,23 +60,6 @@ static void nfc_scene_read_success_on_enter_iso14443_4b(NfcApp* instance) { furi_string_free(temp_str); } -static void nfc_scene_saved_menu_on_enter_iso14443_4b(NfcApp* instance) { - UNUSED(instance); -} - -static bool nfc_scene_read_menu_on_event_iso14443_4b(NfcApp* instance, SceneManagerEvent event) { - if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEmulate) { - scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate); - return true; - } - - return false; -} - -static bool nfc_scene_saved_menu_on_event_iso14443_4b(NfcApp* instance, SceneManagerEvent event) { - return nfc_scene_saved_menu_on_event_iso14443_3b_common(instance, event); -} - const NfcProtocolSupportBase nfc_protocol_support_iso14443_4b = { .features = NfcProtocolFeatureNone, @@ -94,7 +76,7 @@ const NfcProtocolSupportBase nfc_protocol_support_iso14443_4b = { .scene_read_menu = { .on_enter = nfc_protocol_support_common_on_enter_empty, - .on_event = nfc_scene_read_menu_on_event_iso14443_4b, + .on_event = nfc_protocol_support_common_on_event_empty, }, .scene_read_success = { @@ -103,8 +85,8 @@ const NfcProtocolSupportBase nfc_protocol_support_iso14443_4b = { }, .scene_saved_menu = { - .on_enter = nfc_scene_saved_menu_on_enter_iso14443_4b, - .on_event = nfc_scene_saved_menu_on_event_iso14443_4b, + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, }, .scene_save_name = { @@ -116,4 +98,11 @@ const NfcProtocolSupportBase nfc_protocol_support_iso14443_4b = { .on_enter = nfc_protocol_support_common_on_enter_empty, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_write = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, }; + +NFC_PROTOCOL_SUPPORT_PLUGIN(iso14443_4b, NfcProtocolIso14443_4b); diff --git a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c index 7efd102f1..4c69fbe81 100644 --- a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c +++ b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c @@ -80,20 +80,21 @@ static NfcCommand furi_assert(event.protocol == NfcProtocolIso15693_3); furi_assert(event.event_data); - NfcApp* nfc = context; + NfcApp* instance = context; Iso15693_3ListenerEvent* iso15693_3_event = event.event_data; if(iso15693_3_event->type == Iso15693_3ListenerEventTypeCustomCommand) { - if(furi_string_size(nfc->text_box_store) < NFC_LOG_SIZE_MAX) { - furi_string_cat_printf(nfc->text_box_store, "R:"); + if(furi_string_size(instance->text_box_store) < NFC_LOG_SIZE_MAX) { + furi_string_cat_printf(instance->text_box_store, "R:"); for(size_t i = 0; i < bit_buffer_get_size_bytes(iso15693_3_event->data->buffer); i++) { furi_string_cat_printf( - nfc->text_box_store, + instance->text_box_store, " %02X", bit_buffer_get_byte(iso15693_3_event->data->buffer, i)); } - furi_string_push_back(nfc->text_box_store, '\n'); - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventListenerUpdate); + furi_string_push_back(instance->text_box_store, '\n'); + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventListenerUpdate); } } @@ -108,15 +109,6 @@ static void nfc_scene_emulate_on_enter_iso15693_3(NfcApp* instance) { instance->listener, nfc_scene_emulate_listener_callback_iso15693_3, instance); } -static bool nfc_scene_saved_menu_on_event_iso15693_3(NfcApp* instance, SceneManagerEvent event) { - if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) { - scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); - return true; - } - - return false; -} - const NfcProtocolSupportBase nfc_protocol_support_iso15693_3 = { .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureEditUid | NfcProtocolFeatureMoreInfo, @@ -149,7 +141,7 @@ const NfcProtocolSupportBase nfc_protocol_support_iso15693_3 = { .scene_saved_menu = { .on_enter = nfc_protocol_support_common_on_enter_empty, - .on_event = nfc_scene_saved_menu_on_event_iso15693_3, + .on_event = nfc_protocol_support_common_on_event_empty, }, .scene_save_name = { @@ -161,4 +153,11 @@ const NfcProtocolSupportBase nfc_protocol_support_iso15693_3 = { .on_enter = nfc_scene_emulate_on_enter_iso15693_3, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_write = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, }; + +NFC_PROTOCOL_SUPPORT_PLUGIN(iso15693_3, NfcProtocolIso15693_3); diff --git a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c index 4f8540d6e..fdf7fb35a 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c +++ b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c @@ -12,10 +12,9 @@ enum { SubmenuIndexDetectReader = SubmenuIndexCommonMax, - SubmenuIndexWrite, - SubmenuIndexUpdate, SubmenuIndexDictAttack, SubmenuIndexCrackNonces, + SubmenuIndexUpdate, }; static void nfc_scene_info_on_enter_mf_classic(NfcApp* instance) { @@ -115,6 +114,9 @@ static void nfc_scene_read_menu_on_enter_mf_classic(NfcApp* instance) { Submenu* submenu = instance->submenu; const MfClassicData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); + // Doesn't make sense to show "Write to Initial Card" right after reading + submenu_remove_item(submenu, SubmenuIndexCommonWrite); + if(!mf_classic_is_card_read(data)) { submenu_add_item( submenu, @@ -160,6 +162,8 @@ static void nfc_scene_saved_menu_on_enter_mf_classic(NfcApp* instance) { Submenu* submenu = instance->submenu; const MfClassicData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); + submenu_change_item_label(submenu, SubmenuIndexCommonWrite, "Write to Initial Card"); + if(!mf_classic_is_card_read(data)) { submenu_add_item( submenu, @@ -175,12 +179,6 @@ static void nfc_scene_saved_menu_on_enter_mf_classic(NfcApp* instance) { nfc_protocol_support_common_submenu_callback, instance); } - submenu_add_item( - submenu, - "Write to Initial Card", - SubmenuIndexWrite, - nfc_protocol_support_common_submenu_callback, - instance); submenu_add_item( submenu, @@ -215,9 +213,6 @@ static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, SceneManag scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDictAttack); } consumed = true; - } else if(event.event == SubmenuIndexCommonEdit) { - scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); - consumed = true; } else if(event.event == SubmenuIndexCrackNonces) { scene_manager_set_scene_state( instance->scene_manager, NfcSceneSaveConfirm, NfcSceneSaveConfirmStateCrackNonces); @@ -236,18 +231,15 @@ static bool nfc_scene_saved_menu_on_event_mf_classic(NfcApp* instance, SceneMana if(event.event == SubmenuIndexDetectReader) { scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDetectReader); consumed = true; - } else if(event.event == SubmenuIndexWrite) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWriteInitial); - consumed = true; - } else if(event.event == SubmenuIndexUpdate) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicUpdateInitial); - consumed = true; } else if(event.event == SubmenuIndexDictAttack) { if(!scene_manager_search_and_switch_to_previous_scene( instance->scene_manager, NfcSceneMfClassicDictAttack)) { scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDictAttack); } consumed = true; + } else if(event.event == SubmenuIndexUpdate) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicUpdateInitial); + consumed = true; } } @@ -267,8 +259,71 @@ static bool nfc_scene_save_name_on_event_mf_classic(NfcApp* instance, SceneManag return consumed; } +static NfcCommand + nfc_scene_write_poller_callback_mf_classic(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolMfClassic); + + NfcApp* instance = context; + MfClassicPollerEvent* mfc_event = event.event_data; + NfcCommand command = NfcCommandContinue; + const MfClassicData* write_data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); + + if(mfc_event->type == MfClassicPollerEventTypeCardDetected) { + furi_string_reset(instance->text_box_store); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected); + } else if(mfc_event->type == MfClassicPollerEventTypeCardLost) { + furi_string_set(instance->text_box_store, "Use the source\ncard only"); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardLost); + } else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) { + const MfClassicData* tag_data = nfc_poller_get_data(instance->poller); + if(iso14443_3a_is_equal(tag_data->iso14443_3a_data, write_data->iso14443_3a_data)) { + mfc_event->data->poller_mode.mode = MfClassicPollerModeWrite; + } else { + furi_string_set( + instance->text_box_store, "Use source card!\nTo write blanks\nuse NFC Magic app"); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWrongCard); + command = NfcCommandStop; + } + } else if(mfc_event->type == MfClassicPollerEventTypeRequestSectorTrailer) { + uint8_t sector = mfc_event->data->sec_tr_data.sector_num; + uint8_t sec_tr = mf_classic_get_sector_trailer_num_by_sector(sector); + if(mf_classic_is_block_read(write_data, sec_tr)) { + mfc_event->data->sec_tr_data.sector_trailer = write_data->block[sec_tr]; + mfc_event->data->sec_tr_data.sector_trailer_provided = true; + } else { + mfc_event->data->sec_tr_data.sector_trailer_provided = false; + } + } else if(mfc_event->type == MfClassicPollerEventTypeRequestWriteBlock) { + uint8_t block_num = mfc_event->data->write_block_data.block_num; + if(mf_classic_is_block_read(write_data, block_num)) { + mfc_event->data->write_block_data.write_block = write_data->block[block_num]; + mfc_event->data->write_block_data.write_block_provided = true; + } else { + mfc_event->data->write_block_data.write_block_provided = false; + } + } else if(mfc_event->type == MfClassicPollerEventTypeSuccess) { + furi_string_reset(instance->text_box_store); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + command = NfcCommandStop; + } else if(mfc_event->type == MfClassicPollerEventTypeFail) { + furi_string_set(instance->text_box_store, "Not all sectors\nwere written\ncorrectly"); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure); + command = NfcCommandStop; + } + + return command; +} + +static void nfc_scene_write_on_enter_mf_classic(NfcApp* instance) { + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); + nfc_poller_start(instance->poller, nfc_scene_write_poller_callback_mf_classic, instance); + furi_string_set(instance->text_box_store, "Use the source\ncard only"); +} + const NfcProtocolSupportBase nfc_protocol_support_mf_classic = { - .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo, + .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo | + NfcProtocolFeatureWrite, .scene_info = { @@ -310,4 +365,11 @@ const NfcProtocolSupportBase nfc_protocol_support_mf_classic = { .on_enter = nfc_scene_emulate_on_enter_mf_classic, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_write = + { + .on_enter = nfc_scene_write_on_enter_mf_classic, + .on_event = nfc_protocol_support_common_on_event_empty, + }, }; + +NFC_PROTOCOL_SUPPORT_PLUGIN(mf_classic, NfcProtocolMfClassic); 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 deba1bca2..785ccc140 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 @@ -124,4 +124,11 @@ const NfcProtocolSupportBase nfc_protocol_support_mf_desfire = { .on_enter = nfc_scene_emulate_on_enter_mf_desfire, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_write = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, }; + +NFC_PROTOCOL_SUPPORT_PLUGIN(mf_desfire, NfcProtocolMfDesfire); diff --git a/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.c b/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.c index c7b36e21e..2960886f7 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.c +++ b/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.c @@ -25,6 +25,20 @@ static void nfc_scene_info_on_enter_mf_plus(NfcApp* instance) { furi_string_free(temp_str); } + +static void nfc_scene_more_info_on_enter_mf_plus(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const MfPlusData* data = nfc_device_get_data(device, NfcProtocolMfPlus); + + furi_string_reset(instance->text_box_store); + nfc_render_mf_plus_data(data, instance->text_box_store); + + text_box_set_font(instance->text_box, TextBoxFontHex); + text_box_set_text(instance->text_box, furi_string_get_cstr(instance->text_box_store)); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox); +} + static NfcCommand nfc_scene_read_poller_callback_mf_plus(NfcGenericEvent event, void* context) { furi_assert(context); furi_assert(event.protocol == NfcProtocolMfPlus); @@ -78,7 +92,7 @@ static void nfc_scene_emulate_on_enter_mf_plus(NfcApp* instance) { } const NfcProtocolSupportBase nfc_protocol_support_mf_plus = { - .features = NfcProtocolFeatureEmulateUid, + .features = NfcProtocolFeatureEmulateUid | NfcProtocolFeatureMoreInfo, .scene_info = { @@ -87,7 +101,7 @@ const NfcProtocolSupportBase nfc_protocol_support_mf_plus = { }, .scene_more_info = { - .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_enter = nfc_scene_more_info_on_enter_mf_plus, .on_event = nfc_protocol_support_common_on_event_empty, }, .scene_read = @@ -120,4 +134,11 @@ const NfcProtocolSupportBase nfc_protocol_support_mf_plus = { .on_enter = nfc_scene_emulate_on_enter_mf_plus, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_write = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, }; + +NFC_PROTOCOL_SUPPORT_PLUGIN(mf_plus, NfcProtocolMfPlus); diff --git a/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus_render.c b/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus_render.c index 8640fa16d..2311004ad 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus_render.c +++ b/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus_render.c @@ -15,7 +15,21 @@ void nfc_render_mf_plus_info( } void nfc_render_mf_plus_data(const MfPlusData* data, FuriString* str) { - nfc_render_mf_plus_version(&data->version, str); + MfPlusVersion empty_version = {0}; + if(memcmp(&data->version, &empty_version, sizeof(MfPlusVersion)) == 0) { + const char* device_name = mf_plus_get_device_name(data, NfcDeviceNameTypeFull); + if(data->type == MfPlusTypeUnknown || data->size == MfPlusSizeUnknown || + data->security_level == MfPlusSecurityLevelUnknown) { + furi_string_cat_printf(str, "This %s", device_name); + furi_string_replace(str, " Unknown", ""); + } else { + furi_string_cat(str, device_name); + } + furi_string_replace(str, "Mifare", "MIFARE"); + furi_string_cat(str, " does not support the GetVersion command, extra info unavailable\n"); + } else { + nfc_render_mf_plus_version(&data->version, str); + } } void nfc_render_mf_plus_version(const MfPlusVersion* data, FuriString* str) { 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 3adf2a1f5..8eb42b89b 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 @@ -14,7 +14,6 @@ enum { SubmenuIndexUnlock = SubmenuIndexCommonMax, SubmenuIndexUnlockByReader, SubmenuIndexUnlockByPassword, - SubmenuIndexWrite, }; enum { @@ -182,24 +181,22 @@ static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instanc const MfUltralightData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight); + bool is_locked = !mf_ultralight_is_all_data_read(data); - if(!mf_ultralight_is_all_data_read(data)) { + if(is_locked || + (data->type != MfUltralightTypeNTAG213 && data->type != MfUltralightTypeNTAG215 && + data->type != MfUltralightTypeNTAG216 && data->type != MfUltralightTypeUL11 && + data->type != MfUltralightTypeUL21 && data->type != MfUltralightTypeOrigin)) { + submenu_remove_item(submenu, SubmenuIndexCommonWrite); + } + + if(is_locked) { submenu_add_item( submenu, "Unlock", SubmenuIndexUnlock, nfc_protocol_support_common_submenu_callback, instance); - } else if( - data->type == MfUltralightTypeNTAG213 || data->type == MfUltralightTypeNTAG215 || - data->type == MfUltralightTypeNTAG216 || data->type == MfUltralightTypeUL11 || - data->type == MfUltralightTypeUL21 || data->type == MfUltralightTypeOrigin) { - submenu_add_item( - submenu, - "Write", - SubmenuIndexWrite, - nfc_protocol_support_common_submenu_callback, - instance); } } @@ -252,19 +249,57 @@ static bool nfc_scene_read_and_saved_menu_on_event_mf_ultralight( NfcSceneMfUltralightUnlockMenu; scene_manager_next_scene(instance->scene_manager, next_scene); consumed = true; - } else if(event.event == SubmenuIndexWrite) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWrite); - consumed = true; - } else if(event.event == SubmenuIndexCommonEdit) { - scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); - consumed = true; } } return consumed; } +static NfcCommand + nfc_scene_write_poller_callback_mf_ultralight(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolMfUltralight); + + NfcApp* instance = context; + MfUltralightPollerEvent* mf_ultralight_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(mf_ultralight_event->type == MfUltralightPollerEventTypeRequestMode) { + mf_ultralight_event->data->poller_mode = MfUltralightPollerModeWrite; + furi_string_reset(instance->text_box_store); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected); + } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthRequest) { + mf_ultralight_event->data->auth_context.skip_auth = true; + } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeRequestWriteData) { + mf_ultralight_event->data->write_data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight); + } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeCardMismatch) { + furi_string_set(instance->text_box_store, "Card of the same\ntype should be\n presented"); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWrongCard); + command = NfcCommandStop; + } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeCardLocked) { + furi_string_set( + instance->text_box_store, "Card protected by\npassword, AUTH0\nor lock bits"); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure); + command = NfcCommandStop; + } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeWriteFail) { + command = NfcCommandStop; + } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeWriteSuccess) { + furi_string_reset(instance->text_box_store); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + command = NfcCommandStop; + } + + return command; +} + +static void nfc_scene_write_on_enter_mf_ultralight(NfcApp* instance) { + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); + nfc_poller_start(instance->poller, nfc_scene_write_poller_callback_mf_ultralight, instance); + furi_string_set(instance->text_box_store, "Apply the initial\ncard only"); +} + const NfcProtocolSupportBase nfc_protocol_support_mf_ultralight = { - .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo, + .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo | + NfcProtocolFeatureWrite, .scene_info = { @@ -306,4 +341,11 @@ const NfcProtocolSupportBase nfc_protocol_support_mf_ultralight = { .on_enter = nfc_scene_emulate_on_enter_mf_ultralight, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_write = + { + .on_enter = nfc_scene_write_on_enter_mf_ultralight, + .on_event = nfc_protocol_support_common_on_event_empty, + }, }; + +NFC_PROTOCOL_SUPPORT_PLUGIN(mf_ultralight, NfcProtocolMfUltralight); 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 fe3193385..008f9c317 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -9,9 +9,13 @@ #include "nfc/nfc_app_i.h" -#include "nfc_protocol_support_defs.h" +#include "nfc_protocol_support_base.h" #include "nfc_protocol_support_gui_common.h" +#include + +#define TAG "NfcProtocolSupport" + /** * @brief Common scene entry handler. * @@ -46,6 +50,147 @@ typedef struct { static const NfcProtocolSupportCommonSceneBase nfc_protocol_support_scenes[]; +const NfcProtocolSupportBase nfc_protocol_support_empty = { + .features = NfcProtocolFeatureNone, + + .scene_info = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_more_info = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_success = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_emulate = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; + +struct NfcProtocolSupport { + NfcProtocol protocol; + PluginManager* plugin_manager; + const NfcProtocolSupportBase* base; +}; + +const char* nfc_protocol_support_plugin_names[NfcProtocolNum] = { + [NfcProtocolIso14443_3a] = "iso14443_3a", + [NfcProtocolIso14443_3b] = "iso14443_3b", + [NfcProtocolIso14443_4a] = "iso14443_4a", + [NfcProtocolIso14443_4b] = "iso14443_4b", + [NfcProtocolIso15693_3] = "iso15693_3", + [NfcProtocolFelica] = "felica", + [NfcProtocolMfUltralight] = "mf_ultralight", + [NfcProtocolMfClassic] = "mf_classic", + [NfcProtocolMfPlus] = "mf_plus", + [NfcProtocolMfDesfire] = "mf_desfire", + [NfcProtocolSlix] = "slix", + [NfcProtocolSt25tb] = "st25tb", + [NfcProtocolNtag4xx] = "ntag4xx", + [NfcProtocolType4Tag] = "type_4_tag", + [NfcProtocolEmv] = "emv", + /* Add new protocol support plugin names here */ +}; + +void nfc_protocol_support_alloc(NfcProtocol protocol, void* context) { + furi_assert(context); + + NfcApp* instance = context; + + NfcProtocolSupport* protocol_support = malloc(sizeof(NfcProtocolSupport)); + protocol_support->protocol = protocol; + + const char* protocol_name = nfc_protocol_support_plugin_names[protocol]; + FuriString* plugin_path = + furi_string_alloc_printf(APP_ASSETS_PATH("plugins/nfc_%s.fal"), protocol_name); + FURI_LOG_D(TAG, "Loading %s", furi_string_get_cstr(plugin_path)); + + protocol_support->plugin_manager = plugin_manager_alloc( + NFC_PROTOCOL_SUPPORT_PLUGIN_APP_ID, + NFC_PROTOCOL_SUPPORT_PLUGIN_API_VERSION, + composite_api_resolver_get(instance->api_resolver)); + do { + if(plugin_manager_load_single( + protocol_support->plugin_manager, furi_string_get_cstr(plugin_path)) != + PluginManagerErrorNone) { + break; + } + const NfcProtocolSupportPlugin* plugin = + plugin_manager_get_ep(protocol_support->plugin_manager, 0); + + if(plugin->protocol != protocol) { + break; + } + + protocol_support->base = plugin->base; + } while(false); + if(!protocol_support->base) { + protocol_support->base = &nfc_protocol_support_empty; + plugin_manager_free(protocol_support->plugin_manager); + protocol_support->plugin_manager = NULL; + } + + furi_string_free(plugin_path); + + instance->protocol_support = protocol_support; +} + +void nfc_protocol_support_free(void* context) { + furi_assert(context); + + NfcApp* instance = context; + + if(instance->protocol_support->plugin_manager) { + plugin_manager_free(instance->protocol_support->plugin_manager); + } + free(instance->protocol_support); + instance->protocol_support = NULL; +} + +static const NfcProtocolSupportBase* + nfc_protocol_support_get(NfcProtocol protocol, void* context) { + furi_assert(context); + + NfcApp* instance = context; + + if(instance->protocol_support && instance->protocol_support->protocol != protocol) { + nfc_protocol_support_free(instance); + } + if(!instance->protocol_support) { + nfc_protocol_support_alloc(protocol, instance); + } + + return instance->protocol_support->base; +} + // Interface functions void nfc_protocol_support_on_enter(NfcProtocolSupportScene scene, void* context) { furi_assert(scene < NfcProtocolSupportSceneCount); @@ -74,17 +219,23 @@ void nfc_protocol_support_on_exit(NfcProtocolSupportScene scene, void* context) nfc_protocol_support_scenes[scene].on_exit(instance); } -bool nfc_protocol_support_has_feature(NfcProtocol protocol, NfcProtocolFeature feature) { - return nfc_protocol_support[protocol]->features & feature; +bool nfc_protocol_support_has_feature( + NfcProtocol protocol, + void* context, + NfcProtocolFeature feature) { + furi_assert(context); + + NfcApp* instance = context; + return nfc_protocol_support_get(protocol, instance)->features & feature; } // Common scene handlers // SceneInfo static void nfc_protocol_support_scene_info_on_enter(NfcApp* instance) { const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); - nfc_protocol_support[protocol]->scene_info.on_enter(instance); + nfc_protocol_support_get(protocol, instance)->scene_info.on_enter(instance); - if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureMoreInfo)) { + if(nfc_protocol_support_has_feature(protocol, instance, NfcProtocolFeatureMoreInfo)) { widget_add_button_element( instance->widget, GuiButtonTypeRight, @@ -124,7 +275,7 @@ static void nfc_protocol_support_scene_info_on_exit(NfcApp* instance) { // SceneMoreInfo static void nfc_protocol_support_scene_more_info_on_enter(NfcApp* instance) { const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); - nfc_protocol_support[protocol]->scene_more_info.on_enter(instance); + nfc_protocol_support_get(protocol, instance)->scene_more_info.on_enter(instance); } static bool @@ -132,7 +283,8 @@ static bool bool consumed = false; const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); - consumed = nfc_protocol_support[protocol]->scene_more_info.on_event(instance, event); + consumed = + nfc_protocol_support_get(protocol, instance)->scene_more_info.on_event(instance, event); return consumed; } @@ -157,7 +309,7 @@ static void nfc_protocol_support_scene_read_on_enter(NfcApp* instance) { //nfc_supported_cards_load_cache(instance->nfc_supported_cards); // Start poller with the appropriate callback - nfc_protocol_support[protocol]->scene_read.on_enter(instance); + nfc_protocol_support_get(protocol, instance)->scene_read.on_enter(instance); nfc_blink_read_start(instance); } @@ -186,7 +338,8 @@ static bool nfc_protocol_support_scene_read_on_event(NfcApp* instance, SceneMana } else { const NfcProtocol protocol = nfc_detected_protocols_get_selected(instance->detected_protocols); - consumed = nfc_protocol_support[protocol]->scene_read.on_event(instance, event); + consumed = nfc_protocol_support_get(protocol, instance) + ->scene_read.on_event(instance, event); } } else if(event.event == NfcCustomEventPollerFailure) { nfc_poller_stop(instance->poller); @@ -199,7 +352,8 @@ static bool nfc_protocol_support_scene_read_on_event(NfcApp* instance, SceneMana } else if(event.event == NfcCustomEventCardDetected) { const NfcProtocol protocol = nfc_detected_protocols_get_selected(instance->detected_protocols); - consumed = nfc_protocol_support[protocol]->scene_read.on_event(instance, event); + consumed = + nfc_protocol_support_get(protocol, instance)->scene_read.on_event(instance, event); } } else if(event.type == SceneManagerEventTypeBack) { nfc_poller_stop(instance->poller); @@ -241,7 +395,7 @@ static void nfc_protocol_support_scene_read_menu_on_enter(NfcApp* instance) { instance); } - if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateUid)) { + if(nfc_protocol_support_has_feature(protocol, instance, NfcProtocolFeatureEmulateUid)) { submenu_add_item( submenu, "Emulate UID", @@ -249,7 +403,7 @@ static void nfc_protocol_support_scene_read_menu_on_enter(NfcApp* instance) { nfc_protocol_support_common_submenu_callback, instance); - } else if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateFull)) { + } else if(nfc_protocol_support_has_feature(protocol, instance, NfcProtocolFeatureEmulateFull)) { submenu_add_item( submenu, "Emulate", @@ -258,7 +412,16 @@ static void nfc_protocol_support_scene_read_menu_on_enter(NfcApp* instance) { instance); } - nfc_protocol_support[protocol]->scene_read_menu.on_enter(instance); + if(nfc_protocol_support_has_feature(protocol, instance, NfcProtocolFeatureWrite)) { + submenu_add_item( + submenu, + "Write", + SubmenuIndexCommonWrite, + nfc_protocol_support_common_submenu_callback, + instance); + } + + nfc_protocol_support_get(protocol, instance)->scene_read_menu.on_enter(instance); submenu_add_item( submenu, @@ -291,9 +454,17 @@ static bool dolphin_deed(DolphinDeedNfcEmulate); scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate); consumed = true; + } else if(event.event == SubmenuIndexCommonWrite) { + dolphin_deed(DolphinDeedNfcEmulate); + scene_manager_next_scene(instance->scene_manager, NfcSceneWrite); + consumed = true; + } else if(event.event == SubmenuIndexCommonEdit) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); + consumed = true; } else { const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); - consumed = nfc_protocol_support[protocol]->scene_read_menu.on_event(instance, event); + consumed = nfc_protocol_support_get(protocol, instance) + ->scene_read_menu.on_event(instance, event); } } else if(event.type == SceneManagerEventTypeBack) { @@ -312,13 +483,17 @@ static void nfc_protocol_support_scene_read_saved_menu_on_exit(NfcApp* instance) static void nfc_protocol_support_scene_read_success_on_enter(NfcApp* instance) { Widget* widget = instance->widget; + popup_set_header(instance->popup, "Parsing", 85, 27, AlignCenter, AlignTop); + popup_set_icon(instance->popup, 12, 23, &A_Loading_24); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); + FuriString* temp_str = furi_string_alloc(); if(nfc_supported_cards_parse(instance->nfc_supported_cards, instance->nfc_device, temp_str)) { widget_add_text_scroll_element( instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); } else { const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); - nfc_protocol_support[protocol]->scene_read_success.on_enter(instance); + nfc_protocol_support_get(protocol, instance)->scene_read_success.on_enter(instance); } furi_string_free(temp_str); @@ -366,7 +541,7 @@ static void nfc_protocol_support_scene_saved_menu_on_enter(NfcApp* instance) { Submenu* submenu = instance->submenu; // Header submenu items - if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateUid)) { + if(nfc_protocol_support_has_feature(protocol, instance, NfcProtocolFeatureEmulateUid)) { submenu_add_item( submenu, "Emulate UID", @@ -374,7 +549,7 @@ static void nfc_protocol_support_scene_saved_menu_on_enter(NfcApp* instance) { nfc_protocol_support_common_submenu_callback, instance); - } else if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateFull)) { + } else if(nfc_protocol_support_has_feature(protocol, instance, NfcProtocolFeatureEmulateFull)) { submenu_add_item( submenu, "Emulate", @@ -383,7 +558,16 @@ static void nfc_protocol_support_scene_saved_menu_on_enter(NfcApp* instance) { instance); } - if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEditUid)) { + if(nfc_protocol_support_has_feature(protocol, instance, NfcProtocolFeatureWrite)) { + submenu_add_item( + submenu, + "Write", + SubmenuIndexCommonWrite, + nfc_protocol_support_common_submenu_callback, + instance); + } + + if(nfc_protocol_support_has_feature(protocol, instance, NfcProtocolFeatureEditUid)) { submenu_add_item( submenu, "Edit UID", @@ -393,7 +577,7 @@ static void nfc_protocol_support_scene_saved_menu_on_enter(NfcApp* instance) { } // Protocol-dependent menu items - nfc_protocol_support[protocol]->scene_saved_menu.on_enter(instance); + nfc_protocol_support_get(protocol, instance)->scene_saved_menu.on_enter(instance); // Trailer submenu items if(nfc_has_shadow_file(instance)) { @@ -456,12 +640,19 @@ static bool dolphin_deed(is_added ? DolphinDeedNfcAddEmulate : DolphinDeedNfcEmulate); scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate); consumed = true; + } else if(event.event == SubmenuIndexCommonWrite) { + const bool is_added = + scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSetType); + dolphin_deed(is_added ? DolphinDeedNfcAddEmulate : DolphinDeedNfcEmulate); + scene_manager_next_scene(instance->scene_manager, NfcSceneWrite); + consumed = true; } else if(event.event == SubmenuIndexCommonEdit) { scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); consumed = true; } else { const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); - consumed = nfc_protocol_support[protocol]->scene_saved_menu.on_event(instance, event); + consumed = nfc_protocol_support_get(protocol, instance) + ->scene_saved_menu.on_event(instance, event); } } else if(event.type == SceneManagerEventTypeBack) { @@ -480,8 +671,18 @@ static void nfc_protocol_support_scene_save_name_on_enter(NfcApp* instance) { bool name_is_empty = furi_string_empty(instance->file_name); if(name_is_empty) { furi_string_set(instance->file_path, NFC_APP_FOLDER); - name_generator_make_auto_basic( - instance->text_store, NFC_TEXT_STORE_SIZE, NFC_APP_FILENAME_PREFIX); + FuriString* prefix = furi_string_alloc(); + furi_string_set(prefix, nfc_device_get_name(instance->nfc_device, NfcDeviceNameTypeFull)); + furi_string_replace(prefix, "Mifare", "MF"); + furi_string_replace(prefix, " Classic", "C"); // MFC + furi_string_replace(prefix, "Desfire", "Des"); // MF Des + furi_string_replace(prefix, "Ultralight", "UL"); // MF UL + furi_string_replace(prefix, " Plus", "+"); // NTAG I2C+ + furi_string_replace(prefix, " (Unknown)", ""); + furi_string_replace_all(prefix, " ", "_"); + name_generator_make_auto( + instance->text_store, NFC_TEXT_STORE_SIZE, furi_string_get_cstr(prefix)); + furi_string_free(prefix); furi_string_set(folder_path, NFC_APP_FOLDER); } else { nfc_text_store_set(instance, "%s", furi_string_get_cstr(instance->file_name)); @@ -527,8 +728,8 @@ static bool DolphinDeedNfcSave); const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); - consumed = - nfc_protocol_support[protocol]->scene_save_name.on_event(instance, event); + consumed = nfc_protocol_support_get(protocol, instance) + ->scene_save_name.on_event(instance, event); } else { consumed = scene_manager_search_and_switch_to_previous_scene( instance->scene_manager, NfcSceneStart); @@ -570,9 +771,9 @@ 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)) { + if(nfc_protocol_support_has_feature(protocol, instance, NfcProtocolFeatureEmulateUid)) { widget_add_string_element( widget, 90, 26, AlignCenter, AlignCenter, FontPrimary, "Emulating UID"); @@ -613,7 +814,7 @@ static void nfc_protocol_support_scene_emulate_on_enter(NfcApp* instance) { furi_string_reset(instance->text_box_store); // instance->listener is allocated in the respective on_enter() handler - nfc_protocol_support[protocol]->scene_emulate.on_enter(instance); + nfc_protocol_support_get(protocol, instance)->scene_emulate.on_enter(instance); scene_manager_set_scene_state( instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidget); @@ -692,6 +893,191 @@ static void nfc_protocol_support_scene_emulate_on_exit(NfcApp* instance) { nfc_blink_stop(instance); } +// SceneWrite +/** + * @brief Current view displayed on the write scene. + * + * The emulation scene has five states, some protocols may not use all states. + * Protocol handles poller events, when scene state needs to change it should + * fill text_box_store with a short caption (when applicable) before sending + * the relevant view dispatcher event. + */ +enum { + NfcSceneWriteStateSearching, /**< Ask user to touch the card. Event: on_enter, CardLost. Needs caption. */ + NfcSceneWriteStateWriting, /**< Ask not to move while writing. Event: CardDetected. No caption. */ + NfcSceneWriteStateSuccess, /**< Card written successfully. Event: PollerSuccess. No caption. */ + NfcSceneWriteStateFailure, /**< An error is displayed. Event: PollerFailure. Needs caption. */ + NfcSceneWriteStateWrongCard, /**< Wrong card was presented. Event: WrongCard. Needs caption. */ +}; + +static void nfc_protocol_support_scene_write_popup_callback(void* context) { + NfcApp* instance = context; + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventViewExit); +} + +void nfc_protocol_support_scene_write_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + NfcApp* instance = context; + if(type == InputTypeShort && result == GuiButtonTypeLeft) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventRetry); + } +} + +static void nfc_protocol_support_scene_write_setup_view(NfcApp* instance) { + Popup* popup = instance->popup; + Widget* widget = instance->widget; + popup_reset(popup); + widget_reset(widget); + uint32_t state = scene_manager_get_scene_state(instance->scene_manager, NfcSceneWrite); + NfcView view = NfcViewPopup; + + if(state == NfcSceneWriteStateSearching) { + popup_set_header(popup, "Writing", 95, 20, AlignCenter, AlignCenter); + popup_set_text( + popup, + furi_string_get_cstr(instance->text_box_store), + 95, + 38, + AlignCenter, + AlignCenter); + popup_set_icon(popup, 0, 8, &I_NFC_manual_60x50); + } else if(state == NfcSceneWriteStateWriting) { + popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter); + popup_set_icon(popup, 12, 23, &A_Loading_24); + } else if(state == NfcSceneWriteStateSuccess) { + popup_set_header(popup, "Successfully\nwritten!", 126, 2, AlignRight, AlignTop); + popup_set_icon(popup, 0, 9, &I_DolphinSuccess_91x55); + popup_set_timeout(popup, 1500); + popup_set_context(popup, instance); + popup_set_callback(popup, nfc_protocol_support_scene_write_popup_callback); + popup_enable_timeout(popup); + } else if(state == NfcSceneWriteStateFailure) { + view = NfcViewWidget; + widget_add_string_element( + widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!"); + widget_add_string_multiline_element( + widget, + 7, + 17, + AlignLeft, + AlignTop, + FontSecondary, + furi_string_get_cstr(instance->text_box_store)); + widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Retry", + nfc_protocol_support_scene_write_widget_callback, + instance); + } else if(state == NfcSceneWriteStateWrongCard) { + view = NfcViewWidget; + widget_add_string_element(widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Wrong card!"); + widget_add_string_multiline_element( + widget, + 4, + 17, + AlignLeft, + AlignTop, + FontSecondary, + furi_string_get_cstr(instance->text_box_store)); + widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Retry", + nfc_protocol_support_scene_write_widget_callback, + instance); + } + + view_dispatcher_switch_to_view(instance->view_dispatcher, view); +} + +static void nfc_protocol_support_scene_write_on_enter(NfcApp* instance) { + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneWrite, NfcSceneWriteStateSearching); + furi_string_reset(instance->text_box_store); + + const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); + + // instance->poller is allocated in the respective on_enter() handler + nfc_protocol_support_get(protocol, instance)->scene_write.on_enter(instance); + + nfc_protocol_support_scene_write_setup_view(instance); + nfc_blink_emulate_start(instance); +} + +static bool nfc_protocol_support_scene_write_on_event(NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + uint32_t new_state = -1; + bool stop_poller = false; + + if(event.event == NfcCustomEventCardDetected) { + new_state = NfcSceneWriteStateWriting; + consumed = true; + } else if(event.event == NfcCustomEventCardLost) { + new_state = NfcSceneWriteStateSearching; + consumed = true; + } else if(event.event == NfcCustomEventPollerSuccess) { + dolphin_deed(DolphinDeedNfcSave); + notification_message(instance->notifications, &sequence_success); + new_state = NfcSceneWriteStateSuccess; + stop_poller = true; + consumed = true; + } else if(event.event == NfcCustomEventPollerFailure) { + notification_message(instance->notifications, &sequence_error); + new_state = NfcSceneWriteStateFailure; + stop_poller = true; + consumed = true; + } else if(event.event == NfcCustomEventWrongCard) { + notification_message(instance->notifications, &sequence_error); + new_state = NfcSceneWriteStateWrongCard; + stop_poller = true; + consumed = true; + } else if(event.event == NfcCustomEventViewExit) { + scene_manager_previous_scene(instance->scene_manager); + consumed = true; + } else if(event.event == NfcCustomEventRetry) { + nfc_protocol_support_scenes[NfcProtocolSupportSceneWrite].on_exit(instance); + nfc_protocol_support_scenes[NfcProtocolSupportSceneWrite].on_enter(instance); + consumed = true; + } + + if(stop_poller) { + if(instance->poller) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + instance->poller = NULL; + } + nfc_blink_stop(instance); + } + if(new_state != (uint32_t)-1) { + scene_manager_set_scene_state(instance->scene_manager, NfcSceneWrite, new_state); + nfc_protocol_support_scene_write_setup_view(instance); + } + } + + return consumed; +} + +static void nfc_protocol_support_scene_write_on_exit(NfcApp* instance) { + if(instance->poller) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + } + + // Clear view + popup_reset(instance->popup); + widget_reset(instance->widget); + furi_string_reset(instance->text_box_store); + + nfc_blink_stop(instance); +} + static void nfc_protocol_support_scene_rpc_on_enter(NfcApp* instance) { UNUSED(instance); } @@ -709,7 +1095,7 @@ static void nfc_protocol_support_scene_rpc_setup_ui_and_emulate(NfcApp* instance nfc_blink_emulate_start(instance); const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); - nfc_protocol_support[protocol]->scene_emulate.on_enter(instance); + nfc_protocol_support_get(protocol, instance)->scene_emulate.on_enter(instance); instance->rpc_state = NfcRpcStateEmulating; } @@ -807,6 +1193,12 @@ static const NfcProtocolSupportCommonSceneBase .on_event = nfc_protocol_support_scene_emulate_on_event, .on_exit = nfc_protocol_support_scene_emulate_on_exit, }, + [NfcProtocolSupportSceneWrite] = + { + .on_enter = nfc_protocol_support_scene_write_on_enter, + .on_event = nfc_protocol_support_scene_write_on_event, + .on_exit = nfc_protocol_support_scene_write_on_exit, + }, [NfcProtocolSupportSceneRpc] = { .on_enter = nfc_protocol_support_scene_rpc_on_enter, diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.h index d3efc3a41..157d00b83 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.h +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.h @@ -40,7 +40,7 @@ * * | Filename | Explanation | * |:-----------------------|:------------| - * | protocol_name.h | Interface structure declaration used in `nfc_protocol_support_defs.c`. | + * | protocol_name.h | Interface structure declaration. | * | protocol_name.c | Protocol-specific scene implemenatations and definitions. | * | protocol_name_render.h | Protocol-specific rendering (formatting) functions. Used for converting protocol data into textual descriptions. | * | protocol_name_render.c | Implementations for functions declared in `protocol_name_render.h`.| @@ -65,8 +65,13 @@ * * After completing the protocol support, it must be registered within the application in order for it to be usable. * - * In nfc_protocol_support_defs.c, include the `protocol_name.h` file and add a new entry in the `nfc_protocol_support[]` - * array under the appropriate index. + * In `protocol_name.c`, add `NFC_PROTOCOL_SUPPORT_PLUGIN(protocol_name, NfcProtocolName)` at the bottom, + * below the `NfcProtocolSupportBase` structure definition. + * + * In `application.fam`, add a new entry for the plugin, following the other examples. + * + * In nfc_protocol_support.c, add a new entry in the `nfc_protocol_support_plugin_names[]` + * array under the appropriate index with the name of the plugin (without the `nfc_` prefix). * * ## Done! * @@ -80,6 +85,10 @@ #include "nfc_protocol_support_common.h" +typedef struct NfcProtocolSupport NfcProtocolSupport; + +void nfc_protocol_support_free(void* context); + /** * @brief Abstract interface for on_enter() scene handler. * @@ -113,4 +122,7 @@ bool nfc_protocol_support_on_event( */ void nfc_protocol_support_on_exit(NfcProtocolSupportScene scene, void* context); -bool nfc_protocol_support_has_feature(NfcProtocol protocol, NfcProtocolFeature feature); +bool nfc_protocol_support_has_feature( + NfcProtocol protocol, + void* context, + NfcProtocolFeature feature); diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_base.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_base.h index eec736ca2..04648120e 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_base.h +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_base.h @@ -9,6 +9,8 @@ #include "../../nfc_app.h" #include "../../nfc_app_i.h" +#include + /** * @brief Scene entry handler. * @@ -114,4 +116,47 @@ typedef struct { * It is responsible for creating a listener and for handling its events. */ NfcProtocolSupportSceneBase scene_emulate; + + /** + * @brief Handlers for protocol-specific write scene. + * + * This scene is activated when a write operation is in progress. + * It is responsible for creating a poller, handling its events and + * displaying short captions for what is happening. + */ + NfcProtocolSupportSceneBase scene_write; } NfcProtocolSupportBase; + +/** + * @brief Unique string identifier for protocol support plugins. + */ +#define NFC_PROTOCOL_SUPPORT_PLUGIN_APP_ID "NfcProtocolSupportPlugin" + +/** + * @brief Currently supported plugin API version. + */ +#define NFC_PROTOCOL_SUPPORT_PLUGIN_API_VERSION 1 + +/** + * @brief Protocol support plugin interface. + */ +typedef struct { + NfcProtocol protocol; /**< Identifier of the protocol this plugin implements. */ + const NfcProtocolSupportBase* base; /**< Pointer to the protocol support interface. */ +} NfcProtocolSupportPlugin; + +#define NFC_PROTOCOL_SUPPORT_PLUGIN(name, protocol) \ + static const NfcProtocolSupportPlugin nfc_protocol_support_##name##_desc = { \ + protocol, \ + &nfc_protocol_support_##name, \ + }; \ + \ + static const FlipperAppPluginDescriptor plugin_descriptor_##name = { \ + .appid = NFC_PROTOCOL_SUPPORT_PLUGIN_APP_ID, \ + .ep_api_version = NFC_PROTOCOL_SUPPORT_PLUGIN_API_VERSION, \ + .entry_point = &nfc_protocol_support_##name##_desc, \ + }; \ + \ + const FlipperAppPluginDescriptor* nfc_##name##_ep(void) { \ + return &plugin_descriptor_##name; \ + } diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_common.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_common.h index 6e3214106..574c23a4c 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_common.h +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_common.h @@ -13,6 +13,7 @@ typedef enum { NfcProtocolFeatureEmulateFull = 1UL << 1, /**< Complete emulation is supported. */ NfcProtocolFeatureEditUid = 1UL << 2, /**< UID editing is supported. */ NfcProtocolFeatureMoreInfo = 1UL << 3, /**< More information is provided. */ + NfcProtocolFeatureWrite = 1UL << 4, /**< Writing to real card is supported. */ } NfcProtocolFeature; /** @@ -30,6 +31,7 @@ typedef enum { NfcProtocolSupportSceneSavedMenu, /**< Menu for the card that was loaded from file. */ NfcProtocolSupportSceneSaveName, /**< Shown when saving or renaming a file. */ NfcProtocolSupportSceneEmulate, /**< Shown when emulating a card. */ + NfcProtocolSupportSceneWrite, /**< Shown when writing to a card. */ NfcProtocolSupportSceneRpc, /**< Shown in remote-controlled (RPC) mode. */ NfcProtocolSupportSceneCount, /**< Special value equal to total scene count. Internal use. */ diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c deleted file mode 100644 index a80cd6cc0..000000000 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c +++ /dev/null @@ -1,49 +0,0 @@ -/** - * @file nfc_protocol_support_defs.c - * @brief Application-level protocol support definitions. - * - * This file is to be modified whenever support for - * a new protocol is to be added. - */ -#include "nfc_protocol_support_base.h" - -#include - -#include "iso14443_3a/iso14443_3a.h" -#include "iso14443_3b/iso14443_3b.h" -#include "iso14443_4a/iso14443_4a.h" -#include "iso14443_4b/iso14443_4b.h" -#include "iso15693_3/iso15693_3.h" -#include "felica/felica.h" -#include "mf_ultralight/mf_ultralight.h" -#include "mf_classic/mf_classic.h" -#include "mf_plus/mf_plus.h" -#include "mf_desfire/mf_desfire.h" -#include "emv/emv.h" -#include "slix/slix.h" -#include "st25tb/st25tb.h" - -/** - * @brief Array of pointers to concrete protocol support implementations. - * - * When adding support for a new protocol, add it to the end of this array - * under its respective index. - * - * @see nfc_protocol.h - */ -const NfcProtocolSupportBase* nfc_protocol_support[NfcProtocolNum] = { - [NfcProtocolIso14443_3a] = &nfc_protocol_support_iso14443_3a, - [NfcProtocolIso14443_3b] = &nfc_protocol_support_iso14443_3b, - [NfcProtocolIso14443_4a] = &nfc_protocol_support_iso14443_4a, - [NfcProtocolIso14443_4b] = &nfc_protocol_support_iso14443_4b, - [NfcProtocolIso15693_3] = &nfc_protocol_support_iso15693_3, - [NfcProtocolFelica] = &nfc_protocol_support_felica, - [NfcProtocolMfUltralight] = &nfc_protocol_support_mf_ultralight, - [NfcProtocolMfClassic] = &nfc_protocol_support_mf_classic, - [NfcProtocolMfPlus] = &nfc_protocol_support_mf_plus, - [NfcProtocolMfDesfire] = &nfc_protocol_support_mf_desfire, - [NfcProtocolSlix] = &nfc_protocol_support_slix, - [NfcProtocolSt25tb] = &nfc_protocol_support_st25tb, - [NfcProtocolEmv] = &nfc_protocol_support_emv, - /* Add new protocol support implementations here */ -}; diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.h deleted file mode 100644 index 7a9d5b637..000000000 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.h +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @file nfc_protocol_support_defs.h - * @brief Application-level protocol support declarations. - */ -#pragma once - -#include "nfc_protocol_support_base.h" - -/** - * @brief Declaraion of array of pointers to protocol support implementations. - */ -extern const NfcProtocolSupportBase* nfc_protocol_support[]; diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.c index 8c38f8475..ba309244a 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.c @@ -26,9 +26,9 @@ void nfc_protocol_support_common_byte_input_done_callback(void* context) { } void nfc_protocol_support_common_text_input_done_callback(void* context) { - NfcApp* nfc = context; + NfcApp* instance = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventTextInputDone); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventTextInputDone); } void nfc_protocol_support_common_on_enter_empty(NfcApp* instance) { diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.h index 3230f1a7e..4e4edb080 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.h +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.h @@ -15,6 +15,7 @@ enum { SubmenuIndexCommonSave, /**< Save menu option. */ SubmenuIndexCommonEmulate, /**< Emulate menu option. */ + SubmenuIndexCommonWrite, /**< Write menu option. */ SubmenuIndexCommonEdit, /**< Edit menu option. */ SubmenuIndexCommonInfo, /**< Info menu option. */ SubmenuIndexCommonRename, /**< Rename menu option. */ @@ -23,6 +24,10 @@ enum { SubmenuIndexCommonMax, /**< Special value, internal use. */ }; +#ifdef __cplusplus +extern "C" { +#endif + /** * @brief Common submenu callback. * @@ -84,3 +89,7 @@ void nfc_protocol_support_common_on_enter_empty(NfcApp* instance); * @returns always true. */ bool nfc_protocol_support_common_on_event_empty(NfcApp* instance, SceneManagerEvent event); + +#ifdef __cplusplus +} +#endif 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 index 34f52496b..9cb35971e 100644 --- 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 @@ -5,5 +5,13 @@ typedef enum { NfcSceneReadMenuStateCardFound, } NfcSceneUnlockReadState; +#ifdef __cplusplus +extern "C" { +#endif + void nfc_unlock_helper_setup_from_state(NfcApp* instance); void nfc_unlock_helper_card_detected_handler(NfcApp* instance); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/nfc/helpers/protocol_support/ntag4xx/ntag4xx.c b/applications/main/nfc/helpers/protocol_support/ntag4xx/ntag4xx.c new file mode 100644 index 000000000..1ea8951f9 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/ntag4xx/ntag4xx.c @@ -0,0 +1,140 @@ +#include "ntag4xx.h" +#include "ntag4xx_render.h" + +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" +#include "../iso14443_4a/iso14443_4a_i.h" + +static void nfc_scene_info_on_enter_ntag4xx(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Ntag4xxData* data = nfc_device_get_data(device, NfcProtocolNtag4xx); + + FuriString* temp_str = furi_string_alloc(); + nfc_append_filename_string_when_present(instance, temp_str); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_ntag4xx_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static void nfc_scene_more_info_on_enter_ntag4xx(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Ntag4xxData* data = nfc_device_get_data(device, NfcProtocolNtag4xx); + + furi_string_reset(instance->text_box_store); + nfc_render_ntag4xx_data(data, instance->text_box_store); + + text_box_set_font(instance->text_box, TextBoxFontHex); + text_box_set_text(instance->text_box, furi_string_get_cstr(instance->text_box_store)); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox); +} + +static NfcCommand nfc_scene_read_poller_callback_ntag4xx(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolNtag4xx); + + NfcCommand command = NfcCommandContinue; + + NfcApp* instance = context; + const Ntag4xxPollerEvent* ntag4xx_event = event.event_data; + + if(ntag4xx_event->type == Ntag4xxPollerEventTypeReadSuccess) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolNtag4xx, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + command = NfcCommandStop; + } else if(ntag4xx_event->type == Ntag4xxPollerEventTypeReadFailed) { + command = NfcCommandReset; + } + + return command; +} + +static void nfc_scene_read_on_enter_ntag4xx(NfcApp* instance) { + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_ntag4xx, instance); +} + +static void nfc_scene_read_success_on_enter_ntag4xx(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Ntag4xxData* data = nfc_device_get_data(device, NfcProtocolNtag4xx); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_ntag4xx_info(data, NfcProtocolFormatTypeShort, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static void nfc_scene_emulate_on_enter_ntag4xx(NfcApp* instance) { + const Iso14443_4aData* iso14443_4a_data = + nfc_device_get_data(instance->nfc_device, NfcProtocolIso14443_4a); + + instance->listener = + nfc_listener_alloc(instance->nfc, NfcProtocolIso14443_4a, iso14443_4a_data); + nfc_listener_start( + instance->listener, nfc_scene_emulate_listener_callback_iso14443_4a, instance); +} + +const NfcProtocolSupportBase nfc_protocol_support_ntag4xx = { + .features = NfcProtocolFeatureEmulateUid | NfcProtocolFeatureMoreInfo, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_ntag4xx, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_more_info = + { + .on_enter = nfc_scene_more_info_on_enter_ntag4xx, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_ntag4xx, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_ntag4xx, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_emulate = + { + .on_enter = nfc_scene_emulate_on_enter_ntag4xx, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_write = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; + +NFC_PROTOCOL_SUPPORT_PLUGIN(ntag4xx, NfcProtocolNtag4xx); diff --git a/applications/main/nfc/helpers/protocol_support/ntag4xx/ntag4xx.h b/applications/main/nfc/helpers/protocol_support/ntag4xx/ntag4xx.h new file mode 100644 index 000000000..09a8388fa --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/ntag4xx/ntag4xx.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_ntag4xx; diff --git a/applications/main/nfc/helpers/protocol_support/ntag4xx/ntag4xx_render.c b/applications/main/nfc/helpers/protocol_support/ntag4xx/ntag4xx_render.c new file mode 100644 index 000000000..0cb587726 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/ntag4xx/ntag4xx_render.c @@ -0,0 +1,110 @@ +#include "ntag4xx_render.h" + +#include "../iso14443_4a/iso14443_4a_render.h" + +void nfc_render_ntag4xx_info( + const Ntag4xxData* data, + NfcProtocolFormatType format_type, + FuriString* str) { + nfc_render_iso14443_4a_brief(ntag4xx_get_base_data(data), str); + + const Ntag4xxType type = ntag4xx_get_type_from_version(&data->version); + if(type >= Ntag4xxTypeUnknown) { + furi_string_cat(str, "Memory Size: unknown"); + } else { + size_t size_cc = 32; + size_t size_ndef = 0; + size_t size_proprietary = 0; + bool has_tagtamper = false; + switch(type) { + case Ntag4xxType413DNA: + size_ndef = 128; + size_proprietary = 0; + break; + case Ntag4xxType424DNATT: + has_tagtamper = true; + /* fall-through */ + case Ntag4xxType424DNA: + size_ndef = 256; + size_proprietary = 128; + break; + case Ntag4xxType426QDNATT: + has_tagtamper = true; + /* fall-through */ + case Ntag4xxType426QDNA: + size_ndef = 768; + size_proprietary = 128; + break; + default: + break; + } + furi_string_cat_printf( + str, "\nMemory Size: %zu bytes\n", size_cc + size_ndef + size_proprietary); + furi_string_cat_printf(str, "Usable NDEF Size: %zu bytes\n", size_ndef - sizeof(uint16_t)); + furi_string_cat_printf(str, "Capability Cont.: %zu bytes\n", size_cc); + if(size_proprietary) { + furi_string_cat_printf(str, "Proprietary File: %zu bytes\n", size_proprietary); + } + furi_string_cat_printf(str, "TagTamper: %ssupported", has_tagtamper ? "" : "not "); + } + + if(format_type != NfcProtocolFormatTypeFull) return; + + furi_string_cat(str, "\n\e#ISO14443-4 data"); + nfc_render_iso14443_4a_extra(ntag4xx_get_base_data(data), str); +} + +void nfc_render_ntag4xx_data(const Ntag4xxData* data, FuriString* str) { + nfc_render_ntag4xx_version(&data->version, str); +} + +void nfc_render_ntag4xx_version(const Ntag4xxVersion* data, FuriString* str) { + furi_string_cat_printf( + str, + "%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", + data->uid[0], + data->uid[1], + data->uid[2], + data->uid[3], + data->uid[4], + data->uid[5], + data->uid[6]); + furi_string_cat_printf( + str, + "hw %02x type %02x sub %02x\n" + " maj %02x min %02x\n" + " size %02x proto %02x\n", + data->hw_vendor, + data->hw_type, + data->hw_subtype, + data->hw_major, + data->hw_minor, + data->hw_storage, + data->hw_proto); + furi_string_cat_printf( + str, + "sw %02x type %02x sub %02x\n" + " maj %02x min %02x\n" + " size %02x proto %02x\n", + data->sw_vendor, + data->sw_type, + data->sw_subtype, + data->sw_major, + data->sw_minor, + data->sw_storage, + data->sw_proto); + furi_string_cat_printf( + str, + "batch %02x:%02x:%02x:%02x:%01x\n" + "week %d year %d\n" + "fab key %02x id %02x\n", + data->batch[0], + data->batch[1], + data->batch[2], + data->batch[3], + data->batch_extra, + data->prod_week, + data->prod_year, + (data->fab_key_4b << 1) | (data->fab_key_1b), + data->optional.fab_key_id); +} diff --git a/applications/main/nfc/helpers/protocol_support/ntag4xx/ntag4xx_render.h b/applications/main/nfc/helpers/protocol_support/ntag4xx/ntag4xx_render.h new file mode 100644 index 000000000..ca81cf4c4 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/ntag4xx/ntag4xx_render.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" + +void nfc_render_ntag4xx_info( + const Ntag4xxData* data, + NfcProtocolFormatType format_type, + FuriString* str); + +void nfc_render_ntag4xx_data(const Ntag4xxData* data, FuriString* str); + +void nfc_render_ntag4xx_version(const Ntag4xxVersion* data, FuriString* str); diff --git a/applications/main/nfc/helpers/protocol_support/slix/slix.c b/applications/main/nfc/helpers/protocol_support/slix/slix.c index 35592eaa1..32094140d 100644 --- a/applications/main/nfc/helpers/protocol_support/slix/slix.c +++ b/applications/main/nfc/helpers/protocol_support/slix/slix.c @@ -78,20 +78,21 @@ static NfcCommand nfc_scene_emulate_listener_callback_slix(NfcGenericEvent event furi_assert(event.protocol == NfcProtocolSlix); furi_assert(event.event_data); - NfcApp* nfc = context; + NfcApp* instance = context; SlixListenerEvent* slix_event = event.event_data; if(slix_event->type == SlixListenerEventTypeCustomCommand) { - if(furi_string_size(nfc->text_box_store) < NFC_LOG_SIZE_MAX) { - furi_string_cat_printf(nfc->text_box_store, "R:"); + if(furi_string_size(instance->text_box_store) < NFC_LOG_SIZE_MAX) { + furi_string_cat_printf(instance->text_box_store, "R:"); for(size_t i = 0; i < bit_buffer_get_size_bytes(slix_event->data->buffer); i++) { furi_string_cat_printf( - nfc->text_box_store, + instance->text_box_store, " %02X", bit_buffer_get_byte(slix_event->data->buffer, i)); } - furi_string_push_back(nfc->text_box_store, '\n'); - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventListenerUpdate); + furi_string_push_back(instance->text_box_store, '\n'); + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventListenerUpdate); } } @@ -105,15 +106,6 @@ static void nfc_scene_emulate_on_enter_slix(NfcApp* instance) { nfc_listener_start(instance->listener, nfc_scene_emulate_listener_callback_slix, instance); } -static bool nfc_scene_saved_menu_on_event_slix(NfcApp* instance, SceneManagerEvent event) { - if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) { - scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); - return true; - } - - return false; -} - const NfcProtocolSupportBase nfc_protocol_support_slix = { .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo, @@ -145,7 +137,7 @@ const NfcProtocolSupportBase nfc_protocol_support_slix = { .scene_saved_menu = { .on_enter = nfc_protocol_support_common_on_enter_empty, - .on_event = nfc_scene_saved_menu_on_event_slix, + .on_event = nfc_protocol_support_common_on_event_empty, }, .scene_save_name = { @@ -157,4 +149,11 @@ const NfcProtocolSupportBase nfc_protocol_support_slix = { .on_enter = nfc_scene_emulate_on_enter_slix, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_write = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, }; + +NFC_PROTOCOL_SUPPORT_PLUGIN(slix, NfcProtocolSlix); diff --git a/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c index 0305d614c..af123aee4 100644 --- a/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c +++ b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c @@ -61,15 +61,6 @@ static void nfc_scene_read_success_on_enter_st25tb(NfcApp* instance) { furi_string_free(temp_str); } -static bool nfc_scene_saved_menu_on_event_st25tb(NfcApp* instance, SceneManagerEvent event) { - if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) { - scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); - return true; - } - - return false; -} - const NfcProtocolSupportBase nfc_protocol_support_st25tb = { .features = NfcProtocolFeatureNone, @@ -96,7 +87,7 @@ const NfcProtocolSupportBase nfc_protocol_support_st25tb = { .scene_saved_menu = { .on_enter = nfc_protocol_support_common_on_enter_empty, - .on_event = nfc_scene_saved_menu_on_event_st25tb, + .on_event = nfc_protocol_support_common_on_event_empty, }, .scene_save_name = { @@ -108,4 +99,11 @@ const NfcProtocolSupportBase nfc_protocol_support_st25tb = { .on_enter = nfc_protocol_support_common_on_enter_empty, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_write = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, }; + +NFC_PROTOCOL_SUPPORT_PLUGIN(st25tb, NfcProtocolSt25tb); diff --git a/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag.c b/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag.c new file mode 100644 index 000000000..3b1fc91f6 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag.c @@ -0,0 +1,260 @@ +#include "type_4_tag.h" +#include "type_4_tag_render.h" + +#include +#include +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" + +enum { + NfcSceneMoreInfoStateASCII, + NfcSceneMoreInfoStateRawData, +}; + +static void nfc_scene_info_on_enter_type_4_tag(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Type4TagData* data = nfc_device_get_data(device, NfcProtocolType4Tag); + + FuriString* temp_str = furi_string_alloc(); + nfc_append_filename_string_when_present(instance, temp_str); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_type_4_tag_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static void nfc_scene_more_info_on_enter_type_4_tag(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Type4TagData* data = nfc_device_get_data(device, NfcProtocolType4Tag); + + furi_string_reset(instance->text_box_store); + uint32_t scene_state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMoreInfo); + + if(scene_state == NfcSceneMoreInfoStateASCII) { + if(simple_array_get_count(data->ndef_data) == 0) { + furi_string_cat_str(instance->text_box_store, "No NDEF data to show"); + } else { + pretty_format_bytes_hex_canonical( + instance->text_box_store, + TYPE_4_TAG_RENDER_BYTES_PER_LINE, + PRETTY_FORMAT_FONT_MONOSPACE, + simple_array_cget_data(data->ndef_data), + simple_array_get_count(data->ndef_data)); + } + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 48, furi_string_get_cstr(instance->text_box_store)); + widget_add_button_element( + instance->widget, + GuiButtonTypeRight, + "Raw Data", + nfc_protocol_support_common_widget_callback, + instance); + + widget_add_button_element( + instance->widget, + GuiButtonTypeLeft, + "Info", + nfc_protocol_support_common_widget_callback, + instance); + } else if(scene_state == NfcSceneMoreInfoStateRawData) { + nfc_render_type_4_tag_dump(data, instance->text_box_store); + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 48, furi_string_get_cstr(instance->text_box_store)); + + widget_add_button_element( + instance->widget, + GuiButtonTypeLeft, + "ASCII", + nfc_protocol_support_common_widget_callback, + instance); + } +} + +static bool nfc_scene_more_info_on_event_type_4_tag(NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; + + if((event.type == SceneManagerEventTypeCustom && event.event == GuiButtonTypeLeft) || + (event.type == SceneManagerEventTypeBack)) { + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneMoreInfo, NfcSceneMoreInfoStateASCII); + scene_manager_previous_scene(instance->scene_manager); + consumed = true; + } else if(event.type == SceneManagerEventTypeCustom && event.event == GuiButtonTypeRight) { + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneMoreInfo, NfcSceneMoreInfoStateRawData); + scene_manager_next_scene(instance->scene_manager, NfcSceneMoreInfo); + consumed = true; + } + return consumed; +} + +static NfcCommand nfc_scene_read_poller_callback_type_4_tag(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolType4Tag); + + NfcCommand command = NfcCommandContinue; + + NfcApp* instance = context; + const Type4TagPollerEvent* type_4_tag_event = event.event_data; + + if(type_4_tag_event->type == Type4TagPollerEventTypeReadSuccess) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolType4Tag, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + command = NfcCommandStop; + } else if(type_4_tag_event->type == Type4TagPollerEventTypeReadFailed) { + command = NfcCommandReset; + } + + return command; +} + +static void nfc_scene_read_on_enter_type_4_tag(NfcApp* instance) { + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_type_4_tag, instance); +} + +static void nfc_scene_read_success_on_enter_type_4_tag(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Type4TagData* data = nfc_device_get_data(device, NfcProtocolType4Tag); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_type_4_tag_info(data, NfcProtocolFormatTypeShort, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static NfcCommand + nfc_scene_emulate_listener_callback_type_4_tag(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolType4Tag); + + NfcApp* instance = context; + Type4TagListenerEvent* type_4_tag_event = event.event_data; + + if(type_4_tag_event->type == Type4TagListenerEventTypeCustomCommand) { + if(furi_string_size(instance->text_box_store) < NFC_LOG_SIZE_MAX) { + furi_string_cat_printf(instance->text_box_store, "R:"); + for(size_t i = 0; i < bit_buffer_get_size_bytes(type_4_tag_event->data->buffer); i++) { + furi_string_cat_printf( + instance->text_box_store, + " %02X", + bit_buffer_get_byte(type_4_tag_event->data->buffer, i)); + } + furi_string_push_back(instance->text_box_store, '\n'); + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventListenerUpdate); + } + } + + return NfcCommandContinue; +} + +static void nfc_scene_emulate_on_enter_type_4_tag(NfcApp* instance) { + const Type4TagData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolType4Tag); + + instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolType4Tag, data); + nfc_listener_start( + instance->listener, nfc_scene_emulate_listener_callback_type_4_tag, instance); +} + +static NfcCommand + nfc_scene_write_poller_callback_type_4_tag(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolType4Tag); + + NfcApp* instance = context; + Type4TagPollerEvent* type_4_tag_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(type_4_tag_event->type == Type4TagPollerEventTypeRequestMode) { + type_4_tag_event->data->poller_mode.mode = Type4TagPollerModeWrite; + type_4_tag_event->data->poller_mode.data = + nfc_device_get_data(instance->nfc_device, NfcProtocolType4Tag); + furi_string_reset(instance->text_box_store); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected); + } else if(type_4_tag_event->type == Type4TagPollerEventTypeWriteFail) { + const char* error_str = type_4_tag_event->data->error == Type4TagErrorCardLocked ? + "Card does not\nallow writing\nnew data" : + "Failed to\nwrite new data"; + furi_string_set(instance->text_box_store, error_str); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure); + command = NfcCommandStop; + } else if(type_4_tag_event->type == Type4TagPollerEventTypeWriteSuccess) { + furi_string_reset(instance->text_box_store); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + command = NfcCommandStop; + } + + return command; +} + +static void nfc_scene_write_on_enter_type_4_tag(NfcApp* instance) { + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolType4Tag); + nfc_poller_start(instance->poller, nfc_scene_write_poller_callback_type_4_tag, instance); + furi_string_set(instance->text_box_store, "Apply card\nto the back"); +} + +const NfcProtocolSupportBase nfc_protocol_support_type_4_tag = { + .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo | + NfcProtocolFeatureWrite, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_type_4_tag, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_more_info = + { + .on_enter = nfc_scene_more_info_on_enter_type_4_tag, + .on_event = nfc_scene_more_info_on_event_type_4_tag, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_type_4_tag, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_type_4_tag, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_emulate = + { + .on_enter = nfc_scene_emulate_on_enter_type_4_tag, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_write = + { + .on_enter = nfc_scene_write_on_enter_type_4_tag, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; + +NFC_PROTOCOL_SUPPORT_PLUGIN(type_4_tag, NfcProtocolType4Tag); diff --git a/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag.h b/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag.h new file mode 100644 index 000000000..e9d43a3b4 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_type_4_tag; diff --git a/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag_render.c b/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag_render.c new file mode 100644 index 000000000..2dc51a6e8 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag_render.c @@ -0,0 +1,59 @@ +#include "type_4_tag_render.h" + +#include "../iso14443_4a/iso14443_4a_render.h" + +void nfc_render_type_4_tag_info( + const Type4TagData* data, + NfcProtocolFormatType format_type, + FuriString* str) { + nfc_render_iso14443_4a_brief(type_4_tag_get_base_data(data), str); + + furi_string_cat(str, "\n:::::::::::::::[Stored NDEF]:::::::::::::::\n"); + furi_string_cat_printf(str, "Current NDEF Size: %lu", simple_array_get_count(data->ndef_data)); + + if(data->is_tag_specific) { + furi_string_cat(str, "\n::::::::::::::::::[Tag Specs]::::::::::::::::::\n"); + furi_string_cat_printf( + str, + "Card: %s\n", + furi_string_empty(data->platform_name) ? "unknown" : + furi_string_get_cstr(data->platform_name)); + furi_string_cat_printf( + str, "T4T Mapping Version: %u.%u\n", data->t4t_version.major, data->t4t_version.minor); + furi_string_cat_printf(str, "NDEF File ID: %04X\n", data->ndef_file_id); + furi_string_cat_printf(str, "Max NDEF Size: %u\n", data->ndef_max_len); + furi_string_cat_printf( + str, "APDU Sizes: R:%u W:%u\n", data->chunk_max_read, data->chunk_max_write); + furi_string_cat_printf( + str, + "Read Lock: %02X%s\n", + data->ndef_read_lock, + data->ndef_read_lock == 0 ? " (unlocked)" : ""); + furi_string_cat_printf( + str, + "Write Lock: %02X%s", + data->ndef_write_lock, + data->ndef_write_lock == 0 ? " (unlocked)" : ""); + } + + if(format_type != NfcProtocolFormatTypeFull) return; + + furi_string_cat(str, "\n\e#ISO14443-4 data"); + nfc_render_iso14443_4a_extra(type_4_tag_get_base_data(data), str); +} + +void nfc_render_type_4_tag_dump(const Type4TagData* data, FuriString* str) { + size_t ndef_len = simple_array_get_count(data->ndef_data); + if(ndef_len == 0) { + furi_string_cat_str(str, "No NDEF data to show"); + return; + } + const uint8_t* ndef_data = simple_array_cget_data(data->ndef_data); + furi_string_cat_printf(str, "\e*"); + for(size_t i = 0; i < ndef_len; i += TYPE_4_TAG_RENDER_BYTES_PER_LINE) { + const uint8_t* line_data = &ndef_data[i]; + for(size_t j = 0; j < TYPE_4_TAG_RENDER_BYTES_PER_LINE; j += 2) { + furi_string_cat_printf(str, " %02X%02X", line_data[j], line_data[j + 1]); + } + } +} diff --git a/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag_render.h b/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag_render.h new file mode 100644 index 000000000..abb45317c --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag_render.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" + +#define TYPE_4_TAG_RENDER_BYTES_PER_LINE (4U) + +void nfc_render_type_4_tag_info( + const Type4TagData* data, + NfcProtocolFormatType format_type, + FuriString* str); + +void nfc_render_type_4_tag_dump(const Type4TagData* data, FuriString* str); diff --git a/applications/main/nfc/nfc_app.c b/applications/main/nfc/nfc_app.c index c8973fb0d..5f95e4859 100644 --- a/applications/main/nfc/nfc_app.c +++ b/applications/main/nfc/nfc_app.c @@ -1,7 +1,9 @@ #include "nfc_app_i.h" +#include "api/nfc_app_api_interface.h" #include "helpers/protocol_support/nfc_protocol_support.h" #include +#include bool nfc_custom_event_callback(void* context, uint32_t event) { furi_assert(context); @@ -49,12 +51,16 @@ NfcApp* nfc_app_alloc(void) { instance->nfc = nfc_alloc(); + instance->api_resolver = composite_api_resolver_alloc(); + composite_api_resolver_add(instance->api_resolver, firmware_api_interface); + composite_api_resolver_add(instance->api_resolver, nfc_application_api_interface); + instance->detected_protocols = nfc_detected_protocols_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(); - instance->nfc_supported_cards = nfc_supported_cards_alloc(); + instance->nfc_supported_cards = nfc_supported_cards_alloc(instance->api_resolver); // Nfc device instance->nfc_device = nfc_device_alloc(); @@ -148,6 +154,9 @@ void nfc_app_free(NfcApp* instance) { slix_unlock_free(instance->slix_unlock); mf_classic_key_cache_free(instance->mfc_key_cache); nfc_supported_cards_free(instance->nfc_supported_cards); + if(instance->protocol_support) { + nfc_protocol_support_free(instance); + } // Nfc device nfc_device_free(instance->nfc_device); @@ -415,6 +424,11 @@ bool nfc_load_from_file_select(NfcApp* instance) { if(!dialog_file_browser_show( instance->dialogs, instance->file_path, instance->file_path, &browser_options)) break; + + nfc_show_loading_popup(instance, true); + nfc_supported_cards_load_cache(instance->nfc_supported_cards); + nfc_show_loading_popup(instance, false); + success = nfc_load_file(instance, instance->file_path, true); } while(!success); @@ -464,7 +478,7 @@ static bool nfc_is_hal_ready(void) { static void nfc_show_initial_scene_for_device(NfcApp* nfc) { NfcProtocol prot = nfc_device_get_protocol(nfc->nfc_device); uint32_t scene = nfc_protocol_support_has_feature( - prot, NfcProtocolFeatureEmulateFull | NfcProtocolFeatureEmulateUid) ? + prot, nfc, NfcProtocolFeatureEmulateFull | NfcProtocolFeatureEmulateUid) ? NfcSceneEmulate : NfcSceneSavedMenu; // Load plugins (parsers) in case if we are in the saved menu @@ -523,11 +537,6 @@ int32_t nfc_app(void* p) { } else { view_dispatcher_attach_to_gui( nfc->view_dispatcher, nfc->gui, ViewDispatcherTypeFullscreen); - // Load plugins (parsers) one time in case if we running app normally - nfc_show_loading_popup(nfc, true); - nfc_supported_cards_load_cache(nfc->nfc_supported_cards); - nfc_show_loading_popup(nfc, false); - // Switch to the initial scene scene_manager_next_scene(nfc->scene_manager, NfcSceneStart); } diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 78f6e8e98..4336d25e0 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -32,10 +32,12 @@ #include "helpers/mfkey32_logger.h" #include "helpers/nfc_emv_parser.h" #include "helpers/mf_classic_key_cache.h" +#include "helpers/protocol_support/nfc_protocol_support.h" #include "helpers/nfc_supported_cards.h" #include "helpers/felica_auth.h" #include "helpers/slix_unlock.h" +#include #include #include #include @@ -149,6 +151,8 @@ struct NfcApp { Mfkey32Logger* mfkey32_logger; MfUserDict* mf_user_dict; MfClassicKeyCache* mfc_key_cache; + CompositeApiResolver* api_resolver; + NfcProtocolSupport* protocol_support; NfcSupportedCards* nfc_supported_cards; NfcDevice* nfc_device; @@ -176,6 +180,10 @@ typedef enum { NfcSceneSaveConfirmStateCrackNonces, } NfcSceneSaveConfirmState; +#ifdef __cplusplus +extern "C" { +#endif + int32_t nfc_task(void* p); void nfc_text_store_set(NfcApp* nfc, const char* text, ...); @@ -213,3 +221,7 @@ void nfc_make_app_folder(NfcApp* instance); void nfc_append_filename_string_when_present(NfcApp* instance, FuriString* string); void nfc_app_run_external(NfcApp* nfc, const char* app_path); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/nfc/plugins/supported_cards/csc.c b/applications/main/nfc/plugins/supported_cards/csc.c new file mode 100644 index 000000000..a96d74007 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/csc.c @@ -0,0 +1,149 @@ +/* +* Parser for CSC Service Works Reloadable Cash Card (US) +* Date created 2024/5/26 +* Zinong Li +* Discord @torron0483 +* Github @zinongli +*/ + +#include "nfc_supported_card_plugin.h" +#include +#include +#include +#include + +#include +#include + +#define TAG "CSC" + +bool csc_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + bool parsed = false; + + do { + if(data->type != MfClassicType1k) break; // Check card type + + // Verify memory format (checksum is later) + const uint8_t refill_block_num = 2; + const uint8_t current_balance_block_num = 4; + const uint8_t current_balance_copy_block_num = 8; + + const uint8_t* current_balance_block_start_ptr = + &data->block[current_balance_block_num].data[0]; + const uint8_t* current_balance_copy_block_start_ptr = + &data->block[current_balance_copy_block_num].data[0]; + + uint32_t current_balance_and_times = + bit_lib_bytes_to_num_le(current_balance_block_start_ptr, 4); + uint32_t current_balance_and_times_copy = + bit_lib_bytes_to_num_le(current_balance_copy_block_start_ptr, 4); + + // Failed verification if balance != backup + if(current_balance_and_times != current_balance_and_times_copy) { + FURI_LOG_D(TAG, "Backup verification failed"); + break; + } + + // Even if balance = 0, e.g. new card, refilled times can't be zero + if(current_balance_and_times == 0 || current_balance_and_times_copy == 0) { + FURI_LOG_D(TAG, "Value bytes empty"); + break; + } + + // Parse data + const uint8_t card_lives_block_num = 9; + const uint8_t refill_sign_block_num = 13; + + const uint8_t* refilled_balance_block_start_ptr = &data->block[refill_block_num].data[9]; + const uint8_t* refill_times_block_start_ptr = &data->block[refill_block_num].data[5]; + const uint8_t* card_lives_block_start_ptr = &data->block[card_lives_block_num].data[0]; + const uint8_t* refill_sign_block_start_ptr = &data->block[refill_sign_block_num].data[0]; + + uint32_t refilled_balance = bit_lib_bytes_to_num_le(refilled_balance_block_start_ptr, 2); + uint32_t refilled_balance_dollar = refilled_balance / 100; + uint8_t refilled_balance_cent = refilled_balance % 100; + + uint32_t current_balance = bit_lib_bytes_to_num_le(current_balance_block_start_ptr, 2); + uint32_t current_balance_dollar = current_balance / 100; + uint8_t current_balance_cent = current_balance % 100; + + // How many times it can still be used + uint32_t card_lives = bit_lib_bytes_to_num_le(card_lives_block_start_ptr, 2); + + uint32_t refill_times = bit_lib_bytes_to_num_le(refill_times_block_start_ptr, 2); + // This is zero when you buy the card. but after refilling it, the refilling machine will leave a non-zero signature here + uint64_t refill_sign = bit_lib_bytes_to_num_le(refill_sign_block_start_ptr, 8); + + size_t uid_len = 0; + const uint8_t* uid = mf_classic_get_uid(data, &uid_len); + uint32_t card_uid = bit_lib_bytes_to_num_le(uid, 4); + + // Last byte of refill block is checksum + const uint8_t* checksum_block = data->block[refill_block_num].data; + uint8_t xor_result = 0; + for(size_t i = 0; i < 16; ++i) { + xor_result ^= checksum_block[i]; + } + + if(refill_sign == 0 && refill_times == 1) { + // New cards don't comply to checksum but refill time should be once + furi_string_printf( + parsed_data, + "\e#CSC Service Works\n" + "UID: %lu\n" + "New Card\n" + "Card Value: %lu.%02u USD\n" + "Card Usages Left: %lu", + card_uid, + refilled_balance_dollar, + refilled_balance_cent, + card_lives); + } else { + if(xor_result != 0) { + FURI_LOG_D(TAG, "Checksum failed"); + break; + } + furi_string_printf( + parsed_data, + "\e#CSC Service Works\n" + "UID: %lu\n" + "Balance: %lu.%02u USD\n" + "Last Top-up: %lu.%02u USD\n" + "Top-up Count: %lu\n" + "Card Usages Left: %lu", + card_uid, + current_balance_dollar, + current_balance_cent, + refilled_balance_dollar, + refilled_balance_cent, + refill_times, + card_lives); + } + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin csc_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = NULL, + .read = NULL, + .parse = csc_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor csc_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &csc_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* csc_plugin_ep(void) { + return &csc_plugin_descriptor; +} diff --git a/applications/main/nfc/plugins/supported_cards/emv.c b/applications/main/nfc/plugins/supported_cards/emv.c index 97b16acf8..2f5b84790 100644 --- a/applications/main/nfc/plugins/supported_cards/emv.c +++ b/applications/main/nfc/plugins/supported_cards/emv.c @@ -199,6 +199,6 @@ static const FlipperAppPluginDescriptor emv_plugin_descriptor = { }; /* Plugin entry point - must return a pointer to const descriptor */ -const FlipperAppPluginDescriptor* emv_plugin_ep() { +const FlipperAppPluginDescriptor* emv_plugin_ep(void) { return &emv_plugin_descriptor; } diff --git a/applications/main/nfc/plugins/supported_cards/kazan.c b/applications/main/nfc/plugins/supported_cards/kazan.c index 403685468..65838e25c 100644 --- a/applications/main/nfc/plugins/supported_cards/kazan.c +++ b/applications/main/nfc/plugins/supported_cards/kazan.c @@ -223,7 +223,7 @@ static bool kazan_read(Nfc* nfc, NfcDevice* device) { break; } - if(!mf_classic_is_card_read(data)) { + if(error == MfClassicErrorPartialRead) { error = mf_classic_poller_sync_read(nfc, &keys_v2, data); if(error == MfClassicErrorNotPresent) { FURI_LOG_W(TAG, "Failed to read data: keys_v1"); @@ -231,7 +231,7 @@ static bool kazan_read(Nfc* nfc, NfcDevice* device) { } } - if(!mf_classic_is_card_read(data)) { + if(error == MfClassicErrorPartialRead) { error = mf_classic_poller_sync_read(nfc, &keys_v3, data); if(error == MfClassicErrorNotPresent) { FURI_LOG_W(TAG, "Failed to read data: keys_v3"); @@ -241,7 +241,7 @@ static bool kazan_read(Nfc* nfc, NfcDevice* device) { nfc_device_set_data(device, NfcProtocolMfClassic, data); - is_read = mf_classic_is_card_read(data); + is_read = (error == MfClassicErrorNone); } while(false); mf_classic_free(data); @@ -409,6 +409,6 @@ static const FlipperAppPluginDescriptor kazan_plugin_descriptor = { }; /* Plugin entry point - must return a pointer to const descriptor */ -const FlipperAppPluginDescriptor* kazan_plugin_ep() { +const FlipperAppPluginDescriptor* kazan_plugin_ep(void) { return &kazan_plugin_descriptor; } diff --git a/applications/main/nfc/plugins/supported_cards/metromoney.c b/applications/main/nfc/plugins/supported_cards/metromoney.c index 3a3d1fe6e..089bb48bd 100644 --- a/applications/main/nfc/plugins/supported_cards/metromoney.c +++ b/applications/main/nfc/plugins/supported_cards/metromoney.c @@ -185,6 +185,6 @@ static const FlipperAppPluginDescriptor metromoney_plugin_descriptor = { }; /* Plugin entry point - must return a pointer to const descriptor */ -const FlipperAppPluginDescriptor* metromoney_plugin_ep() { +const FlipperAppPluginDescriptor* metromoney_plugin_ep(void) { return &metromoney_plugin_descriptor; } diff --git a/applications/main/nfc/plugins/supported_cards/mizip.c b/applications/main/nfc/plugins/supported_cards/mizip.c index c00fef6b5..240e6dc2f 100644 --- a/applications/main/nfc/plugins/supported_cards/mizip.c +++ b/applications/main/nfc/plugins/supported_cards/mizip.c @@ -199,7 +199,7 @@ static bool mizip_parse(const NfcDevice* device, FuriString* parsed_data) { MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, cfg.verify_sector); uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_b.data, 6); - if(key != cfg.keys[cfg.verify_sector].b) return false; + if(key != cfg.keys[cfg.verify_sector].b) break; //Get UID uint8_t uid[UID_LENGTH]; diff --git a/applications/main/nfc/plugins/supported_cards/ndef.c b/applications/main/nfc/plugins/supported_cards/ndef.c index 06982e111..86d31052e 100644 --- a/applications/main/nfc/plugins/supported_cards/ndef.c +++ b/applications/main/nfc/plugins/supported_cards/ndef.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -31,7 +32,8 @@ #define NDEF_PROTO_UL (1) #define NDEF_PROTO_MFC (2) #define NDEF_PROTO_SLIX (3) -#define NDEF_PROTO_TOTAL (4) +#define NDEF_PROTO_T4T (4) +#define NDEF_PROTO_TOTAL (5) #ifndef NDEF_PROTO #error Must specify what protocol to use with NDEF_PROTO define! @@ -40,10 +42,10 @@ #error Invalid NDEF_PROTO specified! #endif -#define NDEF_TITLE(device, parsed_data) \ - furi_string_printf( \ - parsed_data, \ - "\e#NDEF Format Data\nCard type: %s\n", \ +#define NDEF_TITLE(device, parsed_data) \ + furi_string_printf( \ + parsed_data, \ + "\e#NDEF Format Data\nCard: %s\n", \ nfc_device_get_name(device, NfcDeviceNameTypeFull)) // ---=== structures ===--- @@ -151,6 +153,11 @@ typedef struct { const uint8_t* start; size_t size; } slix; +#elif NDEF_PROTO == NDEF_PROTO_T4T + struct { + const uint8_t* data; + size_t size; + } t4t; #endif } Ndef; @@ -230,6 +237,13 @@ static bool ndef_get(Ndef* ndef, size_t pos, size_t len, void* buf) { memcpy(buf, ndef->slix.start + pos, len); return true; +#elif NDEF_PROTO == NDEF_PROTO_T4T + + // Memory space is contiguous, simply need to remap to data pointer + if(pos + len > ndef->t4t.size) return false; + memcpy(buf, ndef->t4t.data + pos, len); + return true; + #else UNUSED(ndef); @@ -1039,6 +1053,44 @@ static bool ndef_slix_parse(const NfcDevice* device, FuriString* parsed_data) { return parsed > 0; } +#elif NDEF_PROTO == NDEF_PROTO_T4T + +static bool ndef_t4t_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + const Type4TagData* data = nfc_device_get_data(device, NfcProtocolType4Tag); + size_t data_start = 0; + size_t data_size = simple_array_get_count(data->ndef_data); + + NDEF_TITLE(device, parsed_data); + + furi_string_replace(parsed_data, "Card: ", "Protocol: "); + if(data->is_tag_specific && !furi_string_empty(data->platform_name)) { + furi_string_cat_printf( + parsed_data, "Card: %s\n", furi_string_get_cstr(data->platform_name)); + } + + Ndef ndef = { + .output = parsed_data, + .t4t = + { + .data = data_size == 0 ? NULL : simple_array_cget_data(data->ndef_data), + .size = data_size, + }, + }; + size_t parsed = ndef_parse_message(&ndef, data_start, data_size - data_start, 1, false); + + if(parsed) { + furi_string_trim(parsed_data, "\n"); + furi_string_cat(parsed_data, "\n"); + } else { + furi_string_reset(parsed_data); + } + + return parsed > 0; +} + #endif // ---=== boilerplate ===--- @@ -1056,6 +1108,9 @@ static const NfcSupportedCardsPlugin ndef_plugin = { #elif NDEF_PROTO == NDEF_PROTO_SLIX .parse = ndef_slix_parse, .protocol = NfcProtocolSlix, +#elif NDEF_PROTO == NDEF_PROTO_T4T + .parse = ndef_t4t_parse, + .protocol = NfcProtocolType4Tag, #endif }; diff --git a/applications/main/nfc/plugins/supported_cards/plantain.c b/applications/main/nfc/plugins/supported_cards/plantain.c index 49bbaebe8..add7ab560 100644 --- a/applications/main/nfc/plugins/supported_cards/plantain.c +++ b/applications/main/nfc/plugins/supported_cards/plantain.c @@ -247,7 +247,7 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) { // Print card number with 4-digit groups. "3" in "3078" denotes a ticket type "3 - full ticket", will differ on discounted cards. furi_string_cat_printf(parsed_data, "Number: "); FuriString* card_number_s = furi_string_alloc(); - furi_string_cat_printf(card_number_s, "%llu", card_number); + furi_string_cat_printf(card_number_s, "%lld", card_number); FuriString* tmp_s = furi_string_alloc_set_str("9643 3078 "); for(uint8_t i = 0; i < 24; i += 4) { for(uint8_t j = 0; j < 4; j++) { diff --git a/applications/main/nfc/plugins/supported_cards/smartrider.c b/applications/main/nfc/plugins/supported_cards/smartrider.c new file mode 100644 index 000000000..073f0410d --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/smartrider.c @@ -0,0 +1,334 @@ +#include "nfc_supported_card_plugin.h" +#include +#include +#include +#include +#include + +#define MAX_TRIPS 10 +#define TAG "SmartRider" +#define MAX_BLOCKS 64 +#define MAX_DATE_ITERATIONS 366 + +static const uint8_t STANDARD_KEYS[3][6] = { + {0x20, 0x31, 0xD1, 0xE5, 0x7A, 0x3B}, + {0x4C, 0xA6, 0x02, 0x9F, 0x94, 0x73}, + {0x19, 0x19, 0x53, 0x98, 0xE3, 0x2F}}; + +typedef struct { + uint32_t timestamp; + uint16_t cost; + uint16_t transaction_number; + uint16_t journey_number; + char route[5]; + uint8_t tap_on : 1; + uint8_t block; +} __attribute__((packed)) TripData; + +typedef struct { + uint32_t balance; + uint16_t issued_days; + uint16_t expiry_days; + uint16_t purchase_cost; + uint16_t auto_load_threshold; + uint16_t auto_load_value; + char card_serial_number[11]; + uint8_t token; + TripData trips[MAX_TRIPS]; + uint8_t trip_count; +} __attribute__((packed)) SmartRiderData; + +static const char* const CONCESSION_TYPES[] = { + "Pre-issue", + "Standard Fare", + "Student", + NULL, + "Tertiary", + NULL, + "Seniors", + "Health Care", + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + "PTA Staff", + "Pensioner", + "Free Travel"}; + +static inline const char* get_concession_type(uint8_t token) { + return (token <= 0x10) ? CONCESSION_TYPES[token] : "Unknown"; +} + +static bool authenticate_and_read( + Nfc* nfc, + uint8_t sector, + const uint8_t* key, + MfClassicKeyType key_type, + MfClassicBlock* block_data) { + MfClassicKey mf_key; + memcpy(mf_key.data, key, 6); + uint8_t block = mf_classic_get_first_block_num_of_sector(sector); + + if(mf_classic_poller_sync_auth(nfc, block, &mf_key, key_type, NULL) != MfClassicErrorNone) { + FURI_LOG_D(TAG, "Authentication failed for sector %d key type %d", sector, key_type); + return false; + } + + if(mf_classic_poller_sync_read_block(nfc, block, &mf_key, key_type, block_data) != + MfClassicErrorNone) { + FURI_LOG_D(TAG, "Read failed for sector %d", sector); + return false; + } + + return true; +} + +static bool smartrider_verify(Nfc* nfc) { + furi_assert(nfc); + MfClassicBlock block_data; + + for(int i = 0; i < 3; i++) { + if(!authenticate_and_read( + nfc, + i * 6, + STANDARD_KEYS[i], + i % 2 == 0 ? MfClassicKeyTypeA : MfClassicKeyTypeB, + &block_data) || + memcmp(block_data.data, STANDARD_KEYS[i], 6) != 0) { + FURI_LOG_D(TAG, "Authentication or key mismatch for key %d", i); + return false; + } + } + + FURI_LOG_I(TAG, "SmartRider card verified"); + return true; +} + +static inline bool + parse_trip_data(const MfClassicBlock* block_data, TripData* trip, uint8_t block_number) { + trip->timestamp = bit_lib_bytes_to_num_le(block_data->data + 3, 4); + trip->tap_on = (block_data->data[7] & 0x10) == 0x10; + memcpy(trip->route, block_data->data + 8, 4); + trip->route[4] = '\0'; + trip->cost = bit_lib_bytes_to_num_le(block_data->data + 13, 2); + trip->transaction_number = bit_lib_bytes_to_num_le(block_data->data, 2); + trip->journey_number = bit_lib_bytes_to_num_le(block_data->data + 2, 2); + trip->block = block_number; + return true; +} + +static bool smartrider_read(Nfc* nfc, NfcDevice* device) { + furi_assert(nfc); + furi_assert(device); + MfClassicData* data = mf_classic_alloc(); + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + + MfClassicType type; + if(mf_classic_poller_sync_detect_type(nfc, &type) != MfClassicErrorNone || + type != MfClassicType1k) { + mf_classic_free(data); + return false; + } + data->type = type; + + MfClassicDeviceKeys keys = {.key_a_mask = 0, .key_b_mask = 0}; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + memcpy(keys.key_a[i].data, STANDARD_KEYS[i == 0 ? 0 : 1], sizeof(STANDARD_KEYS[0])); + if(i > 0) { + memcpy(keys.key_b[i].data, STANDARD_KEYS[2], sizeof(STANDARD_KEYS[0])); + FURI_BIT_SET(keys.key_b_mask, i); + } + FURI_BIT_SET(keys.key_a_mask, i); + } + + MfClassicError error = mf_classic_poller_sync_read(nfc, &keys, data); + if(error != MfClassicErrorNone) { + FURI_LOG_W(TAG, "Failed to read data"); + mf_classic_free(data); + return false; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + mf_classic_free(data); + return true; +} + +static bool is_leap_year(uint16_t year) { + return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); +} + +static void calculate_date(uint32_t timestamp, char* date_str, size_t date_str_size) { + uint32_t seconds_since_2000 = timestamp; + uint32_t days_since_2000 = seconds_since_2000 / 86400; + uint16_t year = 2000; + uint8_t month = 1; + uint16_t day = 1; + + static const uint16_t days_in_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + + while(days_since_2000 >= (is_leap_year(year) ? 366 : 365)) { + days_since_2000 -= (is_leap_year(year) ? 366 : 365); + year++; + } + + for(month = 0; month < 12; month++) { + uint16_t dim = days_in_month[month]; + if(month == 1 && is_leap_year(year)) { + dim++; + } + if(days_since_2000 < dim) { + break; + } + days_since_2000 -= dim; + } + + day = days_since_2000 + 1; + month++; // Adjust month to 1-based + + if(date_str_size > 0) { + size_t written = 0; + written += snprintf(date_str + written, date_str_size - written, "%02u", day); + if(written < date_str_size - 1) { + written += snprintf(date_str + written, date_str_size - written, "/"); + } + if(written < date_str_size - 1) { + written += snprintf(date_str + written, date_str_size - written, "%02u", month); + } + if(written < date_str_size - 1) { + written += snprintf(date_str + written, date_str_size - written, "/"); + } + if(written < date_str_size - 1) { + snprintf(date_str + written, date_str_size - written, "%02u", year % 100); + } + } else { + // If the buffer size is 0, do nothing + } +} + +static bool smartrider_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + SmartRiderData sr_data = {0}; + + if(data->type != MfClassicType1k) { + FURI_LOG_E(TAG, "Invalid card type"); + return false; + } + + const MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 0); + if(!sec_tr || memcmp(sec_tr->key_a.data, STANDARD_KEYS[0], 6) != 0) { + FURI_LOG_E(TAG, "Key verification failed for sector 0"); + return false; + } + + static const uint8_t required_blocks[] = {14, 4, 5, 1, 52, 50, 0}; + for(size_t i = 0; i < COUNT_OF(required_blocks); i++) { + if(required_blocks[i] >= MAX_BLOCKS || + !mf_classic_is_block_read(data, required_blocks[i])) { + FURI_LOG_E(TAG, "Required block %d is not read or out of range", required_blocks[i]); + return false; + } + } + + sr_data.balance = bit_lib_bytes_to_num_le(data->block[14].data + 7, 2); + sr_data.issued_days = bit_lib_bytes_to_num_le(data->block[4].data + 16, 2); + sr_data.expiry_days = bit_lib_bytes_to_num_le(data->block[4].data + 18, 2); + sr_data.auto_load_threshold = bit_lib_bytes_to_num_le(data->block[4].data + 20, 2); + sr_data.auto_load_value = bit_lib_bytes_to_num_le(data->block[4].data + 22, 2); + sr_data.token = data->block[5].data[8]; + sr_data.purchase_cost = bit_lib_bytes_to_num_le(data->block[0].data + 14, 2); + + snprintf( + sr_data.card_serial_number, + sizeof(sr_data.card_serial_number), + "%02X%02X%02X%02X%02X", + data->block[1].data[6], + data->block[1].data[7], + data->block[1].data[8], + data->block[1].data[9], + data->block[1].data[10]); + + for(uint8_t block_number = 40; block_number <= 52 && sr_data.trip_count < MAX_TRIPS; + block_number++) { + if((block_number != 43 && block_number != 47 && block_number != 51) && + mf_classic_is_block_read(data, block_number) && + parse_trip_data( + &data->block[block_number], &sr_data.trips[sr_data.trip_count], block_number)) { + sr_data.trip_count++; + } + } + + // Sort trips by timestamp (descending order) + for(uint8_t i = 0; i < sr_data.trip_count - 1; i++) { + for(uint8_t j = 0; j < sr_data.trip_count - i - 1; j++) { + if(sr_data.trips[j].timestamp < sr_data.trips[j + 1].timestamp) { + TripData temp = sr_data.trips[j]; + sr_data.trips[j] = sr_data.trips[j + 1]; + sr_data.trips[j + 1] = temp; + } + } + } + + furi_string_printf( + parsed_data, + "\e#SmartRider\nBalance: $%lu.%02lu\nConcession: %s\nSerial: %s%s\n" + "Total Cost: $%u.%02u\nAuto-Load: $%u.%02u/$%u.%02u\n\e#Tag On/Off History\n", + sr_data.balance / 100, + sr_data.balance % 100, + get_concession_type(sr_data.token), + memcmp(sr_data.card_serial_number, "00", 2) == 0 ? "SR0" : "", + memcmp(sr_data.card_serial_number, "00", 2) == 0 ? sr_data.card_serial_number + 2 : + sr_data.card_serial_number, + sr_data.purchase_cost / 100, + sr_data.purchase_cost % 100, + sr_data.auto_load_threshold / 100, + sr_data.auto_load_threshold % 100, + sr_data.auto_load_value / 100, + sr_data.auto_load_value % 100); + + for(uint8_t i = 0; i < sr_data.trip_count; i++) { + char date_str[9]; + calculate_date(sr_data.trips[i].timestamp, date_str, sizeof(date_str)); + + uint32_t cost = sr_data.trips[i].cost; + if(cost > 0) { + furi_string_cat_printf( + parsed_data, + "%s %c $%lu.%02lu %s\n", + date_str, + sr_data.trips[i].tap_on ? '+' : '-', + cost / 100, + cost % 100, + sr_data.trips[i].route); + } else { + furi_string_cat_printf( + parsed_data, + "%s %c %s\n", + date_str, + sr_data.trips[i].tap_on ? '+' : '-', + sr_data.trips[i].route); + } + } + + return true; +} +static const NfcSupportedCardsPlugin smartrider_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = smartrider_verify, + .read = smartrider_read, + .parse = smartrider_parse, +}; + +__attribute__((used)) const FlipperAppPluginDescriptor* smartrider_plugin_ep() { + static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &smartrider_plugin, + }; + return &plugin_descriptor; +} + +// made with love by jay candel <3 diff --git a/applications/main/nfc/plugins/supported_cards/sonicare.c b/applications/main/nfc/plugins/supported_cards/sonicare.c new file mode 100644 index 000000000..3e2a9b15a --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/sonicare.c @@ -0,0 +1,111 @@ +// Parser for Philips Sonicare toothbrush heads. +// Made by @Sil333033 +// Thanks to Cyrill Künzi for this research! https://kuenzi.dev/toothbrush/ + +#include "nfc_supported_card_plugin.h" + +#include +#include + +#define TAG "Sonicare" + +typedef enum { + SonicareHeadWhite, + SonicareHeadBlack, + SonicareHeadUnkown, +} SonicareHead; + +static SonicareHead sonicare_get_head_type(const MfUltralightData* data) { + // data.page[34].data got 4 bytes + // 31:32:31:34 for black (not sure) + // 31:31:31:31 for white (not sure) + // the data should be in here based on the research, but i cant find it. + // page 34 byte 0 is always 0x30 for the white brushes i have, so i guess thats white + // TODO: Get a black brush and test this + + if(data->page[34].data[0] == 0x30) { + return SonicareHeadWhite; + } else { + return SonicareHeadUnkown; + } +} + +static uint32_t sonicare_get_seconds_brushed(const MfUltralightData* data) { + uint32_t seconds_brushed = 0; + + seconds_brushed += data->page[36].data[0]; + seconds_brushed += data->page[36].data[1] << 8; + + return seconds_brushed; +} + +static bool sonicare_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + const MfUltralightData* data = nfc_device_get_data(device, NfcProtocolMfUltralight); + + bool parsed = false; + + do { + // Check for NDEF link match + const char* test = "philips.com/nfcbrushheadtap"; + // Data is a array of arrays, cast to char array and compare + if(strncmp(test, (const char*)&data->page[5].data[3], strlen(test)) != 0) { + FURI_LOG_D(TAG, "Not a Philips Sonicare head"); + break; + } + + const SonicareHead head_type = sonicare_get_head_type(data); + const uint32_t seconds_brushed = sonicare_get_seconds_brushed(data); + + FuriString* head_type_str = furi_string_alloc(); + + switch(head_type) { + case SonicareHeadWhite: + furi_string_printf(head_type_str, "White"); + break; + case SonicareHeadBlack: + furi_string_printf(head_type_str, "Black"); + break; + case SonicareHeadUnkown: + default: + furi_string_printf(head_type_str, "Unknown"); + break; + } + + furi_string_printf( + parsed_data, + "\e#Philips Sonicare head\nColor: %s\nTime brushed: %02.0f:%02.0f:%02ld\n", + furi_string_get_cstr(head_type_str), + floor(seconds_brushed / 3600), + floor((seconds_brushed / 60) % 60), + seconds_brushed % 60); + + furi_string_free(head_type_str); + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin sonicare_plugin = { + .protocol = NfcProtocolMfUltralight, + .verify = NULL, + .read = NULL, + .parse = sonicare_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor sonicare_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &sonicare_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* sonicare_plugin_ep(void) { + return &sonicare_plugin_descriptor; +} diff --git a/applications/main/nfc/plugins/supported_cards/two_cities.c b/applications/main/nfc/plugins/supported_cards/two_cities.c index dc87d3072..240c6c585 100644 --- a/applications/main/nfc/plugins/supported_cards/two_cities.c +++ b/applications/main/nfc/plugins/supported_cards/two_cities.c @@ -112,7 +112,7 @@ static bool two_cities_parse(const NfcDevice* device, FuriString* parsed_data) { // Verify key MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 4); uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6); - if(key != two_cities_4k_keys[4].a) return false; + if(key != two_cities_4k_keys[4].a) break; // ===== // PLANTAIN diff --git a/applications/main/nfc/plugins/supported_cards/ventra.c b/applications/main/nfc/plugins/supported_cards/ventra.c new file mode 100644 index 000000000..9abdc1ed7 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/ventra.c @@ -0,0 +1,292 @@ +// Parser for CTA Ventra Ultralight cards +// Made by @hazardousvoltage +// Based on my own research, with... +// Credit to https://www.lenrek.net/experiments/compass-tickets/ & MetroDroid project for underlying info +// +// This parser can decode the paper single-use and single/multi-day paper passes using Ultralight EV1 +// The plastic cards are DESFire and fully locked down, not much useful info extractable +// TODO: +// - Sort the duplicate/rare ticket types +// - Database of stop IDs for trains? Buses there's just too damn many, but you can find them here: +// https://data.cityofchicago.org/Transportation/CTA-Bus-Stops-kml/84eu-buny/about_data +// - Generalize to handle all known Cubic Nextfare Ultralight systems? Anyone wants to send me specimen dumps, hit me up on Discord. + +#include "nfc_supported_card_plugin.h" + +#include +#include +#include "datetime.h" +#include + +#define TAG "Ventra" + +DateTime ventra_exp_date = {0}, ventra_validity_date = {0}; +uint8_t ventra_high_seq = 0, ventra_cur_blk = 0, ventra_mins_active = 0; + +uint32_t time_now() { + return furi_hal_rtc_get_timestamp(); +} + +static DateTime dt_delta(DateTime dt, uint8_t delta_days) { + // returns shifted DateTime, from initial DateTime and time offset in seconds + DateTime dt_shifted = {0}; + datetime_timestamp_to_datetime( + datetime_datetime_to_timestamp(&dt) - (uint64_t)delta_days * 86400, &dt_shifted); + return dt_shifted; +} + +/* +static long dt_diff(DateTime dta, DateTime dtb) { + // returns difference in seconds between two DateTimes + long diff; + diff = datetime_datetime_to_timestamp(&dta) - datetime_datetime_to_timestamp(&dtb); + return diff; +} +*/ + +// Card is expired if: +// - Hard expiration date passed (90 days from purchase, encoded in product record) +// - Soft expiration date passed: +// - For passes, n days after first use +// - For tickets, 2 hours after first use +// Calculating these is dumber than it needs to be, see xact record parser. +bool isExpired(void) { + uint32_t ts_hard_exp = datetime_datetime_to_timestamp(&ventra_exp_date); + uint32_t ts_soft_exp = datetime_datetime_to_timestamp(&ventra_validity_date); + uint32_t ts_now = time_now(); + return (ts_now >= ts_hard_exp || ts_now > ts_soft_exp); +} + +static FuriString* ventra_parse_xact(const MfUltralightData* data, uint8_t blk, bool is_pass) { + FuriString* ventra_xact_str = furi_string_alloc(); + uint16_t ts = data->page[blk].data[0] | data->page[blk].data[1] << 8; + uint8_t tran_type = ts & 0x1F; + ts >>= 5; + uint8_t day = data->page[blk].data[2]; + uint32_t work = data->page[blk + 1].data[0] | data->page[blk + 1].data[1] << 8 | + data->page[blk + 1].data[2] << 16; + uint8_t seq = work & 0x7F; + uint16_t exp = (work >> 7) & 0x7FF; + uint8_t exp_day = data->page[blk + 2].data[0]; + uint16_t locus = data->page[blk + 2].data[1] | data->page[blk + 2].data[2] << 8; + uint8_t line = data->page[blk + 2].data[3]; + + // This computes the block timestamp, based on the card expiration date and delta from it + DateTime dt = dt_delta(ventra_exp_date, day); + dt.hour = (ts & 0x7FF) / 60; + dt.minute = (ts & 0x7FF) % 60; + + // If sequence is 0, block isn't used yet (new card with only one active block, typically the first one. + // Otherwise, the block with higher sequence is the latest transaction, and the other block is prior transaction. + // Not necessarily in that order on the card. We need the latest data to compute validity and pretty-print them + // in reverse chrono. So this mess sets some globals as to which block is current, computes the validity times, etc. + if(seq == 0) { + furi_string_printf(ventra_xact_str, "-- EMPTY --"); + return (ventra_xact_str); + } + if(seq > ventra_high_seq) { + ventra_high_seq = seq; + ventra_cur_blk = blk; + ventra_mins_active = data->page[blk + 1].data[3]; + // Figure out the soft expiration. For passes it's easy, the readers update the "exp" field in the transaction record. + // Tickets, not so much, readers don't update "exp", but each xact record has "minutes since last tap" which is + // updated and carried forward. That, plus transaction timestamp, gives the expiration time. + if(tran_type == 6) { // Furthermore, purchase transactions set bogus expiration dates + if(is_pass) { + ventra_validity_date = dt_delta(ventra_exp_date, exp_day); + ventra_validity_date.hour = (exp & 0x7FF) / 60; + ventra_validity_date.minute = (exp & 0x7FF) % 60; + } else { + uint32_t validity_ts = datetime_datetime_to_timestamp(&dt); + validity_ts += (120 - ventra_mins_active) * 60; + datetime_timestamp_to_datetime(validity_ts, &ventra_validity_date); + } + } + } + + // Type 0 = Purchase, 1 = Train ride, 2 = Bus ride + // TODO: Check PACE and see if it uses a different line code + char linemap[3] = "PTB"; + char* xact_fmt = (line == 2) ? "%c %5d %04d-%02d-%02d %02d:%02d" : + "%c %04X %04d-%02d-%02d %02d:%02d"; + // I like a nice concise display showing all the relevant infos without having to scroll... + // Line StopID DateTime + furi_string_printf( + ventra_xact_str, + xact_fmt, + (line < 3) ? linemap[line] : '?', + locus, + dt.year, + dt.month, + dt.day, + dt.hour, + dt.minute); + return (ventra_xact_str); +} + +static bool ventra_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + const MfUltralightData* data = nfc_device_get_data(device, NfcProtocolMfUltralight); + + bool parsed = false; + + do { + // This test can probably be improved -- it matches every Ventra I've seen, but will also match others + // in the same family. Or maybe we just generalize this parser. + if(data->page[4].data[0] != 0x0A || data->page[4].data[1] != 4 || + data->page[4].data[2] != 0 || data->page[6].data[0] != 0 || + data->page[6].data[1] != 0 || data->page[6].data[2] != 0) { + FURI_LOG_D(TAG, "Not Ventra Ultralight"); + break; + } + + // Parse the product record, display interesting data & extract info needed to parse transaction blocks + // Had this in its own function, ended up just setting a bunch of shitty globals, so inlined it instead. + FuriString* ventra_prod_str = furi_string_alloc(); + uint8_t otp = data->page[3].data[0]; + uint8_t prod_code = data->page[5].data[2]; + bool is_pass = false; + switch(prod_code) { + case 2: + case 0x1F: // Only ever seen one of these, it parses like a Single + furi_string_cat_printf(ventra_prod_str, "Single"); + break; + case 3: + case 0x3F: + is_pass = true; + furi_string_cat_printf(ventra_prod_str, "1-Day"); + break; + case 4: // Last I checked, 3 day passes only available at airport TVMs & social service agencies + is_pass = true; + furi_string_cat_printf(ventra_prod_str, "3-Day"); + break; + default: + is_pass = + true; // There are some card types I don't know what they are, but they parse like a pass, not a ticket. + furi_string_cat_printf(ventra_prod_str, "0x%02X", data->page[5].data[2]); + break; + } + + uint16_t date_y = data->page[4].data[3] | (data->page[5].data[0] << 8); + uint8_t date_d = date_y & 0x1F; + uint8_t date_m = (date_y >> 5) & 0x0F; + date_y >>= 9; + date_y += 2000; + ventra_exp_date.day = date_d; + ventra_exp_date.month = date_m; + ventra_exp_date.year = date_y; + ventra_validity_date = ventra_exp_date; // Until we know otherwise + + // Parse the transaction blocks. This sets a few sloppy globals, but it's too complex and repetitive to inline. + FuriString* ventra_xact_str1 = ventra_parse_xact(data, 8, is_pass); + FuriString* ventra_xact_str2 = ventra_parse_xact(data, 12, is_pass); + + uint8_t card_state = 1; + uint8_t rides_left = 0; + + char* card_states[5] = {"???", "NEW", "ACT", "USED", "EXP"}; + + if(ventra_high_seq > 1) card_state = 2; + // On "ticket" product, the OTP bits mark off rides used. Bit 0 seems to be unused, the next 3 are set as rides are used. + // Some, not all, readers will set the high bits to 0x7 when a card is tapped after it's expired or depleted. Have not + // seen other combinations, but if we do, we'll make a nice ???. 1-day passes set the OTP bit 1 on first use. 3-day + // passes do not. But we don't really care, since they don't matter on passes, unless you're trying to rollback one. + if(!is_pass) { + switch(otp) { + case 0: + rides_left = 3; + break; + case 2: + card_state = 2; + rides_left = 2; + break; + case 6: + card_state = 2; + rides_left = 1; + break; + case 0x0E: + case 0x7E: + card_state = 3; + rides_left = 0; + break; + default: + card_state = 0; + rides_left = 0; + break; + } + } + if(isExpired()) { + card_state = 4; + rides_left = 0; + } + + furi_string_printf( + parsed_data, + "\e#Ventra %s (%s)\n", + furi_string_get_cstr(ventra_prod_str), + card_states[card_state]); + + furi_string_cat_printf( + parsed_data, + "Exp: %04d-%02d-%02d %02d:%02d\n", + ventra_validity_date.year, + ventra_validity_date.month, + ventra_validity_date.day, + ventra_validity_date.hour, + ventra_validity_date.minute); + + if(rides_left) { + furi_string_cat_printf(parsed_data, "Rides left: %d\n", rides_left); + } + + furi_string_cat_printf( + parsed_data, + "%s\n", + furi_string_get_cstr(ventra_cur_blk == 8 ? ventra_xact_str1 : ventra_xact_str2)); + + furi_string_cat_printf( + parsed_data, + "%s\n", + furi_string_get_cstr(ventra_cur_blk == 8 ? ventra_xact_str2 : ventra_xact_str1)); + + furi_string_cat_printf( + parsed_data, "TVM ID: %02X%02X\n", data->page[7].data[1], data->page[7].data[0]); + furi_string_cat_printf(parsed_data, "Tx count: %d\n", ventra_high_seq); + furi_string_cat_printf( + parsed_data, + "Hard Expiry: %04d-%02d-%02d", + ventra_exp_date.year, + ventra_exp_date.month, + ventra_exp_date.day); + + furi_string_free(ventra_prod_str); + furi_string_free(ventra_xact_str1); + furi_string_free(ventra_xact_str2); + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin ventra_plugin = { + .protocol = NfcProtocolMfUltralight, + .verify = NULL, + .read = NULL, + .parse = ventra_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor ventra_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &ventra_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* ventra_plugin_ep(void) { + return &ventra_plugin_descriptor; +} diff --git a/applications/main/nfc/plugins/supported_cards/zolotaya_korona.c b/applications/main/nfc/plugins/supported_cards/zolotaya_korona.c index cb0254b14..8ba36605b 100644 --- a/applications/main/nfc/plugins/supported_cards/zolotaya_korona.c +++ b/applications/main/nfc/plugins/supported_cards/zolotaya_korona.c @@ -209,6 +209,6 @@ static const FlipperAppPluginDescriptor zolotaya_korona_plugin_descriptor = { }; /* Plugin entry point - must return a pointer to const descriptor */ -const FlipperAppPluginDescriptor* zolotaya_korona_plugin_ep() { +const FlipperAppPluginDescriptor* zolotaya_korona_plugin_ep(void) { return &zolotaya_korona_plugin_descriptor; } diff --git a/applications/main/nfc/plugins/supported_cards/zolotaya_korona_online.c b/applications/main/nfc/plugins/supported_cards/zolotaya_korona_online.c index f2eccd7c0..0e39b8a58 100644 --- a/applications/main/nfc/plugins/supported_cards/zolotaya_korona_online.c +++ b/applications/main/nfc/plugins/supported_cards/zolotaya_korona_online.c @@ -139,6 +139,6 @@ static const FlipperAppPluginDescriptor zolotaya_korona_online_plugin_descriptor }; /* Plugin entry point - must return a pointer to const descriptor */ -const FlipperAppPluginDescriptor* zolotaya_korona_online_plugin_ep() { +const FlipperAppPluginDescriptor* zolotaya_korona_online_plugin_ep(void) { return &zolotaya_korona_online_plugin_descriptor; } diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index 55f8aa3be..b902bc940 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -18,6 +18,7 @@ ADD_SCENE(nfc, extra_actions, ExtraActions) ADD_SCENE(nfc, read_success, ReadSuccess) ADD_SCENE(nfc, read_menu, ReadMenu) ADD_SCENE(nfc, emulate, Emulate) +ADD_SCENE(nfc, write, Write) ADD_SCENE(nfc, rpc, Rpc) ADD_SCENE(nfc, debug, Debug) ADD_SCENE(nfc, field, Field) @@ -25,10 +26,6 @@ ADD_SCENE(nfc, retry_confirm, RetryConfirm) ADD_SCENE(nfc, exit_confirm, ExitConfirm) ADD_SCENE(nfc, save_confirm, SaveConfirm) -ADD_SCENE(nfc, mf_ultralight_write, MfUltralightWrite) -ADD_SCENE(nfc, mf_ultralight_write_success, MfUltralightWriteSuccess) -ADD_SCENE(nfc, mf_ultralight_write_fail, MfUltralightWriteFail) -ADD_SCENE(nfc, mf_ultralight_wrong_card, MfUltralightWrongCard) ADD_SCENE(nfc, mf_ultralight_unlock_menu, MfUltralightUnlockMenu) ADD_SCENE(nfc, mf_ultralight_unlock_warn, MfUltralightUnlockWarn) ADD_SCENE(nfc, mf_ultralight_key_input, MfUltralightKeyInput) @@ -48,10 +45,6 @@ ADD_SCENE(nfc, mf_classic_mfkey_complete, MfClassicMfkeyComplete) ADD_SCENE(nfc, mf_classic_update_initial, MfClassicUpdateInitial) ADD_SCENE(nfc, mf_classic_update_initial_success, MfClassicUpdateInitialSuccess) ADD_SCENE(nfc, mf_classic_update_initial_wrong_card, MfClassicUpdateInitialWrongCard) -ADD_SCENE(nfc, mf_classic_write_initial, MfClassicWriteInitial) -ADD_SCENE(nfc, mf_classic_write_initial_success, MfClassicWriteInitialSuccess) -ADD_SCENE(nfc, mf_classic_write_initial_fail, MfClassicWriteInitialFail) -ADD_SCENE(nfc, mf_classic_write_initial_wrong_card, MfClassicWriteInitialWrongCard) ADD_SCENE(nfc, mf_classic_keys, MfClassicKeys) ADD_SCENE(nfc, mf_classic_keys_list, MfClassicKeysList) diff --git a/applications/main/nfc/scenes/nfc_scene_detect.c b/applications/main/nfc/scenes/nfc_scene_detect.c index 7ef3f9d87..a824c990d 100644 --- a/applications/main/nfc/scenes/nfc_scene_detect.c +++ b/applications/main/nfc/scenes/nfc_scene_detect.c @@ -16,6 +16,10 @@ void nfc_scene_detect_scan_callback(NfcScannerEvent event, void* context) { void nfc_scene_detect_on_enter(void* context) { NfcApp* instance = context; + nfc_show_loading_popup(instance, true); + nfc_supported_cards_load_cache(instance->nfc_supported_cards); + nfc_show_loading_popup(instance, false); + // Setup view popup_reset(instance->popup); popup_set_header(instance->popup, "Reading", 97, 15, AlignCenter, AlignTop); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index 024bc5c1e..500dd759a 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -236,7 +236,6 @@ void nfc_scene_mf_classic_dict_attack_on_enter(void* context) { dict_attack_set_card_state(instance->dict_attack, true); view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewDictAttack); nfc_blink_read_start(instance); - notification_message(instance->notifications, &sequence_display_backlight_enforce_on); instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); @@ -392,5 +391,4 @@ void nfc_scene_mf_classic_dict_attack_on_exit(void* context) { } nfc_blink_stop(instance); - notification_message(instance->notifications, &sequence_display_backlight_enforce_auto); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_wrong_card.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_wrong_card.c index f3aefb781..ac9e7a192 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_wrong_card.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_wrong_card.c @@ -47,6 +47,9 @@ bool nfc_scene_mf_classic_update_initial_wrong_card_on_event( if(event.event == GuiButtonTypeLeft) { consumed = scene_manager_previous_scene(instance->scene_manager); } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneSavedMenu); } return consumed; } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial.c deleted file mode 100644 index 12e7ba1ec..000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial.c +++ /dev/null @@ -1,148 +0,0 @@ -#include "../nfc_app_i.h" - -#include - -enum { - NfcSceneMfClassicWriteInitialStateCardSearch, - NfcSceneMfClassicWriteInitialStateCardFound, -}; - -NfcCommand - nfc_scene_mf_classic_write_initial_worker_callback(NfcGenericEvent event, void* context) { - furi_assert(context); - furi_assert(event.event_data); - furi_assert(event.protocol == NfcProtocolMfClassic); - - NfcCommand command = NfcCommandContinue; - NfcApp* instance = context; - MfClassicPollerEvent* mfc_event = event.event_data; - const MfClassicData* write_data = - nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); - - if(mfc_event->type == MfClassicPollerEventTypeCardDetected) { - view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected); - } else if(mfc_event->type == MfClassicPollerEventTypeCardLost) { - view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardLost); - } else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) { - const MfClassicData* tag_data = nfc_poller_get_data(instance->poller); - if(iso14443_3a_is_equal(tag_data->iso14443_3a_data, write_data->iso14443_3a_data)) { - mfc_event->data->poller_mode.mode = MfClassicPollerModeWrite; - } else { - view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWrongCard); - command = NfcCommandStop; - } - } else if(mfc_event->type == MfClassicPollerEventTypeRequestSectorTrailer) { - uint8_t sector = mfc_event->data->sec_tr_data.sector_num; - uint8_t sec_tr = mf_classic_get_sector_trailer_num_by_sector(sector); - if(mf_classic_is_block_read(write_data, sec_tr)) { - mfc_event->data->sec_tr_data.sector_trailer = write_data->block[sec_tr]; - mfc_event->data->sec_tr_data.sector_trailer_provided = true; - } else { - mfc_event->data->sec_tr_data.sector_trailer_provided = false; - } - } else if(mfc_event->type == MfClassicPollerEventTypeRequestWriteBlock) { - uint8_t block_num = mfc_event->data->write_block_data.block_num; - if(mf_classic_is_block_read(write_data, block_num)) { - mfc_event->data->write_block_data.write_block = write_data->block[block_num]; - mfc_event->data->write_block_data.write_block_provided = true; - } else { - mfc_event->data->write_block_data.write_block_provided = false; - } - } else if(mfc_event->type == MfClassicPollerEventTypeSuccess) { - view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); - command = NfcCommandStop; - } else if(mfc_event->type == MfClassicPollerEventTypeFail) { - view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure); - command = NfcCommandStop; - } - return command; -} - -static void nfc_scene_mf_classic_write_initial_setup_view(NfcApp* instance) { - Popup* popup = instance->popup; - popup_reset(popup); - uint32_t state = - scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicWriteInitial); - - if(state == NfcSceneMfClassicWriteInitialStateCardSearch) { - popup_set_header(instance->popup, "Writing", 95, 20, AlignCenter, AlignCenter); - popup_set_text( - instance->popup, "Use the source\ncard only", 95, 38, AlignCenter, AlignCenter); - popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); - } else { - popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter); - popup_set_icon(popup, 12, 23, &A_Loading_24); - } - - view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); -} - -void nfc_scene_mf_classic_write_initial_on_enter(void* context) { - NfcApp* instance = context; - dolphin_deed(DolphinDeedNfcEmulate); - - scene_manager_set_scene_state( - instance->scene_manager, - NfcSceneMfClassicWriteInitial, - NfcSceneMfClassicWriteInitialStateCardSearch); - nfc_scene_mf_classic_write_initial_setup_view(instance); - - // Setup and start worker - instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); - nfc_poller_start( - instance->poller, nfc_scene_mf_classic_write_initial_worker_callback, instance); - - nfc_blink_emulate_start(instance); -} - -bool nfc_scene_mf_classic_write_initial_on_event(void* context, SceneManagerEvent event) { - NfcApp* instance = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventCardDetected) { - scene_manager_set_scene_state( - instance->scene_manager, - NfcSceneMfClassicWriteInitial, - NfcSceneMfClassicWriteInitialStateCardFound); - nfc_scene_mf_classic_write_initial_setup_view(instance); - consumed = true; - } else if(event.event == NfcCustomEventCardLost) { - scene_manager_set_scene_state( - instance->scene_manager, - NfcSceneMfClassicWriteInitial, - NfcSceneMfClassicWriteInitialStateCardSearch); - nfc_scene_mf_classic_write_initial_setup_view(instance); - consumed = true; - } else if(event.event == NfcCustomEventWrongCard) { - scene_manager_next_scene( - instance->scene_manager, NfcSceneMfClassicWriteInitialWrongCard); - consumed = true; - } else if(event.event == NfcCustomEventPollerSuccess) { - scene_manager_next_scene( - instance->scene_manager, NfcSceneMfClassicWriteInitialSuccess); - consumed = true; - } else if(event.event == NfcCustomEventPollerFailure) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWriteInitialFail); - consumed = true; - } - } - - return consumed; -} - -void nfc_scene_mf_classic_write_initial_on_exit(void* context) { - NfcApp* instance = context; - - nfc_poller_stop(instance->poller); - nfc_poller_free(instance->poller); - - scene_manager_set_scene_state( - instance->scene_manager, - NfcSceneMfClassicWriteInitial, - NfcSceneMfClassicWriteInitialStateCardSearch); - // Clear view - popup_reset(instance->popup); - - nfc_blink_stop(instance); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_fail.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_fail.c deleted file mode 100644 index 4d4367ec8..000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_fail.c +++ /dev/null @@ -1,62 +0,0 @@ -#include "../nfc_app_i.h" - -void nfc_scene_mf_classic_write_initial_fail_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - NfcApp* instance = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(instance->view_dispatcher, result); - } -} - -void nfc_scene_mf_classic_write_initial_fail_on_enter(void* context) { - NfcApp* instance = context; - Widget* widget = instance->widget; - - notification_message(instance->notifications, &sequence_error); - - widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); - widget_add_string_element( - widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!"); - widget_add_string_multiline_element( - widget, - 7, - 17, - AlignLeft, - AlignTop, - FontSecondary, - "Not all sectors\nwere written\ncorrectly."); - - widget_add_button_element( - widget, - GuiButtonTypeLeft, - "Finish", - nfc_scene_mf_classic_write_initial_fail_widget_callback, - instance); - - // Setup and start worker - view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_mf_classic_write_initial_fail_on_event(void* context, SceneManagerEvent event) { - NfcApp* instance = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - consumed = scene_manager_search_and_switch_to_previous_scene( - instance->scene_manager, NfcSceneSavedMenu); - } - } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_search_and_switch_to_previous_scene( - instance->scene_manager, NfcSceneSavedMenu); - } - return consumed; -} - -void nfc_scene_mf_classic_write_initial_fail_on_exit(void* context) { - NfcApp* instance = context; - - widget_reset(instance->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_success.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_success.c deleted file mode 100644 index 100c5c431..000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_success.c +++ /dev/null @@ -1,43 +0,0 @@ -#include "../nfc_app_i.h" - -void nfc_scene_mf_classic_write_initial_success_popup_callback(void* context) { - NfcApp* instance = context; - view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventViewExit); -} - -void nfc_scene_mf_classic_write_initial_success_on_enter(void* context) { - NfcApp* instance = context; - dolphin_deed(DolphinDeedNfcSave); - - notification_message(instance->notifications, &sequence_success); - - Popup* popup = instance->popup; - popup_set_header(popup, "Success!", 75, 10, AlignLeft, AlignTop); - popup_set_icon(popup, 0, 9, &I_DolphinSuccess_91x55); - popup_set_timeout(popup, 1500); - popup_set_context(popup, instance); - popup_set_callback(popup, nfc_scene_mf_classic_write_initial_success_popup_callback); - popup_enable_timeout(popup); - - view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); -} - -bool nfc_scene_mf_classic_write_initial_success_on_event(void* context, SceneManagerEvent event) { - NfcApp* instance = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventViewExit) { - consumed = scene_manager_search_and_switch_to_previous_scene( - instance->scene_manager, NfcSceneSavedMenu); - } - } - return consumed; -} - -void nfc_scene_mf_classic_write_initial_success_on_exit(void* context) { - NfcApp* instance = context; - - // Clear view - popup_reset(instance->popup); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_wrong_card.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_wrong_card.c deleted file mode 100644 index 3d49b3cac..000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_wrong_card.c +++ /dev/null @@ -1,57 +0,0 @@ -#include "../nfc_app_i.h" - -void nfc_scene_mf_classic_write_initial_wrong_card_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - NfcApp* instance = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(instance->view_dispatcher, result); - } -} - -void nfc_scene_mf_classic_write_initial_wrong_card_on_enter(void* context) { - NfcApp* instance = context; - Widget* widget = instance->widget; - - notification_message(instance->notifications, &sequence_error); - - widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); - widget_add_string_element( - widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Use The Source Card!"); - widget_add_string_multiline_element( - widget, - 4, - 17, - AlignLeft, - AlignTop, - FontSecondary, - "Go to NFC Magic\napp if you want to\nwrite blanks"); - widget_add_button_element( - widget, - GuiButtonTypeLeft, - "Retry", - nfc_scene_mf_classic_write_initial_wrong_card_widget_callback, - instance); - - // Setup and start worker - view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_mf_classic_write_initial_wrong_card_on_event(void* context, SceneManagerEvent event) { - NfcApp* instance = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - consumed = scene_manager_previous_scene(instance->scene_manager); - } - } - return consumed; -} - -void nfc_scene_mf_classic_write_initial_wrong_card_on_exit(void* context) { - NfcApp* instance = context; - - widget_reset(instance->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write.c deleted file mode 100644 index 157d6ce1b..000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write.c +++ /dev/null @@ -1,120 +0,0 @@ -#include "../nfc_app_i.h" - -#include - -enum { - NfcSceneMfUltralightWriteStateCardSearch, - NfcSceneMfUltralightWriteStateCardFound, -}; - -NfcCommand nfc_scene_mf_ultralight_write_worker_callback(NfcGenericEvent event, void* context) { - furi_assert(context); - furi_assert(event.event_data); - furi_assert(event.protocol == NfcProtocolMfUltralight); - - NfcCommand command = NfcCommandContinue; - NfcApp* instance = context; - MfUltralightPollerEvent* mfu_event = event.event_data; - - if(mfu_event->type == MfUltralightPollerEventTypeRequestMode) { - mfu_event->data->poller_mode = MfUltralightPollerModeWrite; - view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected); - } else if(mfu_event->type == MfUltralightPollerEventTypeAuthRequest) { - mfu_event->data->auth_context.skip_auth = true; - } else if(mfu_event->type == MfUltralightPollerEventTypeRequestWriteData) { - mfu_event->data->write_data = - nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight); - } else if(mfu_event->type == MfUltralightPollerEventTypeCardMismatch) { - view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWrongCard); - command = NfcCommandStop; - } else if(mfu_event->type == MfUltralightPollerEventTypeCardLocked) { - view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure); - command = NfcCommandStop; - } else if(mfu_event->type == MfUltralightPollerEventTypeWriteFail) { - command = NfcCommandStop; - } else if(mfu_event->type == MfUltralightPollerEventTypeWriteSuccess) { - view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); - command = NfcCommandStop; - } - return command; -} - -static void nfc_scene_mf_ultralight_write_setup_view(NfcApp* instance) { - Popup* popup = instance->popup; - popup_reset(popup); - uint32_t state = - scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightWrite); - - if(state == NfcSceneMfUltralightWriteStateCardSearch) { - popup_set_header(instance->popup, "Writing", 95, 20, AlignCenter, AlignCenter); - popup_set_text( - instance->popup, "Apply the initial\ncard only", 95, 38, AlignCenter, AlignCenter); - popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); - } else { - popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter); - popup_set_icon(popup, 12, 23, &A_Loading_24); - } - - view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); -} - -void nfc_scene_mf_ultralight_write_on_enter(void* context) { - NfcApp* instance = context; - dolphin_deed(DolphinDeedNfcEmulate); - - scene_manager_set_scene_state( - instance->scene_manager, - NfcSceneMfUltralightWrite, - NfcSceneMfUltralightWriteStateCardSearch); - nfc_scene_mf_ultralight_write_setup_view(instance); - - // Setup and start worker - FURI_LOG_D("WMFU", "Card searching..."); - instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); - nfc_poller_start(instance->poller, nfc_scene_mf_ultralight_write_worker_callback, instance); - - nfc_blink_emulate_start(instance); -} - -bool nfc_scene_mf_ultralight_write_on_event(void* context, SceneManagerEvent event) { - NfcApp* instance = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventCardDetected) { - scene_manager_set_scene_state( - instance->scene_manager, - NfcSceneMfUltralightWrite, - NfcSceneMfUltralightWriteStateCardFound); - nfc_scene_mf_ultralight_write_setup_view(instance); - consumed = true; - } else if(event.event == NfcCustomEventWrongCard) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWrongCard); - consumed = true; - } else if(event.event == NfcCustomEventPollerSuccess) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWriteSuccess); - consumed = true; - } else if(event.event == NfcCustomEventPollerFailure) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWriteFail); - consumed = true; - } - } - - return consumed; -} - -void nfc_scene_mf_ultralight_write_on_exit(void* context) { - NfcApp* instance = context; - - nfc_poller_stop(instance->poller); - nfc_poller_free(instance->poller); - - scene_manager_set_scene_state( - instance->scene_manager, - NfcSceneMfUltralightWrite, - NfcSceneMfUltralightWriteStateCardSearch); - // Clear view - popup_reset(instance->popup); - - nfc_blink_stop(instance); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_fail.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_fail.c deleted file mode 100644 index fcfb5f2b0..000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_fail.c +++ /dev/null @@ -1,67 +0,0 @@ -#include "../nfc_app_i.h" - -void nfc_scene_mf_ultralight_write_fail_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - NfcApp* instance = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(instance->view_dispatcher, result); - } -} - -void nfc_scene_mf_ultralight_write_fail_on_enter(void* context) { - NfcApp* instance = context; - Widget* widget = instance->widget; - - notification_message(instance->notifications, &sequence_error); - - widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); - widget_add_string_element( - widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!"); - widget_add_string_multiline_element( - widget, - 7, - 17, - AlignLeft, - AlignTop, - FontSecondary, - "Card protected by\npassword, AUTH0\nor lock bits"); - - widget_add_button_element( - widget, - GuiButtonTypeLeft, - "Finish", - nfc_scene_mf_ultralight_write_fail_widget_callback, - instance); - - // Setup and start worker - view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); -} - -static bool nfc_scene_mf_ultralight_write_fail_move_to_back_scene(const NfcApp* const instance) { - bool was_saved = scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSavedMenu); - uint32_t scene_id = was_saved ? NfcSceneSavedMenu : NfcSceneReadMenu; - - return scene_manager_search_and_switch_to_previous_scene(instance->scene_manager, scene_id); -} - -bool nfc_scene_mf_ultralight_write_fail_on_event(void* context, SceneManagerEvent event) { - NfcApp* instance = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - consumed = nfc_scene_mf_ultralight_write_fail_move_to_back_scene(instance); - } - } else if(event.type == SceneManagerEventTypeBack) { - consumed = nfc_scene_mf_ultralight_write_fail_move_to_back_scene(instance); - } - return consumed; -} - -void nfc_scene_mf_ultralight_write_fail_on_exit(void* context) { - NfcApp* instance = context; - - widget_reset(instance->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_success.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_success.c deleted file mode 100644 index bb34190d2..000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_success.c +++ /dev/null @@ -1,46 +0,0 @@ -#include "../nfc_app_i.h" - -void nfc_scene_mf_ultralight_write_success_popup_callback(void* context) { - NfcApp* instance = context; - view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventViewExit); -} - -void nfc_scene_mf_ultralight_write_success_on_enter(void* context) { - NfcApp* instance = context; - dolphin_deed(DolphinDeedNfcSave); - - notification_message(instance->notifications, &sequence_success); - - Popup* popup = instance->popup; - popup_set_icon(popup, 48, 6, &I_DolphinDone_80x58); - popup_set_header(popup, "Successfully\nwritten", 5, 22, AlignLeft, AlignBottom); - popup_set_timeout(popup, 1500); - popup_set_context(popup, instance); - popup_set_callback(popup, nfc_scene_mf_ultralight_write_success_popup_callback); - popup_enable_timeout(popup); - - view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); -} - -bool nfc_scene_mf_ultralight_write_success_on_event(void* context, SceneManagerEvent event) { - NfcApp* instance = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventViewExit) { - bool was_saved = - scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSavedMenu); - - consumed = scene_manager_search_and_switch_to_previous_scene( - instance->scene_manager, was_saved ? NfcSceneSavedMenu : NfcSceneReadSuccess); - } - } - return consumed; -} - -void nfc_scene_mf_ultralight_write_success_on_exit(void* context) { - NfcApp* instance = context; - - // Clear view - popup_reset(instance->popup); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_wrong_card.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_wrong_card.c deleted file mode 100644 index bc34a45b4..000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_wrong_card.c +++ /dev/null @@ -1,58 +0,0 @@ -#include "../nfc_app_i.h" - -void nfc_scene_mf_ultralight_wrong_card_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - NfcApp* instance = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(instance->view_dispatcher, result); - } -} - -void nfc_scene_mf_ultralight_wrong_card_on_enter(void* context) { - NfcApp* instance = context; - Widget* widget = instance->widget; - - notification_message(instance->notifications, &sequence_error); - - widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); - widget_add_string_element( - widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card"); - widget_add_string_multiline_element( - widget, - 4, - 17, - AlignLeft, - AlignTop, - FontSecondary, - "Card of the same\ntype should be\n presented"); - //"Data management\nis only possible\nwith card of same type"); - widget_add_button_element( - widget, - GuiButtonTypeLeft, - "Retry", - nfc_scene_mf_ultralight_wrong_card_widget_callback, - instance); - - // Setup and start worker - view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_mf_ultralight_wrong_card_on_event(void* context, SceneManagerEvent event) { - NfcApp* instance = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - consumed = scene_manager_previous_scene(instance->scene_manager); - } - } - return consumed; -} - -void nfc_scene_mf_ultralight_wrong_card_on_exit(void* context) { - NfcApp* instance = context; - - widget_reset(instance->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_read.c b/applications/main/nfc/scenes/nfc_scene_read.c index e9603afd1..ff30eb318 100644 --- a/applications/main/nfc/scenes/nfc_scene_read.c +++ b/applications/main/nfc/scenes/nfc_scene_read.c @@ -1,6 +1,12 @@ #include "../helpers/protocol_support/nfc_protocol_support.h" +#include "../nfc_app_i.h" void nfc_scene_read_on_enter(void* context) { + NfcApp* instance = context; + nfc_show_loading_popup(instance, true); + nfc_supported_cards_load_cache(instance->nfc_supported_cards); + nfc_show_loading_popup(instance, false); + nfc_protocol_support_on_enter(NfcProtocolSupportSceneRead, context); } diff --git a/applications/main/nfc/scenes/nfc_scene_start.c b/applications/main/nfc/scenes/nfc_scene_start.c index b6e8f8298..23343daf8 100644 --- a/applications/main/nfc/scenes/nfc_scene_start.c +++ b/applications/main/nfc/scenes/nfc_scene_start.c @@ -56,37 +56,25 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) { bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { + consumed = true; if(event.event == SubmenuIndexRead) { - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, SubmenuIndexRead); scene_manager_next_scene(nfc->scene_manager, NfcSceneDetect); dolphin_deed(DolphinDeedNfcRead); - consumed = true; } else if(event.event == SubmenuIndexDetectReader) { - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneStart, SubmenuIndexDetectReader); scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDetectReader); - consumed = true; } else if(event.event == SubmenuIndexSaved) { - // Save the scene state explicitly in each branch, so that - // if the user cancels loading a file, the Saved menu item - // is properly reselected. - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, SubmenuIndexSaved); scene_manager_next_scene(nfc->scene_manager, NfcSceneFileSelect); - consumed = true; } else if(event.event == SubmenuIndexExtraAction) { - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneStart, SubmenuIndexExtraAction); scene_manager_next_scene(nfc->scene_manager, NfcSceneExtraActions); - consumed = true; } else if(event.event == SubmenuIndexAddManually) { - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneStart, SubmenuIndexAddManually); scene_manager_next_scene(nfc->scene_manager, NfcSceneSetType); - consumed = true; } else if(event.event == SubmenuIndexDebug) { - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, SubmenuIndexDebug); scene_manager_next_scene(nfc->scene_manager, NfcSceneDebug); - consumed = true; + } else { + consumed = false; + } + if(consumed) { + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, event.event); } } return consumed; diff --git a/applications/main/nfc/scenes/nfc_scene_write.c b/applications/main/nfc/scenes/nfc_scene_write.c new file mode 100644 index 000000000..a5f8fbb13 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_write.c @@ -0,0 +1,13 @@ +#include "../helpers/protocol_support/nfc_protocol_support.h" + +void nfc_scene_write_on_enter(void* context) { + nfc_protocol_support_on_enter(NfcProtocolSupportSceneWrite, context); +} + +bool nfc_scene_write_on_event(void* context, SceneManagerEvent event) { + return nfc_protocol_support_on_event(NfcProtocolSupportSceneWrite, context, event); +} + +void nfc_scene_write_on_exit(void* context) { + nfc_protocol_support_on_exit(NfcProtocolSupportSceneWrite, context); +} diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index e2aa3d897..73ae7daae 100755 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -155,6 +155,9 @@ Header,+,lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h,, Header,+,lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h,, Header,+,lib/nfc/protocols/iso14443_4b/iso14443_4b.h,, Header,+,lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h,, +Header,+,lib/nfc/protocols/iso15693_3/iso15693_3.h,, +Header,+,lib/nfc/protocols/iso15693_3/iso15693_3_listener.h,, +Header,+,lib/nfc/protocols/iso15693_3/iso15693_3_poller.h,, Header,+,lib/nfc/protocols/mf_classic/mf_classic.h,, Header,+,lib/nfc/protocols/mf_classic/mf_classic_listener.h,, Header,+,lib/nfc/protocols/mf_classic/mf_classic_poller.h,, @@ -167,11 +170,17 @@ Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight.h,, Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.h,, Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h,, Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h,, +Header,+,lib/nfc/protocols/ntag4xx/ntag4xx.h,, +Header,+,lib/nfc/protocols/ntag4xx/ntag4xx_poller.h,, Header,+,lib/nfc/protocols/slix/slix.h,, +Header,+,lib/nfc/protocols/slix/slix_listener.h,, Header,+,lib/nfc/protocols/slix/slix_poller.h,, Header,+,lib/nfc/protocols/st25tb/st25tb.h,, Header,+,lib/nfc/protocols/st25tb/st25tb_poller.h,, Header,+,lib/nfc/protocols/st25tb/st25tb_poller_sync.h,, +Header,+,lib/nfc/protocols/type_4_tag/type_4_tag.h,, +Header,+,lib/nfc/protocols/type_4_tag/type_4_tag_listener.h,, +Header,+,lib/nfc/protocols/type_4_tag/type_4_tag_poller.h,, Header,+,lib/one_wire/maxim_crc.h,, Header,+,lib/one_wire/one_wire_host.h,, Header,+,lib/one_wire/one_wire_slave.h,, @@ -2166,6 +2175,9 @@ Function,+,iso14443_3a_get_device_name,const char*,"const Iso14443_3aData*, NfcD Function,+,iso14443_3a_get_sak,uint8_t,const Iso14443_3aData* Function,+,iso14443_3a_get_uid,const uint8_t*,"const Iso14443_3aData*, size_t*" Function,+,iso14443_3a_is_equal,_Bool,"const Iso14443_3aData*, const Iso14443_3aData*" +Function,+,iso14443_3a_listener_send_standard_frame,Iso14443_3aError,"Iso14443_3aListener*, const BitBuffer*" +Function,+,iso14443_3a_listener_tx,Iso14443_3aError,"Iso14443_3aListener*, const BitBuffer*" +Function,+,iso14443_3a_listener_tx_with_custom_parity,Iso14443_3aError,"Iso14443_3aListener*, const BitBuffer*" Function,+,iso14443_3a_load,_Bool,"Iso14443_3aData*, FlipperFormat*, uint32_t" Function,+,iso14443_3a_poller_activate,Iso14443_3aError,"Iso14443_3aPoller*, Iso14443_3aData*" Function,+,iso14443_3a_poller_check_presence,Iso14443_3aError,Iso14443_3aPoller* @@ -2212,6 +2224,7 @@ Function,+,iso14443_4a_get_fwt_fc_max,uint32_t,const Iso14443_4aData* Function,+,iso14443_4a_get_historical_bytes,const uint8_t*,"const Iso14443_4aData*, uint32_t*" Function,+,iso14443_4a_get_uid,const uint8_t*,"const Iso14443_4aData*, size_t*" Function,+,iso14443_4a_is_equal,_Bool,"const Iso14443_4aData*, const Iso14443_4aData*" +Function,+,iso14443_4a_listener_send_block,Iso14443_4aError,"Iso14443_4aListener*, const BitBuffer*" Function,+,iso14443_4a_load,_Bool,"Iso14443_4aData*, FlipperFormat*, uint32_t" Function,+,iso14443_4a_poller_halt,Iso14443_4aError,Iso14443_4aPoller* Function,+,iso14443_4a_poller_read_ats,Iso14443_4aError,"Iso14443_4aPoller*, Iso14443_4aAtsData*" @@ -2256,6 +2269,13 @@ Function,+,iso15693_3_get_uid,const uint8_t*,"const Iso15693_3Data*, size_t*" Function,+,iso15693_3_is_block_locked,_Bool,"const Iso15693_3Data*, uint8_t" Function,+,iso15693_3_is_equal,_Bool,"const Iso15693_3Data*, const Iso15693_3Data*" Function,+,iso15693_3_load,_Bool,"Iso15693_3Data*, FlipperFormat*, uint32_t" +Function,+,iso15693_3_poller_activate,Iso15693_3Error,"Iso15693_3Poller*, Iso15693_3Data*" +Function,+,iso15693_3_poller_get_blocks_security,Iso15693_3Error,"Iso15693_3Poller*, uint8_t*, uint16_t" +Function,+,iso15693_3_poller_get_system_info,Iso15693_3Error,"Iso15693_3Poller*, Iso15693_3SystemInfo*" +Function,+,iso15693_3_poller_inventory,Iso15693_3Error,"Iso15693_3Poller*, uint8_t*" +Function,+,iso15693_3_poller_read_block,Iso15693_3Error,"Iso15693_3Poller*, uint8_t*, uint8_t, uint8_t" +Function,+,iso15693_3_poller_read_blocks,Iso15693_3Error,"Iso15693_3Poller*, uint8_t*, uint16_t, uint8_t" +Function,+,iso15693_3_poller_send_frame,Iso15693_3Error,"Iso15693_3Poller*, const BitBuffer*, BitBuffer*, uint32_t" Function,+,iso15693_3_reset,void,Iso15693_3Data* Function,+,iso15693_3_save,_Bool,"const Iso15693_3Data*, FlipperFormat*" Function,+,iso15693_3_set_uid,_Bool,"Iso15693_3Data*, const uint8_t*, size_t" @@ -2682,6 +2702,8 @@ Function,+,mf_desfire_get_file_settings,const MfDesfireFileSettings*,"const MfDe Function,+,mf_desfire_get_uid,const uint8_t*,"const MfDesfireData*, size_t*" Function,+,mf_desfire_is_equal,_Bool,"const MfDesfireData*, const MfDesfireData*" Function,+,mf_desfire_load,_Bool,"MfDesfireData*, FlipperFormat*, uint32_t" +Function,+,mf_desfire_poller_create_application,MfDesfireError,"MfDesfirePoller*, const MfDesfireApplicationId*, const MfDesfireKeySettings*, uint16_t, const uint8_t*, uint8_t" +Function,+,mf_desfire_poller_create_file,MfDesfireError,"MfDesfirePoller*, MfDesfireFileId, const MfDesfireFileSettings*, uint16_t" Function,+,mf_desfire_poller_read_application,MfDesfireError,"MfDesfirePoller*, MfDesfireApplication*" Function,+,mf_desfire_poller_read_application_ids,MfDesfireError,"MfDesfirePoller*, SimpleArray*" Function,+,mf_desfire_poller_read_applications,MfDesfireError,"MfDesfirePoller*, const SimpleArray*, SimpleArray*" @@ -2698,9 +2720,10 @@ Function,+,mf_desfire_poller_read_key_version,MfDesfireError,"MfDesfirePoller*, Function,+,mf_desfire_poller_read_key_versions,MfDesfireError,"MfDesfirePoller*, SimpleArray*, uint32_t" Function,+,mf_desfire_poller_read_version,MfDesfireError,"MfDesfirePoller*, MfDesfireVersion*" Function,+,mf_desfire_poller_select_application,MfDesfireError,"MfDesfirePoller*, const MfDesfireApplicationId*" +Function,+,mf_desfire_poller_send_chunks,MfDesfireError,"MfDesfirePoller*, const BitBuffer*, BitBuffer*" +Function,+,mf_desfire_poller_set_command_mode,void,"MfDesfirePoller*, NxpNativeCommandMode" Function,+,mf_desfire_reset,void,MfDesfireData* Function,+,mf_desfire_save,_Bool,"const MfDesfireData*, FlipperFormat*" -Function,+,mf_desfire_send_chunks,MfDesfireError,"MfDesfirePoller*, const BitBuffer*, BitBuffer*" Function,+,mf_desfire_set_uid,_Bool,"MfDesfireData*, const uint8_t*, size_t" Function,+,mf_desfire_verify,_Bool,"MfDesfireData*, const FuriString*" Function,+,mf_plus_alloc,MfPlusData*, @@ -2914,9 +2937,6 @@ Function,+,nfc_iso14443a_listener_tx_custom_parity,NfcError,"Nfc*, const BitBuff Function,+,nfc_iso14443a_poller_trx_custom_parity,NfcError,"Nfc*, const BitBuffer*, BitBuffer*, uint32_t" Function,+,nfc_iso14443a_poller_trx_sdd_frame,NfcError,"Nfc*, const BitBuffer*, BitBuffer*, uint32_t" Function,+,nfc_iso14443a_poller_trx_short_frame,NfcError,"Nfc*, NfcIso14443aShortFrame, BitBuffer*, uint32_t" -Function,+,nfc_iso15693_detect_mode,NfcError,Nfc* -Function,+,nfc_iso15693_force_1outof256,NfcError,Nfc* -Function,+,nfc_iso15693_force_1outof4,NfcError,Nfc* Function,+,nfc_iso15693_listener_tx_sof,NfcError,Nfc* Function,+,nfc_listener_alloc,NfcListener*,"Nfc*, NfcProtocol, const NfcDeviceData*" Function,+,nfc_listener_free,void,NfcListener* @@ -2956,6 +2976,19 @@ Function,+,notification_internal_message_block,void,"NotificationApp*, const Not Function,+,notification_message,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_message_block,void,"NotificationApp*, const NotificationSequence*" Function,-,nrand48,long,unsigned short[3] +Function,+,ntag4xx_alloc,Ntag4xxData*, +Function,+,ntag4xx_copy,void,"Ntag4xxData*, const Ntag4xxData*" +Function,+,ntag4xx_free,void,Ntag4xxData* +Function,+,ntag4xx_get_base_data,Iso14443_4aData*,const Ntag4xxData* +Function,+,ntag4xx_get_device_name,const char*,"const Ntag4xxData*, NfcDeviceNameType" +Function,+,ntag4xx_get_type_from_version,Ntag4xxType,const Ntag4xxVersion* const +Function,+,ntag4xx_get_uid,const uint8_t*,"const Ntag4xxData*, size_t*" +Function,+,ntag4xx_is_equal,_Bool,"const Ntag4xxData*, const Ntag4xxData*" +Function,+,ntag4xx_load,_Bool,"Ntag4xxData*, FlipperFormat*, uint32_t" +Function,+,ntag4xx_reset,void,Ntag4xxData* +Function,+,ntag4xx_save,_Bool,"const Ntag4xxData*, FlipperFormat*" +Function,+,ntag4xx_set_uid,_Bool,"Ntag4xxData*, const uint8_t*, size_t" +Function,+,ntag4xx_verify,_Bool,"Ntag4xxData*, const FuriString*" Function,+,number_input_alloc,NumberInput*, Function,+,number_input_free,void,NumberInput* Function,+,number_input_get_view,View*,NumberInput* @@ -3726,6 +3759,18 @@ Function,-,toupper_l,int,"int, locale_t" Function,-,trunc,double,double Function,-,truncf,float,float Function,-,truncl,long double,long double +Function,+,type_4_tag_alloc,Type4TagData*, +Function,+,type_4_tag_copy,void,"Type4TagData*, const Type4TagData*" +Function,+,type_4_tag_free,void,Type4TagData* +Function,+,type_4_tag_get_base_data,Iso14443_4aData*,const Type4TagData* +Function,+,type_4_tag_get_device_name,const char*,"const Type4TagData*, NfcDeviceNameType" +Function,+,type_4_tag_get_uid,const uint8_t*,"const Type4TagData*, size_t*" +Function,+,type_4_tag_is_equal,_Bool,"const Type4TagData*, const Type4TagData*" +Function,+,type_4_tag_load,_Bool,"Type4TagData*, FlipperFormat*, uint32_t" +Function,+,type_4_tag_reset,void,Type4TagData* +Function,+,type_4_tag_save,_Bool,"const Type4TagData*, FlipperFormat*" +Function,+,type_4_tag_set_uid,_Bool,"Type4TagData*, const uint8_t*, size_t" +Function,+,type_4_tag_verify,_Bool,"Type4TagData*, const FuriString*" Function,+,uint8_to_hex_chars,void,"const uint8_t*, uint8_t*, int" Function,-,ungetc,int,"int, FILE*" Function,-,unsetenv,int,const char* @@ -4117,7 +4162,9 @@ Variable,-,nfc_device_mf_classic,const NfcDeviceBase, Variable,-,nfc_device_mf_desfire,const NfcDeviceBase, Variable,-,nfc_device_mf_plus,const NfcDeviceBase, Variable,-,nfc_device_mf_ultralight,const NfcDeviceBase, +Variable,-,nfc_device_ntag4xx,const NfcDeviceBase, Variable,-,nfc_device_st25tb,const NfcDeviceBase, +Variable,-,nfc_device_type_4_tag,const NfcDeviceBase, Variable,+,sequence_audiovisual_alert,const NotificationSequence, Variable,+,sequence_blink_blue_10,const NotificationSequence, Variable,+,sequence_blink_blue_100,const NotificationSequence, From 1448a70393e316437e3d7d4aefe07c9100a70a0d Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 30 Jun 2025 21:00:50 +0300 Subject: [PATCH 57/75] upd changelog --- CHANGELOG.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a16db022e..4a4deadc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,9 +12,32 @@ * NFC: Ultralight C - Attempt of authentication with default key (PR #898 | by @mishamyte) * System: Loader - Fix misplaced ApplicationBeforeLoad events (PR #905 | by @WillyJL) * OFW PR 4210: Infrared: Add text scroll to remote buttons (by @956MB) +* NFC: + - NFC Type 4 support + many other improvements (by @WillyJL) + - New Type 4 Tag (NDEF on NTAG4xx / MIFARE DESFire) protocol, full support + - New NTAG4xx (NTAG413 DNA / NTAG424 DNA) protocol, only detection and basic info support + - NDEF parsing plugin supports Type 4 Tag protocol + - Show more version info for MIFARE Plus cards + - Improve detection/verification of MIFARE DESFire and MIFARE Plus SE + - Improve navigation for MIFARE Classic Update from / Write to Initial Card + - Refactor Write code for MIFARE Ultralight/Classic in NFC app helpers + - Cleanup event handling in NFC app + - NFC app uses a bit less RAM because of previous 2 points + - Refactor NXP Native Commands to share between protocols (used by MIFARE DESFire, MIFARE Plus, NTAG4xx) + - MIFARE DESFire poller API can now switch between native and ISO7816-wrapped commands + - Expand ISO14443-4A API with listener (emulation) support for sending responses to reader (except I-block chaining) + - Exposed some APIs for apps to use that were meant to be public: + - ISO14443-3A listener (emulation) + - ISO15693-3 device (data), poller (reading), listener (emulation) + - Cleanup/reorder protocol definitions for tidyness + - Ventra ULEV1 parser (by @hazardousvoltage) + - CSC Service Works parser (by @zinongli) + - Philips Sonicare parser (by @Sil333033) + - SmartRider parser (by @jaylikesbunda) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * RFID 125khz: Fix strange bug with LCD backlight going off after doing "Write" +* GUI: Added `submenu_remove_item()` to API, was needed for NFC Type 4 related changes (by @WillyJL) * SubGHz: Fix possible frequency analyzer deadlock when holding Ok (by @WillyJL) * RFID 125khz: Add DEZ10 representation to EM410X (by @realcatgirly) * OFW PR 4205: fix sample durations when using external CC1101 (by @Aerosnail) From 8f203f47d9e1eea937bcade5eb126ee1fa31ae2c Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 5 Jul 2025 17:48:30 +0300 Subject: [PATCH 58/75] comunello add manually support --- .../main/subghz/helpers/subghz_custom_event.h | 2 ++ .../subghz/scenes/subghz_scene_set_type.c | 22 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index bd8dee161..bdd5f849d 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -79,6 +79,8 @@ typedef enum { SetTypeDoorHan_433_92, SetTypeBeninca433, SetTypeBeninca868, + SetTypeComunello433, + SetTypeComunello868, SetTypeAllmatic433, SetTypeAllmatic868, SetTypeCenturion433, diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index 3155f9f1e..f70aec803 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -25,6 +25,8 @@ static const char* submenu_names[SetTypeMAX] = { [SetTypeDoorHan_433_92] = "KL: DoorHan 433MHz", [SetTypeBeninca433] = "KL: Beninca 433MHz", [SetTypeBeninca868] = "KL: Beninca 868MHz", + [SetTypeComunello433] = "KL: Comunello 433MHz", + [SetTypeComunello868] = "KL: Comunello 868MHz", [SetTypeAllmatic433] = "KL: Allmatic 433MHz", [SetTypeAllmatic868] = "KL: Allmatic 868MHz", [SetTypeCenturion433] = "KL: Centurion 433MHz", @@ -400,6 +402,26 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { .keeloq.cnt = 0x05, .keeloq.manuf = "Beninca"}; break; + case SetTypeComunello433: + gen_info = (GenInfo){ + .type = GenKeeloq, + .mod = "AM650", + .freq = 433920000, + .keeloq.serial = key & 0x00FFFFFF, + .keeloq.btn = 0x08, + .keeloq.cnt = 0x05, + .keeloq.manuf = "Comunello"}; + break; + case SetTypeComunello868: + gen_info = (GenInfo){ + .type = GenKeeloq, + .mod = "AM650", + .freq = 868460000, + .keeloq.serial = key & 0x00FFFFFF, + .keeloq.btn = 0x08, + .keeloq.cnt = 0x05, + .keeloq.manuf = "Comunello"}; + break; case SetTypeAllmatic433: gen_info = (GenInfo){ .type = GenKeeloq, From aad07ed9431b4397073d16231b008a96ba1f0e07 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 5 Jul 2025 17:57:30 +0300 Subject: [PATCH 59/75] expansion and serial fixes and new api by HaxSam & WillyJL --- CHANGELOG.md | 2 + applications/services/expansion/expansion.c | 66 ++++++++++++++++-- applications/services/expansion/expansion.h | 9 +++ .../services/expansion/expansion_worker.c | 8 ++- .../services/expansion/expansion_worker.h | 8 ++- targets/f7/furi_hal/furi_hal_serial.c | 67 +++++++++++-------- 6 files changed, 122 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a4deadc0..3e3969f86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,8 @@ - SmartRider parser (by @jaylikesbunda) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes +* FuriHalSerial: Fix RXFNE interrupt hang, aka freezing with UART output when Expansion Modules are enabled (by @WillyJL) +* Expansion: add is_connected api (by @HaxSam & @WillyJL) * RFID 125khz: Fix strange bug with LCD backlight going off after doing "Write" * GUI: Added `submenu_remove_item()` to API, was needed for NFC Type 4 related changes (by @WillyJL) * SubGHz: Fix possible frequency analyzer deadlock when holding Ok (by @WillyJL) diff --git a/applications/services/expansion/expansion.c b/applications/services/expansion/expansion.c index 219bf0641..2b0c5b27a 100644 --- a/applications/services/expansion/expansion.c +++ b/applications/services/expansion/expansion.c @@ -18,6 +18,7 @@ typedef enum { ExpansionStateDisabled, ExpansionStateEnabled, ExpansionStateRunning, + ExpansionStateConnectionEstablished, } ExpansionState; typedef enum { @@ -27,10 +28,13 @@ typedef enum { ExpansionMessageTypeReloadSettings, ExpansionMessageTypeModuleConnected, ExpansionMessageTypeModuleDisconnected, + ExpansionMessageTypeConnectionEstablished, + ExpansionMessageTypeIsConnected, } ExpansionMessageType; typedef union { FuriHalSerialId serial_id; + bool* is_connected; } ExpansionMessageData; typedef struct { @@ -67,13 +71,21 @@ static void expansion_detect_callback(void* context) { UNUSED(status); } -static void expansion_worker_callback(void* context) { +static void expansion_worker_callback(void* context, ExpansionWorkerCallbackReason reason) { furi_assert(context); Expansion* instance = context; - ExpansionMessage message = { - .type = ExpansionMessageTypeModuleDisconnected, - .api_lock = NULL, // Not locking the API here to avoid a deadlock + ExpansionMessage message; + switch(reason) { + case ExpansionWorkerCallbackReasonExit: + message.type = ExpansionMessageTypeModuleDisconnected; + message.api_lock = NULL; // Not locking the API here to avoid a deadlock + break; + + case ExpansionWorkerCallbackReasonConnected: + message.type = ExpansionMessageTypeConnectionEstablished; + message.api_lock = api_lock_alloc_locked(); + break; }; const FuriStatus status = furi_message_queue_put(instance->queue, &message, FuriWaitForever); @@ -106,7 +118,9 @@ static void UNUSED(data); if(instance->state == ExpansionStateDisabled) { return; - } else if(instance->state == ExpansionStateRunning) { + } else if( + instance->state == ExpansionStateRunning || + instance->state == ExpansionStateConnectionEstablished) { expansion_worker_stop(instance->worker); expansion_worker_free(instance->worker); } else { @@ -124,7 +138,9 @@ static void expansion_control_handler_set_listen_serial( if(instance->state != ExpansionStateDisabled && instance->serial_id == data->serial_id) { return; - } else if(instance->state == ExpansionStateRunning) { + } else if( + instance->state == ExpansionStateRunning || + instance->state == ExpansionStateConnectionEstablished) { expansion_worker_stop(instance->worker); expansion_worker_free(instance->worker); @@ -182,7 +198,8 @@ static void expansion_control_handler_module_disconnected( Expansion* instance, const ExpansionMessageData* data) { UNUSED(data); - if(instance->state != ExpansionStateRunning) { + if(instance->state != ExpansionStateRunning && + instance->state != ExpansionStateConnectionEstablished) { return; } @@ -192,6 +209,23 @@ static void expansion_control_handler_module_disconnected( instance->serial_id, expansion_detect_callback, instance); } +static void expansion_control_handler_connection_established( + Expansion* instance, + const ExpansionMessageData* data) { + UNUSED(data); + if(instance->state != ExpansionStateRunning && + instance->state != ExpansionStateConnectionEstablished) { + return; + } + + instance->state = ExpansionStateConnectionEstablished; +} + +static void + expansion_control_handler_is_connected(Expansion* instance, const ExpansionMessageData* data) { + *data->is_connected = instance->state == ExpansionStateConnectionEstablished; +} + typedef void (*ExpansionControlHandler)(Expansion*, const ExpansionMessageData*); static const ExpansionControlHandler expansion_control_handlers[] = { @@ -201,6 +235,8 @@ static const ExpansionControlHandler expansion_control_handlers[] = { [ExpansionMessageTypeReloadSettings] = expansion_control_handler_reload_settings, [ExpansionMessageTypeModuleConnected] = expansion_control_handler_module_connected, [ExpansionMessageTypeModuleDisconnected] = expansion_control_handler_module_disconnected, + [ExpansionMessageTypeConnectionEstablished] = expansion_control_handler_connection_established, + [ExpansionMessageTypeIsConnected] = expansion_control_handler_is_connected, }; static int32_t expansion_control(void* context) { @@ -295,6 +331,22 @@ void expansion_disable(Expansion* instance) { api_lock_wait_unlock_and_free(message.api_lock); } +bool expansion_is_connected(Expansion* instance) { + furi_check(instance); + bool is_connected; + + ExpansionMessage message = { + .type = ExpansionMessageTypeIsConnected, + .data.is_connected = &is_connected, + .api_lock = api_lock_alloc_locked(), + }; + + furi_message_queue_put(instance->queue, &message, FuriWaitForever); + api_lock_wait_unlock_and_free(message.api_lock); + + return is_connected; +} + void expansion_set_listen_serial(Expansion* instance, FuriHalSerialId serial_id) { furi_check(instance); furi_check(serial_id < FuriHalSerialIdMax); diff --git a/applications/services/expansion/expansion.h b/applications/services/expansion/expansion.h index e169b3c15..1b0879b1e 100644 --- a/applications/services/expansion/expansion.h +++ b/applications/services/expansion/expansion.h @@ -50,6 +50,15 @@ void expansion_enable(Expansion* instance); */ void expansion_disable(Expansion* instance); +/** + * @brief Check if an expansion module is connected. + * + * @param[in,out] instance pointer to the Expansion instance. + * + * @returns true if the module is connected and initialized, false otherwise. + */ +bool expansion_is_connected(Expansion* instance); + /** * @brief Enable support for expansion modules on designated serial port. * diff --git a/applications/services/expansion/expansion_worker.c b/applications/services/expansion/expansion_worker.c index ac2a5935b..e6d17c152 100644 --- a/applications/services/expansion/expansion_worker.c +++ b/applications/services/expansion/expansion_worker.c @@ -35,7 +35,8 @@ typedef enum { ExpansionWorkerFlagError = 1 << 2, } ExpansionWorkerFlag; -#define EXPANSION_ALL_FLAGS (ExpansionWorkerFlagData | ExpansionWorkerFlagStop) +#define EXPANSION_ALL_FLAGS \ + (ExpansionWorkerFlagData | ExpansionWorkerFlagStop | ExpansionWorkerFlagError) struct ExpansionWorker { FuriThread* thread; @@ -225,6 +226,7 @@ static bool expansion_worker_handle_state_handshake( if(furi_hal_serial_is_baud_rate_supported(instance->serial_handle, baud_rate)) { instance->state = ExpansionWorkerStateConnected; + instance->callback(instance->cb_context, ExpansionWorkerCallbackReasonConnected); // Send response at previous baud rate if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorNone)) break; furi_hal_serial_set_br(instance->serial_handle, baud_rate); @@ -360,6 +362,8 @@ static int32_t expansion_worker(void* context) { expansion_worker_state_machine(instance); } + furi_hal_serial_async_rx_stop(instance->serial_handle); + if(instance->state == ExpansionWorkerStateRpcActive) { expansion_worker_rpc_session_close(instance); } @@ -371,7 +375,7 @@ static int32_t expansion_worker(void* context) { // Do not invoke worker callback on user-requested exit if((instance->exit_reason != ExpansionWorkerExitReasonUser) && (instance->callback != NULL)) { - instance->callback(instance->cb_context); + instance->callback(instance->cb_context, ExpansionWorkerCallbackReasonExit); } return 0; diff --git a/applications/services/expansion/expansion_worker.h b/applications/services/expansion/expansion_worker.h index 761f79c1d..faab2887f 100644 --- a/applications/services/expansion/expansion_worker.h +++ b/applications/services/expansion/expansion_worker.h @@ -17,14 +17,20 @@ */ typedef struct ExpansionWorker ExpansionWorker; +typedef enum { + ExpansionWorkerCallbackReasonExit, + ExpansionWorkerCallbackReasonConnected, +} ExpansionWorkerCallbackReason; + /** * @brief Worker callback type. * * @see expansion_worker_set_callback() * * @param[in,out] context pointer to a user-defined object. + * @param[in] reason reason for the callback. */ -typedef void (*ExpansionWorkerCallback)(void* context); +typedef void (*ExpansionWorkerCallback)(void* context, ExpansionWorkerCallbackReason reason); /** * @brief Create an expansion worker instance. diff --git a/targets/f7/furi_hal/furi_hal_serial.c b/targets/f7/furi_hal/furi_hal_serial.c index 8ad9794a8..e5c30a278 100644 --- a/targets/f7/furi_hal/furi_hal_serial.c +++ b/targets/f7/furi_hal/furi_hal_serial.c @@ -817,6 +817,21 @@ static void furi_hal_serial_async_rx_configure( FuriHalSerialHandle* handle, FuriHalSerialAsyncRxCallback callback, void* context) { + // Disable RXFNE interrupts before unsetting the user callback that reads data + // Otherwise interrupt runs without reading data and without clearing RXFNE flag + // This would cause a system hang as the same interrupt runs in loop forever + if(!callback) { + if(handle->id == FuriHalSerialIdUsart) { + LL_USART_DisableIT_RXNE_RXFNE(USART1); + furi_hal_interrupt_set_isr(FuriHalInterruptIdUart1, NULL, NULL); + furi_hal_serial_usart_deinit_dma_rx(); + } else if(handle->id == FuriHalSerialIdLpuart) { + LL_LPUART_DisableIT_RXNE_RXFNE(LPUART1); + furi_hal_interrupt_set_isr(FuriHalInterruptIdLpUart1, NULL, NULL); + furi_hal_serial_lpuart_deinit_dma_rx(); + } + } + // Handle must be configured before enabling RX interrupt // as it might be triggered right away on a misconfigured handle furi_hal_serial[handle->id].rx_byte_callback = callback; @@ -824,27 +839,17 @@ static void furi_hal_serial_async_rx_configure( furi_hal_serial[handle->id].rx_dma_callback = NULL; furi_hal_serial[handle->id].context = context; - if(handle->id == FuriHalSerialIdUsart) { - if(callback) { + if(callback) { + if(handle->id == FuriHalSerialIdUsart) { furi_hal_serial_usart_deinit_dma_rx(); furi_hal_interrupt_set_isr( FuriHalInterruptIdUart1, furi_hal_serial_usart_irq_callback, NULL); LL_USART_EnableIT_RXNE_RXFNE(USART1); - } else { - furi_hal_interrupt_set_isr(FuriHalInterruptIdUart1, NULL, NULL); - furi_hal_serial_usart_deinit_dma_rx(); - LL_USART_DisableIT_RXNE_RXFNE(USART1); - } - } else if(handle->id == FuriHalSerialIdLpuart) { - if(callback) { + } else if(handle->id == FuriHalSerialIdLpuart) { furi_hal_serial_lpuart_deinit_dma_rx(); furi_hal_interrupt_set_isr( FuriHalInterruptIdLpUart1, furi_hal_serial_lpuart_irq_callback, NULL); LL_LPUART_EnableIT_RXNE_RXFNE(LPUART1); - } else { - furi_hal_interrupt_set_isr(FuriHalInterruptIdLpUart1, NULL, NULL); - furi_hal_serial_lpuart_deinit_dma_rx(); - LL_LPUART_DisableIT_RXNE_RXFNE(LPUART1); } } } @@ -944,33 +949,39 @@ static void furi_hal_serial_dma_configure( FuriHalSerialHandle* handle, FuriHalSerialDmaRxCallback callback, void* context) { - furi_check(handle); - - if(handle->id == FuriHalSerialIdUsart) { - if(callback) { - furi_hal_serial_usart_init_dma_rx(); - furi_hal_interrupt_set_isr( - FuriHalInterruptIdUart1, furi_hal_serial_usart_irq_callback, NULL); - } else { + // Disable RXFNE interrupts before unsetting the user callback that reads data + // Otherwise interrupt runs without reading data and without clearing RXFNE flag + // This would cause a system hang as the same interrupt runs in loop forever + if(!callback) { + if(handle->id == FuriHalSerialIdUsart) { LL_USART_DisableIT_RXNE_RXFNE(USART1); furi_hal_interrupt_set_isr(FuriHalInterruptIdUart1, NULL, NULL); furi_hal_serial_usart_deinit_dma_rx(); - } - } else if(handle->id == FuriHalSerialIdLpuart) { - if(callback) { - furi_hal_serial_lpuart_init_dma_rx(); - furi_hal_interrupt_set_isr( - FuriHalInterruptIdLpUart1, furi_hal_serial_lpuart_irq_callback, NULL); - } else { + } else if(handle->id == FuriHalSerialIdLpuart) { LL_LPUART_DisableIT_RXNE_RXFNE(LPUART1); furi_hal_interrupt_set_isr(FuriHalInterruptIdLpUart1, NULL, NULL); furi_hal_serial_lpuart_deinit_dma_rx(); } } + + // Handle must be configured before enabling RX interrupt + // as it might be triggered right away on a misconfigured handle furi_hal_serial[handle->id].rx_byte_callback = NULL; furi_hal_serial[handle->id].handle = handle; furi_hal_serial[handle->id].rx_dma_callback = callback; furi_hal_serial[handle->id].context = context; + + if(callback) { + if(handle->id == FuriHalSerialIdUsart) { + furi_hal_serial_usart_init_dma_rx(); + furi_hal_interrupt_set_isr( + FuriHalInterruptIdUart1, furi_hal_serial_usart_irq_callback, NULL); + } else if(handle->id == FuriHalSerialIdLpuart) { + furi_hal_serial_lpuart_init_dma_rx(); + furi_hal_interrupt_set_isr( + FuriHalInterruptIdLpUart1, furi_hal_serial_lpuart_irq_callback, NULL); + } + } } void furi_hal_serial_dma_rx_start( From e003a19edcfc3201c449f7be76c782af3adf9d55 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 5 Jul 2025 17:59:27 +0300 Subject: [PATCH 60/75] add 868 46 mhz to default freq list --- CHANGELOG.md | 2 +- documentation/SubGHzSettings.md | 1 + lib/subghz/subghz_setting.c | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e3969f86..591829d8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ * SubGHz: V2 Phoenix show counter value * SubGHz: Add keeloq ironlogic (aka il100) smart clone cloners support (thanks to Vitaly for RAWs) * SubGHz: Fix CAME 24bit decoder -* SubGHz: Add 462.750 MHz to default subghz freqs list +* SubGHz: Add 462.750 MHz & 868.46 MHz to default subghz freqs list * SubGHz: Tune holtek ht12x to decode holtek only and not conflict with came 12bit * SubGHz: Fix Rename scene bug, that was replacing file name with random name when Rename is opened then closed then opened again * Display: Backlight option "always on" and RGB bug removed (PR #900 | by @Dmitry422) diff --git a/documentation/SubGHzSettings.md b/documentation/SubGHzSettings.md index 95e19bdbf..89f3eba7f 100644 --- a/documentation/SubGHzSettings.md +++ b/documentation/SubGHzSettings.md @@ -68,6 +68,7 @@ if you need your custom one, make sure it doesn't listed here 779000000, 868350000, 868400000, + 868460000, 868800000, 868950000, 906400000, diff --git a/lib/subghz/subghz_setting.c b/lib/subghz/subghz_setting.c index 8f41f576c..867459d05 100644 --- a/lib/subghz/subghz_setting.c +++ b/lib/subghz/subghz_setting.c @@ -74,6 +74,7 @@ static const uint32_t subghz_frequency_list[] = { 779000000, 868350000, 868400000, + 868460000, 868800000, 868950000, 906400000, From 95171046268ee0f2037e221a6cd1a63609fdb409 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 5 Jul 2025 19:14:48 +0300 Subject: [PATCH 61/75] Update doorhan programming instructions by @li0ard --- CHANGELOG.md | 1 + documentation/SubGHzRemoteProg.md | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 591829d8f..5116f3154 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ - SmartRider parser (by @jaylikesbunda) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes +* Docs: Update doorhan programming instructions (by @li0ard) * FuriHalSerial: Fix RXFNE interrupt hang, aka freezing with UART output when Expansion Modules are enabled (by @WillyJL) * Expansion: add is_connected api (by @HaxSam & @WillyJL) * RFID 125khz: Fix strange bug with LCD backlight going off after doing "Write" diff --git a/documentation/SubGHzRemoteProg.md b/documentation/SubGHzRemoteProg.md index c02c6ee43..18f726941 100644 --- a/documentation/SubGHzRemoteProg.md +++ b/documentation/SubGHzRemoteProg.md @@ -74,13 +74,20 @@ Watch this video to learn more and see how different boards can be programmed (v ## Doorhan With access to the receiver box: -1. Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> KL: Doorhan 433Mhz or 315Mhz depends on your receiver (find out by reading your existing remote) -2. Open your new remote file -3. Push `P` button for ~2 sec, led will start flashing -4. Press `Send` on your flipper for ~2 seconds -5. Led on the receiver board will flash and turn off -6. Done! +1. Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> KL: Doorhan 433Mhz or 315Mhz depends on your receiver (find out by reading your existing remote or follow guide below) +- Finding frequency +There are 2 frequencies for DoorHan: 315.00 / 433.92. To determine them it is enough to create a DoorHan remote control with one of the frequencies via Sub-GHz -> Add manually, press the button and watch the receiver's reaction. If you have guessed the frequency, the light bulb will turn on when we press the button on the FZ and turn off when we release it. + +2. Binding the remote control + +Once you have access to the receiver (removed the protective cover), look at the buttons: +- If there are 4 buttons (Radio, Reverse, Auto, ...) then press and hold Radio until the LED lights up, then press the FZ button 2 times and the LED goes out; +- If there are 4 buttons (R, P, +, -) and display, press R, then press 2 times the button on FZ and wait +/- 10 seconds; +- If there are 4 buttons (+, -, F, TR) and display, press TR, then press 2 times the button on FZ and wait +/- 10 seconds; +- In other cases there is a “universal” instruction: Press and hold the button “P” +/- 2 seconds until the LED flashes, then press 2 times the button on the FZ and the LED goes out. + +In all cases it is recommended to wait until the receiver returns to normal mode. With existing remote: 1. Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> KL: Doorhan 433Mhz or 315Mhz depends on your receiver (find out by reading your existing remote) From 6ae1ce68610376ba5e198896d94588ccc9ca8df1 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 6 Jul 2025 01:36:26 +0300 Subject: [PATCH 62/75] upd api 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 73ae7daae..d0cea83e2 100755 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1071,6 +1071,7 @@ Function,-,exp2f,float,float Function,-,exp2l,long double,long double Function,+,expansion_disable,void,Expansion* Function,+,expansion_enable,void,Expansion* +Function,+,expansion_is_connected,_Bool,Expansion* Function,+,expansion_set_listen_serial,void,"Expansion*, FuriHalSerialId" Function,-,expf,float,float Function,-,expl,long double,long double From 43b35019ed168fd921313f51f1a84a2e939d0d32 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 6 Jul 2025 18:42:16 +0300 Subject: [PATCH 63/75] subghz marantec protocol implement crc verification and add manually fix crc function, add valid/invalid display (does not affect TX) and use new crc sum in add manually menu --- .../main/subghz/helpers/subghz_custom_event.h | 2 ++ .../helpers/subghz_txrx_create_protocol_key.c | 25 ++++++++++++++ .../helpers/subghz_txrx_create_protocol_key.h | 7 ++++ .../subghz/scenes/subghz_scene_set_type.c | 27 +++++++++++++++ lib/subghz/protocols/marantec.c | 34 ++++++++++++++++--- lib/subghz/protocols/marantec.h | 8 +++++ 6 files changed, 99 insertions(+), 4 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index bdd5f849d..a3dae60b8 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -126,6 +126,8 @@ typedef enum { SetTypeHollarm_433, SetTypeReversRB2_433, SetTypeMarantec24_868, + SetTypeMarantec_433, + SetTypeMarantec_868, SetTypeLinear_300_00, // SetTypeNeroSketch, //Deleted in OFW // SetTypeNeroRadio, //Deleted in OFW diff --git a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c index 63b892401..813b706b6 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -395,3 +396,27 @@ void subghz_txrx_gen_serial_gangqi(uint64_t* result_key) { // serial | const_and_button *result_key = (serial << 18) | (const_and_button << 10) | (bytesum << 2); } + +void subghz_txrx_gen_key_marantec(uint64_t* result_key) { + uint64_t randkey = (uint64_t)rand(); + uint32_t serial = (uint32_t)((randkey) & 0xFFFFF); + // 0x130 is the constant + // 0x4 is the button code + // 0x86 is the serial constant + // serial is random value that we pre generate above + // At the end we will put the crc sum + uint64_t full_key_no_crc = (uint64_t)((uint64_t)0x130 << 40 | (uint64_t)serial << 20 | + (uint64_t)0x4 << 16 | (uint64_t)0x86 << 8); + + uint8_t tdata[6] = { + full_key_no_crc >> 48, + full_key_no_crc >> 40, + full_key_no_crc >> 32, + full_key_no_crc >> 24, + full_key_no_crc >> 16, + full_key_no_crc >> 8}; + + uint8_t crc = subghz_protocol_marantec_crc8(tdata, sizeof(tdata)); + + *result_key = ((full_key_no_crc >> 8) << 8) | crc; +} diff --git a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h index 55932bd39..fba7acb6f 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h @@ -153,3 +153,10 @@ bool subghz_txrx_gen_secplus_v1_protocol( * @return uint64_t if success */ void subghz_txrx_gen_serial_gangqi(uint64_t* result_key); + +/** + * Generate key for Marantec protocol + * + * @param result_key Pointer to a uint64_t where the key will be stored + */ +void subghz_txrx_gen_key_marantec(uint64_t* result_key); diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index f70aec803..134d2e1d1 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -71,6 +71,8 @@ static const char* submenu_names[SetTypeMAX] = { [SetTypeHollarm_433] = "Hollarm 433MHz", [SetTypeReversRB2_433] = "Revers RB2 433MHz", [SetTypeMarantec24_868] = "Marantec24 868MHz", + [SetTypeMarantec_433] = "Marantec 433MHz", + [SetTypeMarantec_868] = "Marantec 868MHz", [SetTypeBETT_433] = "BETT 433MHz", [SetTypeLinear_300_00] = "Linear 300MHz", // [SetTypeNeroSketch] = "Nero Sketch", // Deleted in OFW @@ -192,6 +194,9 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { uint64_t gangqi_key; subghz_txrx_gen_serial_gangqi(&gangqi_key); + uint64_t marantec_key; + subghz_txrx_gen_key_marantec(&marantec_key); + GenInfo gen_info = {0}; switch(event.event) { case SetTypePricenton433: @@ -360,6 +365,28 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { .data.bits = 24, .data.te = 0}; break; + case SetTypeMarantec_433: + gen_info = (GenInfo){ + .type = GenData, + .mod = "AM650", + .freq = 433920000, + .data.name = + SUBGHZ_PROTOCOL_MARANTEC_NAME, // Button code is 0x4 and crc sum to the end + .data.key = marantec_key, + .data.bits = 49, + .data.te = 0}; + break; + case SetTypeMarantec_868: + gen_info = (GenInfo){ + .type = GenData, + .mod = "AM650", + .freq = 868350000, + .data.name = + SUBGHZ_PROTOCOL_MARANTEC_NAME, // Button code is 0x4 and crc sum to the end + .data.key = marantec_key, + .data.bits = 49, + .data.te = 0}; + break; case SetTypeFaacSLH_433: gen_info = (GenInfo){ .type = GenFaacSLH, diff --git a/lib/subghz/protocols/marantec.c b/lib/subghz/protocols/marantec.c index fc4aa0dca..edb176635 100644 --- a/lib/subghz/protocols/marantec.c +++ b/lib/subghz/protocols/marantec.c @@ -165,7 +165,7 @@ static void subghz_protocol_encoder_marantec_get_upload(SubGhzProtocolEncoderMar } uint8_t subghz_protocol_marantec_crc8(uint8_t* data, size_t len) { - uint8_t crc = 0x08; + uint8_t crc = 0x01; size_t i, j; for(i = 0; i < len; i++) { crc ^= data[i]; @@ -184,6 +184,18 @@ uint8_t subghz_protocol_marantec_crc8(uint8_t* data, size_t len) { * @param instance Pointer to a SubGhzBlockGeneric* instance */ static void subghz_protocol_marantec_remote_controller(SubGhzBlockGeneric* instance) { + // Key samples + // 1307EDF6486C5 = 000 100110000 01111110110111110110 0100 10000110 11000101 + // 1303EFAFD8683 = 000 100110000 00111110111110101111 1101 10000110 10000011 + + // From unittests + // 1300710DF869F + + // const serial button serial crc + // 130 7EDF6 4 86 C5 + // 130 3EFAF D 86 83 + // 130 0710D F 86 9F + instance->btn = (instance->data >> 16) & 0xF; instance->serial = ((instance->data >> 12) & 0xFFFFFF00) | ((instance->data >> 8) & 0xFF); } @@ -367,16 +379,30 @@ void subghz_protocol_decoder_marantec_get_string(void* context, FuriString* outp SubGhzProtocolDecoderMarantec* instance = context; subghz_protocol_marantec_remote_controller(&instance->generic); + uint8_t tdata[6] = { + instance->generic.data >> 48, + instance->generic.data >> 40, + instance->generic.data >> 32, + instance->generic.data >> 24, + instance->generic.data >> 16, + instance->generic.data >> 8}; + + uint8_t crc = subghz_protocol_marantec_crc8(tdata, sizeof(tdata)); + bool crc_ok = (crc == (instance->generic.data & 0xFF)); + furi_string_cat_printf( output, "%s %db\r\n" - "Key:0x%lX%08lX\r\n" - "Sn:0x%07lX \r\n" - "Btn:%X\r\n", + "Key: 0x%lX%08lX\r\n" + "Sn: 0x%07lX \r\n" + "CRC: 0x%02X - %s\r\n" + "Btn: %X\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)(instance->generic.data >> 32), (uint32_t)(instance->generic.data & 0xFFFFFFFF), instance->generic.serial, + crc, + crc_ok ? "Valid" : "Invalid", instance->generic.btn); } diff --git a/lib/subghz/protocols/marantec.h b/lib/subghz/protocols/marantec.h index 485c563b2..9a7b55b7e 100644 --- a/lib/subghz/protocols/marantec.h +++ b/lib/subghz/protocols/marantec.h @@ -107,3 +107,11 @@ SubGhzProtocolStatus * @param output Resulting text */ void subghz_protocol_decoder_marantec_get_string(void* context, FuriString* output); + +/** + * Calculate CRC8 for Marantec protocol. + * @param data Pointer to the data buffer + * @param len Length of the data buffer + * @return CRC8 value + */ +uint8_t subghz_protocol_marantec_crc8(uint8_t* data, size_t len); From e025c5742550b2a6609a3ee3a536c235e67c0835 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 6 Jul 2025 18:47:12 +0300 Subject: [PATCH 64/75] Update marantec24 protocol info --- lib/subghz/protocols/marantec24.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/subghz/protocols/marantec24.c b/lib/subghz/protocols/marantec24.c index 588aa1e5a..ac602fc96 100644 --- a/lib/subghz/protocols/marantec24.c +++ b/lib/subghz/protocols/marantec24.c @@ -217,6 +217,11 @@ void subghz_protocol_decoder_marantec24_feed(void* context, bool level, volatile // Marantec24 Decoder // 2024 - @xMasterX (MMX) + // 2025 update - The protocol is not real marantec, + // it comes from chinese remote that pretends to be replica of original marantec, actually it was a cloner + // which had some thing written on it, which is uknown, but since its pretentding to be marantec, + // it was decided to keep the name of the protocol as marantec24 (24 bits) + // Key samples // 101011000000010111001000 = AC05C8 // 101011000000010111000100 = AC05C4 @@ -266,16 +271,12 @@ void subghz_protocol_decoder_marantec24_feed(void* context, bool level, volatile //Found next GAP and add bit 0 or 1 (only bit 0 was found on the remotes) if((DURATION_DIFF( instance->decoder.te_last, subghz_protocol_marantec24_const.te_long) < - subghz_protocol_marantec24_const.te_delta) && - (DURATION_DIFF(duration, subghz_protocol_marantec24_const.te_long * 9) < - subghz_protocol_marantec24_const.te_delta * 4)) { + subghz_protocol_marantec24_const.te_delta)) { subghz_protocol_blocks_add_bit(&instance->decoder, 0); } if((DURATION_DIFF( instance->decoder.te_last, subghz_protocol_marantec24_const.te_short) < - subghz_protocol_marantec24_const.te_delta) && - (DURATION_DIFF(duration, subghz_protocol_marantec24_const.te_long * 9) < - subghz_protocol_marantec24_const.te_delta * 4)) { + subghz_protocol_marantec24_const.te_delta)) { subghz_protocol_blocks_add_bit(&instance->decoder, 1); } // If got 24 bits key reading is finished From a547c946abbc3fcfae95c4aa2fe28d2d59d461a9 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 7 Jul 2025 04:16:58 +0300 Subject: [PATCH 65/75] reduce less popular freqs in default hopper preset, make it faster also add 303 mhz freq to default list --- documentation/SubGHzSettings.md | 5 ++--- lib/subghz/subghz_setting.c | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/documentation/SubGHzSettings.md b/documentation/SubGHzSettings.md index 89f3eba7f..682111bcd 100644 --- a/documentation/SubGHzSettings.md +++ b/documentation/SubGHzSettings.md @@ -12,6 +12,7 @@ if you need your custom one, make sure it doesn't listed here /* 300 - 348 */ 300000000, 302757000, + 303000000, 303875000, 303900000, 304250000, @@ -100,10 +101,8 @@ Your frequencies will be added after default ones ### Default hopper list ``` - 310000000, 315000000, - 318000000, - 418000000, 433920000, + 434420000, 868350000, ``` diff --git a/lib/subghz/subghz_setting.c b/lib/subghz/subghz_setting.c index 867459d05..49a0af58d 100644 --- a/lib/subghz/subghz_setting.c +++ b/lib/subghz/subghz_setting.c @@ -18,6 +18,7 @@ static const uint32_t subghz_frequency_list[] = { /* 300 - 348 */ 300000000, 302757000, + 303000000, 303875000, 303900000, 304250000, @@ -85,11 +86,9 @@ static const uint32_t subghz_frequency_list[] = { }; static const uint32_t subghz_hopper_frequency_list[] = { - 310000000, 315000000, - 318000000, - 418000000, 433920000, + 434420000, 868350000, 0, }; From 74f6ee1e7ceac5d2405cb00c64928d4c55296c03 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 7 Jul 2025 17:55:14 +0300 Subject: [PATCH 66/75] badusb fix modifier keys with HOLD/RELEASE commands by WillyJL --- CHANGELOG.md | 1 + applications/main/bad_usb/helpers/ducky_script.c | 13 ++++++++++--- .../main/bad_usb/helpers/ducky_script_commands.c | 4 ++-- applications/main/bad_usb/helpers/ducky_script_i.h | 4 +++- .../main/bad_usb/helpers/ducky_script_keycodes.c | 12 ++++++++++++ 5 files changed, 28 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5116f3154..beab8f799 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ - SmartRider parser (by @jaylikesbunda) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes +* BadUSB: Fix modifier keys with HOLD/RELEASE commands (by @WillyJL) * Docs: Update doorhan programming instructions (by @li0ard) * FuriHalSerial: Fix RXFNE interrupt hang, aka freezing with UART output when Expansion Modules are enabled (by @WillyJL) * Expansion: add is_connected api (by @HaxSam & @WillyJL) diff --git a/applications/main/bad_usb/helpers/ducky_script.c b/applications/main/bad_usb/helpers/ducky_script.c index 43621de78..4b427b759 100644 --- a/applications/main/bad_usb/helpers/ducky_script.c +++ b/applications/main/bad_usb/helpers/ducky_script.c @@ -50,13 +50,20 @@ bool ducky_is_line_end(const char chr) { return (chr == ' ') || (chr == '\0') || (chr == '\r') || (chr == '\n'); } -uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_chars) { +uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_modifiers) { uint16_t keycode = ducky_get_keycode_by_name(param); if(keycode != HID_KEYBOARD_NONE) { return keycode; } - if((accept_chars) && (strlen(param) > 0)) { + if(accept_modifiers) { + uint16_t keycode = ducky_get_modifier_keycode_by_name(param); + if(keycode != HID_KEYBOARD_NONE) { + return keycode; + } + } + + if(strlen(param) > 0) { return BADUSB_ASCII_TO_KEY(bad_usb, param[0]) & 0xFF; } return 0; @@ -213,7 +220,7 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) { // Main key char next_char = *line_cstr; - key = modifiers | ducky_get_keycode(bad_usb, line_cstr, true); + key = modifiers | ducky_get_keycode(bad_usb, line_cstr, false); if(key == 0 && next_char) ducky_error(bad_usb, "No keycode defined for %s", line_cstr); diff --git a/applications/main/bad_usb/helpers/ducky_script_commands.c b/applications/main/bad_usb/helpers/ducky_script_commands.c index 6c6fe36c7..dbf9c1aef 100644 --- a/applications/main/bad_usb/helpers/ducky_script_commands.c +++ b/applications/main/bad_usb/helpers/ducky_script_commands.c @@ -92,7 +92,7 @@ static int32_t ducky_fnc_sysrq(BadUsbScript* bad_usb, const char* line, int32_t UNUSED(param); line = &line[ducky_get_command_len(line) + 1]; - uint16_t key = ducky_get_keycode(bad_usb, line, true); + uint16_t key = ducky_get_keycode(bad_usb, line, false); bad_usb->hid->kb_press(bad_usb->hid_inst, KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN); bad_usb->hid->kb_press(bad_usb->hid_inst, key); bad_usb->hid->release_all(bad_usb->hid_inst); @@ -196,7 +196,7 @@ static int32_t ducky_fnc_globe(BadUsbScript* bad_usb, const char* line, int32_t UNUSED(param); line = &line[ducky_get_command_len(line) + 1]; - uint16_t key = ducky_get_keycode(bad_usb, line, true); + uint16_t key = ducky_get_keycode(bad_usb, line, false); if(key == HID_KEYBOARD_NONE) { return ducky_error(bad_usb, "No keycode defined for %s", line); } diff --git a/applications/main/bad_usb/helpers/ducky_script_i.h b/applications/main/bad_usb/helpers/ducky_script_i.h index d735a8407..e5e0d645c 100644 --- a/applications/main/bad_usb/helpers/ducky_script_i.h +++ b/applications/main/bad_usb/helpers/ducky_script_i.h @@ -50,7 +50,7 @@ struct BadUsbScript { size_t string_print_pos; }; -uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_chars); +uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_modifiers); uint32_t ducky_get_command_len(const char* line); @@ -58,6 +58,8 @@ bool ducky_is_line_end(const char chr); uint16_t ducky_get_next_modifier_keycode_by_name(const char** param); +uint16_t ducky_get_modifier_keycode_by_name(const char* param); + uint16_t ducky_get_keycode_by_name(const char* param); uint16_t ducky_get_media_keycode_by_name(const char* param); diff --git a/applications/main/bad_usb/helpers/ducky_script_keycodes.c b/applications/main/bad_usb/helpers/ducky_script_keycodes.c index ce957bb4e..77cc324d3 100644 --- a/applications/main/bad_usb/helpers/ducky_script_keycodes.c +++ b/applications/main/bad_usb/helpers/ducky_script_keycodes.c @@ -131,6 +131,18 @@ uint16_t ducky_get_next_modifier_keycode_by_name(const char** param) { return HID_KEYBOARD_NONE; } +uint16_t ducky_get_modifier_keycode_by_name(const char* param) { + for(size_t i = 0; i < COUNT_OF(ducky_modifier_keys); i++) { + size_t key_cmd_len = strlen(ducky_modifier_keys[i].name); + if((strncmp(param, ducky_modifier_keys[i].name, key_cmd_len) == 0) && + (ducky_is_line_end(param[key_cmd_len]))) { + return ducky_modifier_keys[i].keycode; + } + } + + return HID_KEYBOARD_NONE; +} + uint16_t ducky_get_keycode_by_name(const char* param) { for(size_t i = 0; i < COUNT_OF(ducky_keys); i++) { size_t key_cmd_len = strlen(ducky_keys[i].name); From 8ef9a07608949513e4c30fb990da4b3195a8f89b Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 9 Jul 2025 04:54:34 +0300 Subject: [PATCH 67/75] Subghz V2 Phoenix fully supported now With big thanks to all contributors 2022.08 - @Skorpionm 2025.07 - @xMasterX & @RocketGod-git --- .../main/subghz/helpers/subghz_custom_event.h | 1 + .../helpers/subghz_txrx_create_protocol_key.c | 28 ++ .../helpers/subghz_txrx_create_protocol_key.h | 7 + .../subghz/scenes/subghz_scene_set_type.c | 30 +- lib/subghz/protocols/phoenix_v2.c | 280 +++++++++++++++++- lib/subghz/protocols/public_api.h | 16 + targets/f7/api_symbols.csv | 1 + 7 files changed, 352 insertions(+), 11 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index a3dae60b8..e971bd056 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -74,6 +74,7 @@ typedef enum { SetTypeSomfyTelis, SetTypeANMotorsAT4, SetTypeAlutechAT4N, + SetTypePhoenix_V2_433, SetTypeHCS101_433_92, SetTypeDoorHan_315_00, SetTypeDoorHan_433_92, diff --git a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c index 813b706b6..5a7e07e0e 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c @@ -384,6 +384,34 @@ bool subghz_txrx_gen_secplus_v1_protocol( return ret; } +bool subghz_txrx_gen_phoenix_v2_protocol( + void* context, + const char* preset_name, + uint32_t frequency, + uint32_t serial, + uint16_t cnt) { + SubGhzTxRx* txrx = context; + + bool res = false; + + txrx->transmitter = + subghz_transmitter_alloc_init(txrx->environment, SUBGHZ_PROTOCOL_PHOENIX_V2_NAME); + subghz_txrx_set_preset(txrx, preset_name, frequency, NULL, 0); + + if(txrx->transmitter && subghz_protocol_phoenix_v2_create_data( + subghz_transmitter_get_protocol_instance(txrx->transmitter), + txrx->fff_data, + serial, + cnt, + txrx->preset)) { + res = true; + } + + subghz_transmitter_free(txrx->transmitter); + + return res; +} + void subghz_txrx_gen_serial_gangqi(uint64_t* result_key) { uint64_t randkey = (uint64_t)rand(); uint16_t serial = (uint16_t)((randkey) & 0xFFFF); diff --git a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h index fba7acb6f..7daa61b31 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h @@ -115,6 +115,13 @@ bool subghz_txrx_gen_came_atomo_protocol( uint32_t serial, uint16_t cnt); +bool subghz_txrx_gen_phoenix_v2_protocol( + void* context, + const char* preset_name, + uint32_t frequency, + uint32_t serial, + uint16_t cnt); + /** * Generate data SecPlus v2 protocol * diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index 134d2e1d1..3f2fd9eff 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -20,6 +20,7 @@ static const char* submenu_names[SetTypeMAX] = { [SetTypeSomfyTelis] = "Somfy Telis 433MHz", [SetTypeANMotorsAT4] = "AN-Motors AT4 433MHz", [SetTypeAlutechAT4N] = "Alutech AT4N 433MHz", + [SetTypePhoenix_V2_433] = "V2 Phoenix 433MHz", [SetTypeHCS101_433_92] = "KL: HCS101 433MHz", [SetTypeDoorHan_315_00] = "KL: DoorHan 315MHz", [SetTypeDoorHan_433_92] = "KL: DoorHan 433MHz", @@ -112,6 +113,7 @@ typedef enum { GenNiceFlorS, GenSecPlus1, GenSecPlus2, + GenPhoenixV2, } GenType; typedef struct { @@ -170,6 +172,10 @@ typedef struct { uint8_t btn; uint32_t cnt; } sec_plus_2; + struct { + uint32_t serial; + uint16_t cnt; + } phoenix_v2; }; } GenInfo; @@ -634,16 +640,16 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { .type = GenCameAtomo, .mod = "AM650", .freq = 433920000, - .keeloq.serial = (key & 0x0FFFFFFF) | 0x10000000, - .keeloq.cnt = 0x03}; + .came_atomo.serial = (key & 0x0FFFFFFF) | 0x10000000, + .came_atomo.cnt = 0x03}; break; case SetTypeCameAtomo868: gen_info = (GenInfo){ .type = GenCameAtomo, .mod = "AM650", .freq = 868350000, - .keeloq.serial = (key & 0x0FFFFFFF) | 0x10000000, - .keeloq.cnt = 0x03}; + .came_atomo.serial = (key & 0x0FFFFFFF) | 0x10000000, + .came_atomo.cnt = 0x03}; break; case SetTypeBFTMitto: gen_info = (GenInfo){ @@ -869,6 +875,14 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { .sec_plus_2.btn = 0x68, .sec_plus_2.cnt = 0xE500000}; break; + case SetTypePhoenix_V2_433: + gen_info = (GenInfo){ + .type = GenPhoenixV2, + .mod = "AM650", + .freq = 433920000, + .phoenix_v2.serial = (key & 0x0FFFFFFF) | 0xB0000000, + .phoenix_v2.cnt = 0x025D}; + break; default: furi_crash("Not implemented"); break; @@ -976,6 +990,14 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { gen_info.sec_plus_2.btn, gen_info.sec_plus_2.cnt); break; + case GenPhoenixV2: + generated_protocol = subghz_txrx_gen_phoenix_v2_protocol( + subghz->txrx, + gen_info.mod, + gen_info.freq, + gen_info.phoenix_v2.serial, + gen_info.phoenix_v2.cnt); + break; default: furi_crash("Not implemented"); break; diff --git a/lib/subghz/protocols/phoenix_v2.c b/lib/subghz/protocols/phoenix_v2.c index 9e88324c4..0229aeaeb 100644 --- a/lib/subghz/protocols/phoenix_v2.c +++ b/lib/subghz/protocols/phoenix_v2.c @@ -6,9 +6,9 @@ #include "../blocks/generic.h" #include "../blocks/math.h" -#define TAG "SubGhzProtocolPhoenixV2" +#include "../blocks/custom_btn_i.h" -//transmission only static mode +#define TAG "SubGhzProtocolPhoenixV2" static const SubGhzBlockConst subghz_protocol_phoenix_v2_const = { .te_short = 427, @@ -62,7 +62,7 @@ const SubGhzProtocolEncoder subghz_protocol_phoenix_v2_encoder = { const SubGhzProtocol subghz_protocol_phoenix_v2 = { .name = SUBGHZ_PROTOCOL_PHOENIX_V2_NAME, - .type = SubGhzProtocolTypeStatic, + .type = SubGhzProtocolTypeDynamic, .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, @@ -91,6 +91,138 @@ void subghz_protocol_encoder_phoenix_v2_free(void* context) { free(instance); } +// Pre define functions +static uint16_t subghz_protocol_phoenix_v2_encrypt_counter(uint64_t full_key, uint16_t counter); +static void subghz_protocol_phoenix_v2_check_remote_controller(SubGhzBlockGeneric* instance); + +bool subghz_protocol_phoenix_v2_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint16_t cnt, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolEncoderPhoenix_V2* instance = context; + instance->generic.btn = 0x1; + instance->generic.serial = serial; + instance->generic.cnt = cnt; + instance->generic.data_count_bit = 52; + + uint64_t local_data_rev = + (uint64_t)(((uint64_t)instance->generic.cnt << 40) | + ((uint64_t)instance->generic.btn << 32) | (uint64_t)instance->generic.serial); + + uint16_t encrypted_counter = (uint16_t)subghz_protocol_phoenix_v2_encrypt_counter( + local_data_rev, instance->generic.cnt); + + instance->generic.data = subghz_protocol_blocks_reverse_key( + (uint64_t)(((uint64_t)encrypted_counter << 40) | ((uint64_t)instance->generic.btn << 32) | + (uint64_t)instance->generic.serial), + instance->generic.data_count_bit + 4); + + return SubGhzProtocolStatusOk == + subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +// Get custom button code +static uint8_t subghz_protocol_phoenix_v2_get_btn_code(void) { + uint8_t custom_btn_id = subghz_custom_btn_get(); + uint8_t original_btn_code = subghz_custom_btn_get_original(); + uint8_t btn = original_btn_code; + + // Set custom button + if((custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) && (original_btn_code != 0)) { + // Restore original button code + btn = original_btn_code; + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_UP) { + switch(original_btn_code) { + case 0x1: + btn = 0x2; + break; + case 0x2: + btn = 0x1; + break; + case 0x4: + btn = 0x1; + break; + case 0x8: + btn = 0x1; + break; + case 0x3: + btn = 0x1; + break; + + default: + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_DOWN) { + switch(original_btn_code) { + case 0x1: + btn = 0x4; + break; + case 0x2: + btn = 0x4; + break; + case 0x4: + btn = 0x2; + break; + case 0x8: + btn = 0x4; + break; + case 0x3: + btn = 0x4; + break; + + default: + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_LEFT) { + switch(original_btn_code) { + case 0x1: + btn = 0x8; + break; + case 0x2: + btn = 0x8; + break; + case 0x4: + btn = 0x8; + break; + case 0x8: + btn = 0x2; + break; + case 0x3: + btn = 0x8; + break; + + default: + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_RIGHT) { + switch(original_btn_code) { + case 0x1: + btn = 0x3; + break; + case 0x2: + btn = 0x3; + break; + case 0x4: + btn = 0x3; + break; + case 0x8: + btn = 0x3; + break; + case 0x3: + btn = 0x2; + break; + + default: + break; + } + } + + return btn; +} + /** * Generating an upload from data. * @param instance Pointer to a SubGhzProtocolEncoderPhoenix_V2 instance @@ -107,6 +239,40 @@ static bool } else { instance->encoder.size_upload = size_upload; } + + uint8_t btn = instance->generic.btn; + + // Save original button for later use + if(subghz_custom_btn_get_original() == 0) { + subghz_custom_btn_set_original(btn); + } + + // Get custom button code + // This will override the btn variable if a custom button is set + btn = subghz_protocol_phoenix_v2_get_btn_code(); + + // Reconstruction of the data + if(instance->generic.cnt < 0xFFFF) { + if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + instance->generic.cnt = 0; + } else { + instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); + } + } else if((instance->generic.cnt >= 0xFFFF) && (furi_hal_subghz_get_rolling_counter_mult() != 0)) { + instance->generic.cnt = 0; + } + + uint64_t local_data_rev = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit + 4); + + uint16_t encrypted_counter = (uint16_t)subghz_protocol_phoenix_v2_encrypt_counter( + local_data_rev, instance->generic.cnt); + + instance->generic.data = subghz_protocol_blocks_reverse_key( + (uint64_t)(((uint64_t)encrypted_counter << 40) | ((uint64_t)btn << 32) | + (uint64_t)instance->generic.serial), + instance->generic.data_count_bit + 4); + //Send header instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)subghz_protocol_phoenix_v2_const.te_short * 60); @@ -149,10 +315,22 @@ SubGhzProtocolStatus flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + subghz_protocol_phoenix_v2_check_remote_controller(&instance->generic); + if(!subghz_protocol_encoder_phoenix_v2_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; } + + uint8_t key_data[sizeof(uint64_t)] = {0}; + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF; + } + if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Key"); + break; + } + instance->encoder.is_running = true; } while(false); @@ -274,16 +452,103 @@ void subghz_protocol_decoder_phoenix_v2_feed(void* context, bool level, uint32_t } } +static uint16_t subghz_protocol_phoenix_v2_encrypt_counter(uint64_t full_key, uint16_t counter) { + uint8_t xor_key1 = (uint8_t)(full_key >> 24); // First byte of serial + uint8_t xor_key2 = (uint8_t)((full_key >> 16) & 0xFF); // Second byte of serial + + uint8_t byte2 = (uint8_t)(counter >> 8); // First counter byte + uint8_t byte1 = (uint8_t)(counter & 0xFF); // Second counter byte + + // See decrypt function before reading these comments + for(int i = 0; i < 16; i++) { + // The key to reversing the process is that the MSB of the *current* byte2 + // tells us what the MSB of the *previous* byte1 was. This allows us to + // determine if the conditional XOR was applied before?. + uint8_t msb_of_prev_byte1 = byte2 & 0x80; + + if(msb_of_prev_byte1 == 0) { + // reverse the XOR. + byte2 ^= xor_key2; + byte1 ^= xor_key1; + } + + // Perform the bit shuffle in reverse + // Store the least significant bit (LSB) of the current byte1. + uint8_t lsb_of_current_byte1 = byte1 & 1; + + byte2 = (byte2 << 1) | lsb_of_current_byte1; + byte1 = (byte1 >> 1) | msb_of_prev_byte1; + } + + return (uint16_t)byte1 << 8 | byte2; +} + +static uint16_t subghz_protocol_phoenix_v2_decrypt_counter(uint64_t full_key) { + uint16_t encrypted_value = (uint16_t)((full_key >> 40) & 0xFFFF); + + uint8_t byte1 = (uint8_t)(encrypted_value >> 8); // First encrypted counter byte + uint8_t byte2 = (uint8_t)(encrypted_value & 0xFF); // Second encrypted counter byte + + uint8_t xor_key1 = (uint8_t)(full_key >> 24); // First byte of serial + uint8_t xor_key2 = (uint8_t)((full_key >> 16) & 0xFF); // Second byte of serial + + for(int i = 0; i < 16; i++) { + // Store the most significant bit (MSB) of byte1. + // The check `(msb_of_byte1 == 0)` will determine if we apply the XOR keys. + uint8_t msb_of_byte1 = byte1 & 0x80; + + // Store the least significant bit (LSB) of byte2. + uint8_t lsb_of_byte2 = byte2 & 1; + + // Perform a bit shuffle between the two bytes + byte2 = (byte2 >> 1) | msb_of_byte1; + byte1 = (byte1 << 1) | lsb_of_byte2; + + // Conditionally apply the XOR keys based on the original MSB of byte1. + if(msb_of_byte1 == 0) { + byte1 ^= xor_key1; + // The mask `& 0x7F` clears the MSB of byte2 after the XOR. + byte2 = (byte2 ^ xor_key2) & 0x7F; + } + } + + return (uint16_t)byte2 << 8 | byte1; +} + /** * Analysis of received data * @param instance Pointer to a SubGhzBlockGeneric* instance */ static void subghz_protocol_phoenix_v2_check_remote_controller(SubGhzBlockGeneric* instance) { + // 2022.08 - @Skorpionm + // 2025.07 - @xMasterX & @RocketGod-git + // Fully supported now, with button switch and add manually + // + // Key samples + // Full key example: 0xC63E01B9615720 - after subghz_protocol_blocks_reverse_key was applied + // Serial - B9615720 + // Button - 01 + // Encrypted -> Decrypted counters + // C63E - 025C + // BCC1 - 025D + // 3341 - 025E + // 49BE - 025F + // 99D3 - 0260 + // E32C - 0261 + uint64_t data_rev = subghz_protocol_blocks_reverse_key(instance->data, instance->data_count_bit + 4); + instance->serial = data_rev & 0xFFFFFFFF; - instance->cnt = (data_rev >> 40) & 0xFFFF; + instance->cnt = subghz_protocol_phoenix_v2_decrypt_counter(data_rev); instance->btn = (data_rev >> 32) & 0xF; + // encrypted cnt is (data_rev >> 40) & 0xFFFF + + // Save original button for later use + if(subghz_custom_btn_get_original() == 0) { + subghz_custom_btn_set_original(instance->btn); + } + subghz_custom_btn_set_max(4); } uint8_t subghz_protocol_decoder_phoenix_v2_get_hash_data(void* context) { @@ -321,12 +586,13 @@ void subghz_protocol_decoder_phoenix_v2_get_string(void* context, FuriString* ou "%s %dbit\r\n" "Key:%05lX%08lX\r\n" "Sn:0x%07lX \r\n" - "Btn:%X Cnt: 0x%04lX\r\n", + "Cnt: 0x%04lX\r\n" + "Btn: %X\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)(instance->generic.data >> 32) & 0xFFFFFFFF, (uint32_t)(instance->generic.data & 0xFFFFFFFF), instance->generic.serial, - instance->generic.btn, - instance->generic.cnt); + instance->generic.cnt, + instance->generic.btn); } diff --git a/lib/subghz/protocols/public_api.h b/lib/subghz/protocols/public_api.h index d7ae21c2a..39c08e6aa 100644 --- a/lib/subghz/protocols/public_api.h +++ b/lib/subghz/protocols/public_api.h @@ -123,6 +123,22 @@ bool subghz_protocol_came_atomo_create_data( uint16_t cnt, SubGhzRadioPreset* preset); +/** + * Key generation from simple data. + * @param context Pointer to a SubGhzProtocolEncoderPhoenix_V2 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number + * @param cnt Counter value, 16 bit + * @param preset Modulation, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_phoenix_v2_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint16_t cnt, + SubGhzRadioPreset* preset); + /** * New remote generation. * @param context Pointer to a SubGhzProtocolEncoderNiceFlorS instance diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index d0cea83e2..7696a05bc 100755 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -3621,6 +3621,7 @@ Function,+,subghz_protocol_faac_slh_create_data,_Bool,"void*, FlipperFormat*, ui Function,+,subghz_protocol_keeloq_bft_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, uint32_t, const char*, SubGhzRadioPreset*" Function,+,subghz_protocol_keeloq_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, const char*, SubGhzRadioPreset*" Function,+,subghz_protocol_nice_flor_s_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, SubGhzRadioPreset*, _Bool" +Function,+,subghz_protocol_phoenix_v2_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint16_t, SubGhzRadioPreset*" Function,+,subghz_protocol_raw_file_encoder_worker_set_callback_end,void,"SubGhzProtocolEncoderRAW*, SubGhzProtocolEncoderRAWCallbackEnd, void*" Function,+,subghz_protocol_raw_gen_fff_data,void,"FlipperFormat*, const char*, const char*" Function,+,subghz_protocol_raw_get_sample_write,size_t,SubGhzProtocolDecoderRAW* From 06b29ddc1bedae78ab8605c66838b3ca068691b9 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 9 Jul 2025 04:59:01 +0300 Subject: [PATCH 68/75] upd changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index beab8f799..8e9eff410 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## Main changes - Current API: 86.0 +* SubGHz: V2 Phoenix full support (button switch, add manually, counter decrypt/encrypt) (by @xMasterX & @RocketGod-git, original code by @Skorpionm) +* SubGHz: Reduce less popular freqs in default hopper preset, make it faster +* SubGHz: Marantec protocol implement crc verification display and add manually support (by @xMasterX & @li0ard, original code by @Skorpionm) +* SubGHz: Keeloq: Comunello - add manually support * iButton: TM01x Dallas write support (PR #899 | by @Leptopt1los) * SubGHz: Rename and extend Alarms, Sensors, Cars ignore options (Alarms: Hollarm, GangQi | Cars: Kia, Starline, ScherKhan | Sensors: Magellan, Honeywell, Honeywell WDB (doorbells), Legrand (doorbells), Feron (RGB lights)) * SubGHz: V2 Phoenix show counter value From 30621b2fd7367115e868a31f7f9ed70448170d35 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 9 Jul 2025 05:47:38 +0300 Subject: [PATCH 69/75] Update keeloq keys, motorline add manually support, readme spoiler alert Add keeloq keys: by @xMasterX & @RocketGod-git --- CHANGELOG.md | 22 +-- ReadMe.md | 12 +- .../main/subghz/helpers/subghz_custom_event.h | 1 + .../resources/subghz/assets/keeloq_mfcodes | 126 +++++++++--------- .../subghz/scenes/subghz_scene_set_type.c | 11 ++ lib/subghz/protocols/keeloq.c | 13 +- 6 files changed, 109 insertions(+), 76 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e9eff410..feb903787 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,23 +1,25 @@ ## Main changes - Current API: 86.0 -* SubGHz: V2 Phoenix full support (button switch, add manually, counter decrypt/encrypt) (by @xMasterX & @RocketGod-git, original code by @Skorpionm) -* SubGHz: Reduce less popular freqs in default hopper preset, make it faster -* SubGHz: Marantec protocol implement crc verification display and add manually support (by @xMasterX & @li0ard, original code by @Skorpionm) -* SubGHz: Keeloq: Comunello - add manually support -* iButton: TM01x Dallas write support (PR #899 | by @Leptopt1los) -* SubGHz: Rename and extend Alarms, Sensors, Cars ignore options (Alarms: Hollarm, GangQi | Cars: Kia, Starline, ScherKhan | Sensors: Magellan, Honeywell, Honeywell WDB (doorbells), Legrand (doorbells), Feron (RGB lights)) +* SubGHz: **Roger (static 28 bit) with add manually support** (by @xMasterX & @mishamyte) +* SubGHz: **V2 Phoenix full support** (button switch, add manually, counter decrypt/encrypt) (by @xMasterX & @RocketGod-git, original code by @Skorpionm) +* SubGHz: **Keeloq: Add support for - Motorline (with add manually support), Rosh, Pecinin, Rossi, Merlin, Steelmate** (by @xMasterX & @RocketGod-git) +* SubGHz: Reduce less popular freqs in default hopper preset, **make it faster** +* SubGHz: **Marantec protocol implement crc verification display and add manually support** (by @xMasterX & @li0ard, original code by @Skorpionm) +* SubGHz: **Keeloq: Comunello - add manually support** +* iButton: **TM01x Dallas write support** (PR #899 | by @Leptopt1los) +* SubGHz: Rename and **extend Alarms, Sensors, Cars ignore options** (Alarms: Hollarm, GangQi | Cars: Kia, Starline, ScherKhan | Sensors: Magellan, Honeywell, Honeywell WDB (doorbells), Legrand (doorbells), Feron (RGB lights)) * SubGHz: V2 Phoenix show counter value -* SubGHz: Add keeloq ironlogic (aka il100) smart clone cloners support (thanks to Vitaly for RAWs) -* SubGHz: Fix CAME 24bit decoder +* SubGHz: **Add keeloq ironlogic (aka il100) smart clone cloners support** (thanks to Vitaly for RAWs) +* SubGHz: **Fix CAME 24bit decoder** * SubGHz: Add 462.750 MHz & 868.46 MHz to default subghz freqs list -* SubGHz: Tune holtek ht12x to decode holtek only and not conflict with came 12bit +* SubGHz: **Tune holtek ht12x to decode holtek only** and not conflict with came 12bit * SubGHz: Fix Rename scene bug, that was replacing file name with random name when Rename is opened then closed then opened again * Display: Backlight option "always on" and RGB bug removed (PR #900 | by @Dmitry422) * NFC: Ultralight C - Attempt of authentication with default key (PR #898 | by @mishamyte) * System: Loader - Fix misplaced ApplicationBeforeLoad events (PR #905 | by @WillyJL) * OFW PR 4210: Infrared: Add text scroll to remote buttons (by @956MB) * NFC: - - NFC Type 4 support + many other improvements (by @WillyJL) + - **NFC Type 4 support + many other improvements** (by @WillyJL) - New Type 4 Tag (NDEF on NTAG4xx / MIFARE DESFire) protocol, full support - New NTAG4xx (NTAG413 DNA / NTAG424 DNA) protocol, only detection and basic info support - NDEF parsing plugin supports Type 4 Tag protocol diff --git a/ReadMe.md b/ReadMe.md index 395222fff..eaa221ea7 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -127,7 +127,7 @@ Before getting started: > - 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) -> - **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) +> - **BadKB** (BadUSB) [(by Willy-JL, ClaraCrazy, XFW contributors)](https://github.com/Flipper-XFW/Xtreme-Firmware/tree/dev/applications/main/bad_kb) - (Integrated into BadUSB app now!) - (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 @@ -157,8 +157,9 @@ Thanks to Official team (to their SubGHz Developer, Skorp) for implementing supp > | Cenmax | FAAC_SLH | KGB/Subaru | Pantera_CLK | Tomahawk_Z,X_3-5 | > | Cenmax_St-5 | Faraon | Leopard | Pantera_XS/Jaguar | ZX-730-750-1055 | > | Cenmax_St-7 | Genius_Bravo | Magic_1 | Partisan_RX | IL-100(Smart) | -> | Centurion | Gibidi | Magic_2 | Reff | | -> | Monarch | Jolly Motors | Magic_3 | Sheriff | | +> | Centurion | Gibidi | Magic_2 | Reff | Merlin | +> | Monarch | Jolly Motors | Magic_3 | Sheriff | Steelmate | +> | Motorline | Rosh | Pecinin | Rossi | | >

@@ -166,6 +167,9 @@ Thanks to Official team (to their SubGHz Developer, Skorp) for implementing supp Decoders/Encoders or emulation (+ programming mode) support made by @xMasterX
+- Roger (static 28 bit) with add manually support (by @xMasterX & @mishamyte) +- V2 Phoenix (Phox) (dynamic 52 bit) (by @xMasterX & @RocketGod-git) +- Marantec (static 49 bit) (add manually support and CRC verify) (by @xMasterX & @li0ard) - Feron (static 32 bit) - ReversRB2 / RB2M (static 64 bit) with add manually support - Marantec24 (static 24 bit) with add manually support @@ -174,7 +178,7 @@ Thanks to Official team (to their SubGHz Developer, Skorp) for implementing supp - Hay21 (dynamic 21 bit) with button parsing - 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 !), Hormann EcoStar, Novoferm, Sommer, Monarch (thanks @ashphx !), Jolly Motors (thanks @pkooiman !), IL-100(Smart) (thx Vitaly for RAWs) +- 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 !), Hormann EcoStar, Novoferm, Sommer, Monarch (thanks @ashphx !), Jolly Motors (thanks @pkooiman !), IL-100(Smart) (thx Vitaly for RAWs), Motorline (with add manually support), Rosh, Pecinin, Rossi, Merlin, Steelmate (thanks @RocketGod-git)
diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index e971bd056..cffad2929 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -75,6 +75,7 @@ typedef enum { SetTypeANMotorsAT4, SetTypeAlutechAT4N, SetTypePhoenix_V2_433, + SetTypeMotorline433, SetTypeHCS101_433_92, SetTypeDoorHan_315_00, SetTypeDoorHan_433_92, diff --git a/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes index 3f3e825af..62135c0b9 100644 --- a/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes +++ b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes @@ -1,63 +1,69 @@ Filetype: Flipper SubGhz Keystore File Version: 0 Encryption: 1 -IV: 4E 6F 20 66 75 72 69 20 63 68 65 63 6B 3F 21 3F -2F0767B5B190608EB032D12BFA937D760A77D08D37F851E940767F1915E97ACF -332F8DCCFDBF0485EC2EEED0C279F277E52A86A93BC5E4E96BE5F7276CC66713 -D9A02CC785FC0495063C424B0B1BAE7C120A2C24D4C0EE743F5D216718B16490 -4D9DD617090BDB100986B6987CAAC3652D2ADAB1AD9E368C5806D98562FF6B2F -28D21748FF3826FA13C785A6721CC5927C81EDAB0C5CF31C92EAFF12AA91608298485D8A3AB443640237372ADF0DDC49 -5058E12C0A41EDCB5C0812554F619DADFB6E895B94421952ECD9255A04EE5E1A -83A3EB8B22D94487A6B0F37856FB6AE9F42272BF25E1AE06DE03AA881A12D15F -D0E207DE64402B43ECD0C341216B6BCDC449508116E81D8ACDE7FA0BFBEA56F7 -6C4F723DE3B775D4C07E12ED3C369250B4D2089ADE2207816DED130D4B498CDF -B041911C56555E5F4676BF16819F61BF7A92402EB0427B8C2E7367B0AEA6B53C -1AD460260F20146A763BF6D4CD26DF5139EE29FFF8B53F6C5367EA779E1BEE56D5DFD872EA0268FE27204175925079AA -B1A9331AED36137CD078536A67775E2880D3CD7305373BC44A5649435E466AD2DC9FDE8AC1F572EF094D4B438C9509EA -105819300A9152F16E3478151799ECBBB7CCCE63DADA3F6C6D16D46830E1E035 -354186E04BC90D672F76A427FC1CD35C2EFAE8D4D1C36247FFB71ACB862A3601 -84B533148282D0D8121E5BBBBD39DE16F398365B015E02417ECC535C88EB9C57 -E899C9DC779F82E798EE923D257D5F051E1254DCDA2A6A955882727AA8E98ED8 -B8EC34F9B75E61D34E9C075A5316FAFC395E8FBA4F76B215620C5B5C76C53DB7BF316E53582161AD20F64CAB8E3347B2 -966C3B0873F48889B51D7D9DACC0674CBC95B06E231E30F235C826285D529A54 -370DED014764D7278E342D3AB631AB37D8F8A8FAE08987E03D7FC279DEEEB913 -2318A2DA42EEA2A35FFC9BDFBB244DF3FF6933010D74B3465336C0E37FFDC48A200568F8D6003AB215388734B8AC1F20 -475B35437FECEE9792F53A671252E78566AA9894DE7A4DEC9AED70834864E804E87478009F424CE1424C00F162BB03C5 -01CE6251ED9682BA6366075081167196CD740D346C4DAC4E0012C7951C475AE7 -CB225891F937CA491B711AA942B04C61C7CFA6A8E912D91D0906B401737E03B4 -F35D279815DEF19C9C5BC9ED55E7C1A0366E37DCD0A93E6E168B5A2602201C7B -3569D8DF2490797D40978F8325B94CC707383FEA1B46240BFDAECFEFB1F8176D -3D7BAF13573BBF3102757C68D52236638CC57126FF3795A604CFFA2D3F1B9C26 -B9102C87D7DBCF35463F38B6B80B70408968B6E01A47F6A7E8A3E87A0577B4ED -7673FAC14D94ABF72800A78E2DC4CAF2166FBB24719C22CFC1010492F4C87734 -1AF74DA07EA3A418EB86BB7ABAD6192B8E5A53F61B3E74CB898CB3EE4A7E244A -832D18C44062DDE856384E19D1417FA48D809C2CB2107CDEC5281943559791A6 -CD482A8FAB2A2CBE25A0B4A4788F274CA7095AA24508C00DBB78DD12BFB11C37 -EAC52E802DB76B51058752D7EFA91BCB1212AB96B589F9A88465195C1DE3242E -96CC75952A513AB5FE62A69AB6CDDA93C2156A3EA607C25B3201CE7284B3DAA9 -986E71EE87E860192141A1453929E575706E3FE72B7A9FEF5ACA696388649EB6 -FFF89FECC1C01FA3F266B95BDEF61A16F514E59599DAA07E908C604E9FE686C0 -ACC159D4AE78E26B5A1468F69D961028D0BF962D03E865415E7FE746553FEF15 -0FF46B2F9D4E907B9924675081D17C38C09957AA2F4C3C1F5568461DBA850F6301328CDC0FCEE83C7E8BA00CF8FC0F97 -7FD793C05E499739C3C4F8CC1D2D205A55928AB5BC60752A81D86DFBE30C50BD -CE444F4A1BEB38C9E93579E1B7FB3E90B4F85D8DA94DFC622272DED093A35192 -C7C31D8AB9D717FAF842F850A3E0D6B404EB9A980D9679362ABA90594B1957AB -1D48A6CFFBB6F14DD5BED1F8E74E6CC0292150546EDD76361792677434A8FE5F -F7335B8877DDF17F0737ECF77E2E6286E78534CE28E72077360E8E24D7750DFE -51051D9A8D5941661EBCF437B4640E6DA0B9B27518D761F3EF26BF7EABC691D4 -79F279733E18393FEDB50D654A0D0A532A64BED5ACBD13319439EEC007BC359C -646666FDB75D439C0427A9E3EF47F145DBD4FF5FE2E244909D74F075B24FF5A9B47E7AF98271210057D937A0E4B1F46D -DE7E814A2BD4D8823D9F2380EFAFFA1380A90391F87CBF24CE46BD46205EABAB -1335C4C3E819E942F5C212E9BEFAF5D984316C0A2FF6E9886886B565625618A9 -65386F906F18FF9C3A20AB57F3241D4975FE312ACDEB7FB1B91F2B816CAA46E7 -DF8A8B33782D56667F4C98F8F91B49B71A9E83AF015D8841986D41663233A0DC -27264455248878BB226FA1DED0922BD10313FF65F8A6A0E3CCDFB77890C838BB -43A08F784F36A3E8049BA63A401F3F15B3CA2ED263F8638595B5F22A0B081369 -F9F82F89C15AD970320E3D7A83B272EB00CD0ED657E4D230AB33C995859EA77F -70AD020D172E18E1011DF88E5F934F03F34DCE8148F8053B4FFA6F92CAC9FC93 -2B845F67BAB432CED64F2D68454A2B4B3BC46FFDC2A16D3340360C7BEA110BBB -B85F16A2370B278FDB3C85E6455B8DA239D6413B727839DEFBCB5628A6C747266291AB9D9F4F5DA1826B219C1A29F956 -FFB7B10D96F241FDB994008AF85EC85D147A97AA599D05F5EE1BB2FC27644A26 -0BD42CA312CBBCAE556AA0159EC2CC2FA70BBB00D8DF7B63BBEA60A282481AED -9CC73810056A21EA6F311B01BA7F44655A075D1F60947FBC9B6924C3BD0ED819 -024FCB96977ECA1C0D4B9C7C461361329D96E5AFF315124FEFC0DF2A400DE312F45D602DB40CD4EB088F144EB0B8DF41 +IV: 46 75 72 72 79 20 52 6F 63 6B 65 74 21 21 21 30 +05176EEFAC177FE261FE3EB5C8E103BE7CF9F2FEB32BDD6BB63D22EE9C17B9D2 +B645E3CAC0D5E26891249D326BCEB09850E4FB8F8E86A466E97E83437A9E0041 +AA4255FFA1ADE8FB840F80A93F8F1A2D1E39051131D24DE7258D66A8CF2066CF +13ACA390FD5254B024084D5D1F41B8DDF5304FF00C3C85A9C26CD13A7A268654 +4CFBF498D5E2C85496985E83D91B0F4229A925E16A90C6712750032C3699EE0AA5D04123E579B6121573FC61766E89AD +93DADC2AE4235470E171E0E85D24D04A84C37187284C38D1CBB48666FDA8CD6C +DB13D8CCC0CB07685F29F33AE07DA2FD14C2AE4F4D001DB88465D5CFE8CFDAA9 +E51CD1B5074B63D26E274218A0AB3B2E435454EE094DCA5679F35477658A72F9 +10AFD5FD9C296E67EDD9504A60BA9EF84556F40213DEC4DE44F99B088BCC6A57 +EF7AA55F6A473DE093D648240D5FCEB05F8B3295DC37B3E83239A4AF320CD688 +A22892E71B9D0D7FAF92B27C724E76C4A6824DBE5F083F1006D11E42D153C4AC98D0A11C6A8D62F5921A24ECC7437485 +7A25416E390D81DA68A59C3BA30D4B7FC8269B5E0DAF77CA3A857B6F478A050585918485AEE72D375F02D177CB296E31 +94004BA0BB1E47965E60025949EF4CC2738C463F57C97FD2A89C76CCCDEA5397 +111CB1C19863A0165521D974F838CE718DA07948A8D9A8A7490E75032A62ECA2 +17B6E27C69FA002F6CF23D719DFE595140BEFA5083D12E774CF89E2CED53D68D +73311E0FF8ABB3E9461AD14A4F52791647A50E2102D3B74188A73C35BC14EB55 +54E15840A6A6DCA85275E38E4218EE2B539E9E468E24C49428DA363C955C5FC81ACEE79EEB941B83EE4147A0817043BD +7D0FBB417B99B3C6AB18C7B2DC82582D2DCD1E10515028874E73254188F7FEE9 +3F6E89BBCC133B85945234A8201539ECD8796909CC81FE67673F8DE1ECA63045 +39554C0DC1C3694FAAFF65537FF710D9593B7B461E011FC39D014F253F0432533A40276D8259AFD8C957A378237D574F +E60F6CD7063B85F0F20ACB7E7A42B03DE4A9F6CCA54CB7F036AFA23A27D3E9E006BD523E5356260AA78206D9276E6E57 +9EB252EDA9352B966EC4F053D5B013772361D2AD4B217EF33F46A5CEC97A00F3 +AA6773E79BC6D76314BB523FDF203358E01ECB2BBCF3B5DD1EBD043663C74B05 +29B29A50F3F27F4D8C7B0FADA98CC004A7871078DAD1CBAC4846862C3DF82E02 +6E3A479D4334FF05606899B0383116125056A316621B279F904A02B842918C59 +3991732015F4A213E9912E34AC92515D88010C07DA0B118AD6F64A05DC38D2C5 +550B1866F7493C75812DF85DDADC38AF21D9B58189E4EE99A021328523881A9D +77960CA031D28362586100F17DF94FF4E7D6EFAFAF23952887F9DF0507825A99 +01E6FC89E97B7729BF4D1ED8041F69005181BF3639F939C5833B009E96B9F2F7 +D1CC7C536706ECFC5826C8933135D2B110996F1CB13388A702B8453DA40E40AD +B64D2F1E1A80E6DAB92283A512B40DB7FFC519F394AA94CC86C8532F69949723 +6399409A0AC0298DEDA76037C83042FC0870132CFF7F82E54AD0966BE16AC882 +D310536FA78F95BB0B408676990AA937117717BADE9D3B975C0ECE10FB586A1B +A8149C0581DCC291D037E96EF321DB6214BD7CB25F1696226A9FE750AA23B334 +BA3BEBD564D8F571202CD6FE89BC33F89C8E01C03AE0814F2BEF37C33CE874B4 +88CD81AC7605A7F6EFF85FD62C65E0C9945335CFC085B92B27B69648C6E5BF6B +8057C7CB5071DFFFAE4804FD9EC1EC1D3F54D06514906A34B17F6B6CB45A9D473992DF6BC8A9F9E146E39D6163209CC6 +9ABC8814C8FD1AB254374150177616F5C7B43049473C84329BEC855578B96002 +8BCA39A498B00245C71D94E3160CEE8ACA5BEB18AE0AD64A385AFCC018E99744 +5AD75C51CA5AE5FA9BBC6A41576C745F265CC28FC4DA2AD230B6692CF151FD61 +E86092E04CD72D874A92DE838035E811E75E411049C0A7BD0FE2AA9C802BE5AB +CE70ADB22E85747FDC064F0B5974385CD57D41D376CE1C7490C1BEC8A3FC5A7A +8F096E0A11682DB315825213D3DB5D725555C1CDF444169EB919E47E0F0FA6F7 +AD9C9A694D807BA77E5A54B248A88B55000757203D931506255BF8F4215C00D3 +F0E804B6C6B6E91916CB73EB44FB2D1992400BC90ED8B22DF5D038317588341207D74E08C00E529DF2CF2A64F2C7C0FF +72212FCEED35E9C3A176B67DCDB84B284F4DFDCD0ECE8D3F6089C58C2B8A616C +000F9F746BFB47FC10B23E3F08C2A84BCB3870D0C5AE974472849699884BC929 +7B8F9AB04E5F86D6DDCF6164A25EA927788A03F57977FC5C55E1D565279B09C4 +0E9CDCD07D1D4F1429E59F81B524960A75F19A464798C7E822E52728AC83784A +F2DE2B108A1476BB6F85DD3CCB0F0527627B45179092BA7A56D5971490E3875C +7F307358D988FEA12648739F58DD249EBDF0B1C44B73BA547C50EB576C071DAE +2DFBA988592CEF3B62A76183DBA727E734359B89F53AFF3160441EF8709FC633 +57F7DC38DDC87C19CE956BC44C638DEF34D814A7BAB0AC8AD61855143FD984FD +A8AADB687251FA6AC2BBC8EF1E3FA621893293DFBD8C1D07971BF82F22A00DC3 +65AEA1EE34E316C769E551AC2309D07FC2ED92EA044674E3A99CD7B543C730EB +968ECC790E5590E7EB22AFD3546C28F4EB87EA4CEE35F72DDFE7153F74611EAA +0F937930D4E1BDF0B729277CF94A47064BCB959938C70CDB3AC3C65DA68DA1FB +A8AB66375D59E112104CD81B819D618BE43D6A6F159BAD35583653EF3547D25D +A81D5DE2102F05D50750DC37C26E9C9502FA89EF98A2EB1EA546EE48C628E9C4 +EAFDE0A8936AF8EF718027937BC17CEF691E570996B403CF4762240D267EB305 +C48686348F0A94B07BC60AB825C1A0791C20DBBDD7DAE0ED47E8A7FBD9334EACF8E33DCEC36963E87929260DF769520B +493D53BD7BB2B3E081AE793A3BADB3AB0F33C95B83677715D6DE2922F2BEC892 +63FFD3D8CAB980E45D49253A69C99A6813CBE6013992EFBC862173BAD0E26373 +2EF88F43C5A76EC87E02B780585B10957F4EA386F96710FAB98BC2C1E214DBFA +A021CFA0E72AADFD75BC67FBE9345082B0A8B31782E933E81196F84B1797D83E8B2F81E1CF5C3F026D11B9DFC95222E2 diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index 3f2fd9eff..b6ff75768 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -30,6 +30,7 @@ static const char* submenu_names[SetTypeMAX] = { [SetTypeComunello868] = "KL: Comunello 868MHz", [SetTypeAllmatic433] = "KL: Allmatic 433MHz", [SetTypeAllmatic868] = "KL: Allmatic 868MHz", + [SetTypeMotorline433] = "KL: Motorline 433MHz", [SetTypeCenturion433] = "KL: Centurion 433MHz", [SetTypeMonarch433] = "KL: Monarch 433MHz", [SetTypeJollyMotors433] = "KL: Jolly Mot. 433MHz", @@ -680,6 +681,16 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { .somfy_telis.btn = 0x02, .somfy_telis.cnt = 0x03}; break; + case SetTypeMotorline433: + gen_info = (GenInfo){ + .type = GenKeeloq, + .mod = "AM650", + .freq = 433920000, + .keeloq.serial = key & 0x0FFFFFFF, + .keeloq.btn = 0x01, + .keeloq.cnt = 0x03, + .keeloq.manuf = "Motorline"}; + break; case SetTypeDoorHan_433_92: gen_info = (GenInfo){ .type = GenKeeloq, diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index a774e5825..e1ccf8c2f 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -237,9 +237,15 @@ static bool subghz_protocol_keeloq_gen_data( (strcmp(instance->manufacture_name, "Mutanco_Mutancode") == 0) || (strcmp(instance->manufacture_name, "Came_Space") == 0) || (strcmp(instance->manufacture_name, "Genius_Bravo") == 0) || - (strcmp(instance->manufacture_name, "GSN") == 0)) { + (strcmp(instance->manufacture_name, "GSN") == 0) || + (strcmp(instance->manufacture_name, "Rosh") == 0) || + (strcmp(instance->manufacture_name, "Rossi") == 0) || + (strcmp(instance->manufacture_name, "Pecinin") == 0) || + (strcmp(instance->manufacture_name, "Steelmate") == 0)) { // DTM Neo, Came_Space uses 12bit serial -> simple learning // FAAC_RC,XT , Mutanco_Mutancode, Genius_Bravo, GSN 12bit serial -> normal learning + // Rosh, Rossi, Pecinin -> 12bit serial - simple learning + // Steelmate -> 12bit serial - normal learning decrypt = btn << 28 | (instance->generic.serial & 0xFFF) << 16 | instance->generic.cnt; } else if( @@ -249,9 +255,12 @@ static bool subghz_protocol_keeloq_gen_data( // Nice Smilo, MHouse, JCM -> 8bit serial - simple learning decrypt = btn << 28 | (instance->generic.serial & 0xFF) << 16 | instance->generic.cnt; - } else if(strcmp(instance->manufacture_name, "Beninca") == 0) { + } else if( + (strcmp(instance->manufacture_name, "Beninca") == 0) || + (strcmp(instance->manufacture_name, "Merlin") == 0)) { decrypt = btn << 28 | (0x000) << 16 | instance->generic.cnt; // Beninca / Allmatic -> no serial - simple XOR + // Merlin -> no serial - simple XOR } else if(strcmp(instance->manufacture_name, "Centurion") == 0) { decrypt = btn << 28 | (0x1CE) << 16 | instance->generic.cnt; // Centurion -> no serial in hop, uses fixed value 0x1CE - normal learning From 1bed4d29cba326285f3c9942c249aa45991b7cef Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 9 Jul 2025 08:01:36 +0300 Subject: [PATCH 70/75] Nero Radio static - better parsing --- lib/subghz/protocols/nero_radio.c | 42 +++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/lib/subghz/protocols/nero_radio.c b/lib/subghz/protocols/nero_radio.c index 7e787ffd0..d7822ac1c 100644 --- a/lib/subghz/protocols/nero_radio.c +++ b/lib/subghz/protocols/nero_radio.c @@ -385,6 +385,36 @@ SubGhzProtocolStatus } } +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_nero_radio_parse_data(SubGhzBlockGeneric* instance) { + // Key samples from unit tests + // 57250501049DD3 + // 57250502049D13 + // + // Samples from remote + // 36E4E80104A644 + // 36E4E80204A684 + // 36E4E80304A604 + // 36E4E80404A6E4 + + // possible contents + // serial button serial/const crc?? + // 5725050 1 049D D3 + // 5725050 2 049D 13 + // 36E4E80 1 04A6 44 + // 36E4E80 2 04A6 84 + // 36E4E80 3 04A6 04 + // 36E4E80 4 04A6 E4 + + // serial is larger than uint32 can't fit into serial field + // using data2 var since its uint64_t + instance->btn = (instance->data >> 24) & 0xF; + instance->data_2 = ((instance->data >> 28) << 16) | ((instance->data >> 8) & 0xFFFF); +} + void subghz_protocol_decoder_nero_radio_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderNeroRadio* instance = context; @@ -398,15 +428,23 @@ void subghz_protocol_decoder_nero_radio_get_string(void* context, FuriString* ou uint32_t code_found_reverse_hi = code_found_reverse >> 32; uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; + subghz_protocol_nero_radio_parse_data(&instance->generic); + furi_string_cat_printf( output, "%s %dbit\r\n" "Key:0x%lX%08lX\r\n" - "Yek:0x%lX%08lX\r\n", + "Yek:0x%lX%08lX\r\n" + "Sn: 0x%llX \r\n" + "CRC?: 0x%02X\r\n" + "Btn: %X\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, code_found_hi, code_found_lo, code_found_reverse_hi, - code_found_reverse_lo); + code_found_reverse_lo, + instance->generic.data_2, + (uint8_t)(instance->generic.data & 0xFF), + instance->generic.btn); } From 3c3d06bae093925a6158d1ce437f33ce06b97e4e Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 9 Jul 2025 08:02:05 +0300 Subject: [PATCH 71/75] Roger that --- .../main/subghz/helpers/subghz_custom_event.h | 3 +- .../subghz/scenes/subghz_scene_set_type.c | 11 + lib/subghz/protocols/protocol_items.c | 1 + lib/subghz/protocols/protocol_items.h | 1 + lib/subghz/protocols/roger.c | 442 ++++++++++++++++++ lib/subghz/protocols/roger.h | 109 +++++ 6 files changed, 566 insertions(+), 1 deletion(-) create mode 100644 lib/subghz/protocols/roger.c create mode 100644 lib/subghz/protocols/roger.h diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index cffad2929..9fc607889 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -75,7 +75,6 @@ typedef enum { SetTypeANMotorsAT4, SetTypeAlutechAT4N, SetTypePhoenix_V2_433, - SetTypeMotorline433, SetTypeHCS101_433_92, SetTypeDoorHan_315_00, SetTypeDoorHan_433_92, @@ -88,6 +87,7 @@ typedef enum { SetTypeCenturion433, SetTypeMonarch433, SetTypeJollyMotors433, + SetTypeMotorline433, SetTypeSommer_FM_434, SetTypeSommer_FM_868, SetTypeSommer_FM238_434, @@ -130,6 +130,7 @@ typedef enum { SetTypeMarantec24_868, SetTypeMarantec_433, SetTypeMarantec_868, + SetTypeRoger_433, SetTypeLinear_300_00, // SetTypeNeroSketch, //Deleted in OFW // SetTypeNeroRadio, //Deleted in OFW diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index b6ff75768..b986c2c5d 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -20,6 +20,7 @@ static const char* submenu_names[SetTypeMAX] = { [SetTypeSomfyTelis] = "Somfy Telis 433MHz", [SetTypeANMotorsAT4] = "AN-Motors AT4 433MHz", [SetTypeAlutechAT4N] = "Alutech AT4N 433MHz", + [SetTypeRoger_433] = "Roger 433MHz", [SetTypePhoenix_V2_433] = "V2 Phoenix 433MHz", [SetTypeHCS101_433_92] = "KL: HCS101 433MHz", [SetTypeDoorHan_315_00] = "KL: DoorHan 315MHz", @@ -286,6 +287,16 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { .data.bits = 24, .data.te = 0}; break; + case SetTypeRoger_433: + gen_info = (GenInfo){ + .type = GenData, + .mod = "AM650", + .freq = 433920000, + .data.name = SUBGHZ_PROTOCOL_ROGER_NAME, + .data.key = (key & 0xFFFF000) | 0x0000101, // button code 0x1 and (crc?) is 0x01 + .data.bits = 28, + .data.te = 0}; + break; case SetTypeLinear_300_00: gen_info = (GenInfo){ .type = GenData, diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index c73923c7a..465585d77 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -53,6 +53,7 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = { &subghz_protocol_hay21, &subghz_protocol_revers_rb2, &subghz_protocol_feron, + &subghz_protocol_roger, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index 6165d748a..4f63b030e 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -54,3 +54,4 @@ #include "hay21.h" #include "revers_rb2.h" #include "feron.h" +#include "roger.h" diff --git a/lib/subghz/protocols/roger.c b/lib/subghz/protocols/roger.c new file mode 100644 index 000000000..d14547876 --- /dev/null +++ b/lib/subghz/protocols/roger.c @@ -0,0 +1,442 @@ +#include "roger.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#include "../blocks/custom_btn_i.h" + +#define TAG "SubGhzProtocolRoger" + +static const SubGhzBlockConst subghz_protocol_roger_const = { + .te_short = 500, + .te_long = 1000, + .te_delta = 270, + .min_count_bit_for_found = 28, +}; + +struct SubGhzProtocolDecoderRoger { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderRoger { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + RogerDecoderStepReset = 0, + RogerDecoderStepSaveDuration, + RogerDecoderStepCheckDuration, +} RogerDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_roger_decoder = { + .alloc = subghz_protocol_decoder_roger_alloc, + .free = subghz_protocol_decoder_roger_free, + + .feed = subghz_protocol_decoder_roger_feed, + .reset = subghz_protocol_decoder_roger_reset, + + .get_hash_data = subghz_protocol_decoder_roger_get_hash_data, + .serialize = subghz_protocol_decoder_roger_serialize, + .deserialize = subghz_protocol_decoder_roger_deserialize, + .get_string = subghz_protocol_decoder_roger_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_roger_encoder = { + .alloc = subghz_protocol_encoder_roger_alloc, + .free = subghz_protocol_encoder_roger_free, + + .deserialize = subghz_protocol_encoder_roger_deserialize, + .stop = subghz_protocol_encoder_roger_stop, + .yield = subghz_protocol_encoder_roger_yield, +}; + +const SubGhzProtocol subghz_protocol_roger = { + .name = SUBGHZ_PROTOCOL_ROGER_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_AM | + SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | + SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_roger_decoder, + .encoder = &subghz_protocol_roger_encoder, +}; + +void* subghz_protocol_encoder_roger_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderRoger* instance = malloc(sizeof(SubGhzProtocolEncoderRoger)); + + instance->base.protocol = &subghz_protocol_roger; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 256; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_roger_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderRoger* instance = context; + free(instance->encoder.upload); + free(instance); +} + +// Get custom button code +static uint8_t subghz_protocol_roger_get_btn_code(void) { + uint8_t custom_btn_id = subghz_custom_btn_get(); + uint8_t original_btn_code = subghz_custom_btn_get_original(); + uint8_t btn = original_btn_code; + + // Set custom button + if((custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) && (original_btn_code != 0)) { + // Restore original button code + btn = original_btn_code; + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_UP) { + switch(original_btn_code) { + case 0x1: + btn = 0x2; + break; + case 0x2: + btn = 0x1; + break; + case 0x4: + btn = 0x1; + break; + case 0x8: + btn = 0x1; + break; + + default: + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_DOWN) { + switch(original_btn_code) { + case 0x1: + btn = 0x4; + break; + case 0x2: + btn = 0x4; + break; + case 0x4: + btn = 0x2; + break; + case 0x8: + btn = 0x4; + break; + + default: + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_LEFT) { + switch(original_btn_code) { + case 0x1: + btn = 0x8; + break; + case 0x2: + btn = 0x8; + break; + case 0x4: + btn = 0x8; + break; + case 0x8: + btn = 0x2; + break; + + default: + break; + } + } + + return btn; +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderRoger instance + */ +static void subghz_protocol_encoder_roger_get_upload(SubGhzProtocolEncoderRoger* instance) { + furi_assert(instance); + size_t index = 0; + + uint8_t btn = instance->generic.btn; + + // Save original button for later use + if(subghz_custom_btn_get_original() == 0) { + subghz_custom_btn_set_original(btn); + } + + // Get custom button code + // This will override the btn variable if a custom button is set + btn = subghz_protocol_roger_get_btn_code(); + + // If CRC is not == button - transmit as is, no custom button allowed + if((instance->generic.data & 0xFF) == instance->generic.btn) { + instance->generic.data = (uint64_t)instance->generic.serial << 12 | ((uint64_t)btn << 8) | + btn; + } + + // Send key and GAP + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + // Send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_roger_const.te_long); + if(i == 1) { + //Send gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_roger_const.te_short * 19); + } else { + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_roger_const.te_short); + } + } else { + // Send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_roger_const.te_short); + if(i == 1) { + //Send gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_roger_const.te_short * 19); + } else { + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_roger_const.te_long); + } + } + } + + instance->encoder.size_upload = index; + return; +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_roger_check_remote_controller(SubGhzBlockGeneric* instance) { + // Roger Decoder + // 2025.07 - @xMasterX (MMX) + + // Key samples + // 0010001111111001 0001 00100000 // S/N: 0x23F9 Btn: 0x1 CRC: 0x20 + // 0010001111111001 0010 00100011 // S/N: 0x23F9 Btn: 0x2 CRC: 0x23 + // 0101011001010110 0001 00000001 // S/N: 0x5656 Btn: 0x1 CRC: 0x01 + // 0101011001010110 0010 00000010 // S/N: 0x5656 Btn: 0x2 CRC: 0x02 + // 0000110111111110 0001 00000001 // S/N: 0x0DFE Btn: 0x1 CRC: 0x01 + // 0000110111111110 0100 00000100 // S/N: 0x0DFE Btn: 0x4 CRC: 0x04 + // 0000110111111110 0010 00000010 // S/N: 0x0DFE Btn: 0x2 CRC: 0x02 + // 0000110111111110 1000 00001000 // S/N: 0x0DFE Btn: 0x8 CRC: 0x08 + + instance->serial = instance->data >> 12; + instance->btn = (instance->data >> 8) & 0xF; + + // Save original button for later use + if(subghz_custom_btn_get_original() == 0) { + subghz_custom_btn_set_original(instance->btn); + } + subghz_custom_btn_set_max(3); +} + +SubGhzProtocolStatus + subghz_protocol_encoder_roger_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderRoger* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_roger_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_roger_check_remote_controller(&instance->generic); + subghz_protocol_encoder_roger_get_upload(instance); + + uint8_t key_data[sizeof(uint64_t)] = {0}; + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF; + } + if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Key"); + break; + } + + instance->encoder.is_running = true; + } while(false); + + return ret; +} + +void subghz_protocol_encoder_roger_stop(void* context) { + SubGhzProtocolEncoderRoger* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_roger_yield(void* context) { + SubGhzProtocolEncoderRoger* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_roger_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderRoger* instance = malloc(sizeof(SubGhzProtocolDecoderRoger)); + instance->base.protocol = &subghz_protocol_roger; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_roger_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderRoger* instance = context; + free(instance); +} + +void subghz_protocol_decoder_roger_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderRoger* instance = context; + instance->decoder.parser_step = RogerDecoderStepReset; +} + +void subghz_protocol_decoder_roger_feed(void* context, bool level, volatile uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderRoger* instance = context; + + switch(instance->decoder.parser_step) { + case RogerDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_roger_const.te_short * 19) < + subghz_protocol_roger_const.te_delta * 3)) { + //Found GAP + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = RogerDecoderStepSaveDuration; + } + break; + case RogerDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = RogerDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = RogerDecoderStepReset; + } + break; + case RogerDecoderStepCheckDuration: + if(!level) { + // Bit 1 is long and short timing = 1000us HIGH (te_last) and 500us LOW + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_long) < + subghz_protocol_roger_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_roger_const.te_short) < + subghz_protocol_roger_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = RogerDecoderStepSaveDuration; + // Bit 0 is short and long timing = 500us HIGH (te_last) and 1000us LOW + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_short) < + subghz_protocol_roger_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_roger_const.te_long) < + subghz_protocol_roger_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = RogerDecoderStepSaveDuration; + } else if( + // End of the key + DURATION_DIFF(duration, subghz_protocol_roger_const.te_short * 19) < + subghz_protocol_roger_const.te_delta * 3) { + //Found next GAP and add bit 1 or 0 + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_long) < + subghz_protocol_roger_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_short) < + subghz_protocol_roger_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } + // If got full 28 bits key reading is finished + if(instance->decoder.decode_count_bit == + subghz_protocol_roger_const.min_count_bit_for_found) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = RogerDecoderStepReset; + } else { + instance->decoder.parser_step = RogerDecoderStepReset; + } + } else { + instance->decoder.parser_step = RogerDecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_roger_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderRoger* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus subghz_protocol_decoder_roger_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderRoger* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + subghz_protocol_decoder_roger_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderRoger* instance = context; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, subghz_protocol_roger_const.min_count_bit_for_found); +} + +void subghz_protocol_decoder_roger_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderRoger* instance = context; + + subghz_protocol_roger_check_remote_controller(&instance->generic); + + furi_string_cat_printf( + output, + "%s %db\r\n" + "Key: 0x%07lX\r\n" + "Serial: 0x%04lX\r\n" + "CRC: 0x%02lX\r\n" + "Btn: %01X", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0xFFFFFFF), + instance->generic.serial, + (uint32_t)(instance->generic.data & 0xFF), + instance->generic.btn); +} diff --git a/lib/subghz/protocols/roger.h b/lib/subghz/protocols/roger.h new file mode 100644 index 000000000..c279164f9 --- /dev/null +++ b/lib/subghz/protocols/roger.h @@ -0,0 +1,109 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_ROGER_NAME "Roger" + +typedef struct SubGhzProtocolDecoderRoger SubGhzProtocolDecoderRoger; +typedef struct SubGhzProtocolEncoderRoger SubGhzProtocolEncoderRoger; + +extern const SubGhzProtocolDecoder subghz_protocol_roger_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_roger_encoder; +extern const SubGhzProtocol subghz_protocol_roger; + +/** + * Allocate SubGhzProtocolEncoderRoger. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderRoger* pointer to a SubGhzProtocolEncoderRoger instance + */ +void* subghz_protocol_encoder_roger_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderRoger. + * @param context Pointer to a SubGhzProtocolEncoderRoger instance + */ +void subghz_protocol_encoder_roger_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderRoger instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_encoder_roger_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderRoger instance + */ +void subghz_protocol_encoder_roger_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderRoger instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_roger_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderRoger. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderRoger* pointer to a SubGhzProtocolDecoderRoger instance + */ +void* subghz_protocol_decoder_roger_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderRoger. + * @param context Pointer to a SubGhzProtocolDecoderRoger instance + */ +void subghz_protocol_decoder_roger_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderRoger. + * @param context Pointer to a SubGhzProtocolDecoderRoger instance + */ +void subghz_protocol_decoder_roger_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderRoger instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_roger_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderRoger instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_roger_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderRoger. + * @param context Pointer to a SubGhzProtocolDecoderRoger instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus subghz_protocol_decoder_roger_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderRoger. + * @param context Pointer to a SubGhzProtocolDecoderRoger instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_decoder_roger_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderRoger instance + * @param output Resulting text + */ +void subghz_protocol_decoder_roger_get_string(void* context, FuriString* output); From bddebff1341592c18211d1c72563970e66ea80f3 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 9 Jul 2025 08:03:03 +0300 Subject: [PATCH 72/75] upd changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index feb903787..bcc118151 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ * SubGHz: **Roger (static 28 bit) with add manually support** (by @xMasterX & @mishamyte) * SubGHz: **V2 Phoenix full support** (button switch, add manually, counter decrypt/encrypt) (by @xMasterX & @RocketGod-git, original code by @Skorpionm) * SubGHz: **Keeloq: Add support for - Motorline (with add manually support), Rosh, Pecinin, Rossi, Merlin, Steelmate** (by @xMasterX & @RocketGod-git) +* SubGHz: Nero Radio static parse and display more data * SubGHz: Reduce less popular freqs in default hopper preset, **make it faster** * SubGHz: **Marantec protocol implement crc verification display and add manually support** (by @xMasterX & @li0ard, original code by @Skorpionm) * SubGHz: **Keeloq: Comunello - add manually support** From 269cbd66e1b683cf4fb879bf55be41e0386d5e39 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 9 Jul 2025 08:14:31 +0300 Subject: [PATCH 73/75] A little better naming for display in v2phox --- lib/subghz/protocols/phoenix_v2.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/subghz/protocols/phoenix_v2.c b/lib/subghz/protocols/phoenix_v2.c index 0229aeaeb..a6a8a7108 100644 --- a/lib/subghz/protocols/phoenix_v2.c +++ b/lib/subghz/protocols/phoenix_v2.c @@ -583,12 +583,11 @@ void subghz_protocol_decoder_phoenix_v2_get_string(void* context, FuriString* ou subghz_protocol_phoenix_v2_check_remote_controller(&instance->generic); furi_string_cat_printf( output, - "%s %dbit\r\n" + "V2 Phoenix %dbit\r\n" "Key:%05lX%08lX\r\n" "Sn:0x%07lX \r\n" "Cnt: 0x%04lX\r\n" "Btn: %X\r\n", - instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)(instance->generic.data >> 32) & 0xFFFFFFFF, (uint32_t)(instance->generic.data & 0xFFFFFFFF), From 3b29bd65088046bca065f39a945f8b5ab94655a3 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 12 Jul 2025 03:57:14 +0300 Subject: [PATCH 74/75] Roger decoder allow bigger gap and extend buttons functionality --- lib/subghz/protocols/roger.c | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/lib/subghz/protocols/roger.c b/lib/subghz/protocols/roger.c index d14547876..7b57cbab4 100644 --- a/lib/subghz/protocols/roger.c +++ b/lib/subghz/protocols/roger.c @@ -178,10 +178,17 @@ static void subghz_protocol_encoder_roger_get_upload(SubGhzProtocolEncoderRoger* // This will override the btn variable if a custom button is set btn = subghz_protocol_roger_get_btn_code(); - // If CRC is not == button - transmit as is, no custom button allowed + // If End is not == button - transmit as is, no custom button allowed + // For "End" values 23 and 20 - transmit correct ending used for their buttons if((instance->generic.data & 0xFF) == instance->generic.btn) { instance->generic.data = (uint64_t)instance->generic.serial << 12 | ((uint64_t)btn << 8) | btn; + } else if(((instance->generic.data & 0xFF) == 0x23) && btn == 0x1) { + instance->generic.data = (uint64_t)instance->generic.serial << 12 | ((uint64_t)btn << 8) | + 0x20; + } else if(((instance->generic.data & 0xFF) == 0x20) && btn == 0x2) { + instance->generic.data = (uint64_t)instance->generic.serial << 12 | ((uint64_t)btn << 8) | + 0x23; } // Send key and GAP @@ -226,14 +233,14 @@ static void subghz_protocol_roger_check_remote_controller(SubGhzBlockGeneric* in // 2025.07 - @xMasterX (MMX) // Key samples - // 0010001111111001 0001 00100000 // S/N: 0x23F9 Btn: 0x1 CRC: 0x20 - // 0010001111111001 0010 00100011 // S/N: 0x23F9 Btn: 0x2 CRC: 0x23 - // 0101011001010110 0001 00000001 // S/N: 0x5656 Btn: 0x1 CRC: 0x01 - // 0101011001010110 0010 00000010 // S/N: 0x5656 Btn: 0x2 CRC: 0x02 - // 0000110111111110 0001 00000001 // S/N: 0x0DFE Btn: 0x1 CRC: 0x01 - // 0000110111111110 0100 00000100 // S/N: 0x0DFE Btn: 0x4 CRC: 0x04 - // 0000110111111110 0010 00000010 // S/N: 0x0DFE Btn: 0x2 CRC: 0x02 - // 0000110111111110 1000 00001000 // S/N: 0x0DFE Btn: 0x8 CRC: 0x08 + // 0010001111111001 0001 00100000 // S/N: 0x23F9 Btn: 0x1 End: 0x20 + // 0010001111111001 0010 00100011 // S/N: 0x23F9 Btn: 0x2 End: 0x23 + // 0101011001010110 0001 00000001 // S/N: 0x5656 Btn: 0x1 End: 0x01 + // 0101011001010110 0010 00000010 // S/N: 0x5656 Btn: 0x2 End: 0x02 + // 0000110111111110 0001 00000001 // S/N: 0x0DFE Btn: 0x1 End: 0x01 + // 0000110111111110 0100 00000100 // S/N: 0x0DFE Btn: 0x4 End: 0x04 + // 0000110111111110 0010 00000010 // S/N: 0x0DFE Btn: 0x2 End: 0x02 + // 0000110111111110 1000 00001000 // S/N: 0x0DFE Btn: 0x8 End: 0x08 instance->serial = instance->data >> 12; instance->btn = (instance->data >> 8) & 0xF; @@ -330,7 +337,7 @@ void subghz_protocol_decoder_roger_feed(void* context, bool level, volatile uint switch(instance->decoder.parser_step) { case RogerDecoderStepReset: if((!level) && (DURATION_DIFF(duration, subghz_protocol_roger_const.te_short * 19) < - subghz_protocol_roger_const.te_delta * 3)) { + subghz_protocol_roger_const.te_delta * 5)) { //Found GAP instance->decoder.decode_data = 0; instance->decoder.decode_count_bit = 0; @@ -365,7 +372,7 @@ void subghz_protocol_decoder_roger_feed(void* context, bool level, volatile uint } else if( // End of the key DURATION_DIFF(duration, subghz_protocol_roger_const.te_short * 19) < - subghz_protocol_roger_const.te_delta * 3) { + subghz_protocol_roger_const.te_delta * 5) { //Found next GAP and add bit 1 or 0 if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_long) < subghz_protocol_roger_const.te_delta)) { @@ -431,7 +438,7 @@ void subghz_protocol_decoder_roger_get_string(void* context, FuriString* output) "%s %db\r\n" "Key: 0x%07lX\r\n" "Serial: 0x%04lX\r\n" - "CRC: 0x%02lX\r\n" + "End: 0x%02lX\r\n" "Btn: %01X", instance->generic.protocol_name, instance->generic.data_count_bit, From ac6621cdcbdbfce9f90c64b907c8ef943e54bd41 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 16 Jul 2025 02:38:19 +0300 Subject: [PATCH 75/75] upd changelog --- CHANGELOG.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bcc118151..e33b7843f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,17 +3,17 @@ * SubGHz: **Roger (static 28 bit) with add manually support** (by @xMasterX & @mishamyte) * SubGHz: **V2 Phoenix full support** (button switch, add manually, counter decrypt/encrypt) (by @xMasterX & @RocketGod-git, original code by @Skorpionm) * SubGHz: **Keeloq: Add support for - Motorline (with add manually support), Rosh, Pecinin, Rossi, Merlin, Steelmate** (by @xMasterX & @RocketGod-git) -* SubGHz: Nero Radio static parse and display more data +* SubGHz: **Nero Radio static parse** and display more data * SubGHz: Reduce less popular freqs in default hopper preset, **make it faster** -* SubGHz: **Marantec protocol implement crc verification display and add manually support** (by @xMasterX & @li0ard, original code by @Skorpionm) +* SubGHz: **Marantec protocol implement CRC verification display and Add manually support** (by @xMasterX & @li0ard, original code by @Skorpionm) * SubGHz: **Keeloq: Comunello - add manually support** * iButton: **TM01x Dallas write support** (PR #899 | by @Leptopt1los) * SubGHz: Rename and **extend Alarms, Sensors, Cars ignore options** (Alarms: Hollarm, GangQi | Cars: Kia, Starline, ScherKhan | Sensors: Magellan, Honeywell, Honeywell WDB (doorbells), Legrand (doorbells), Feron (RGB lights)) -* SubGHz: V2 Phoenix show counter value -* SubGHz: **Add keeloq ironlogic (aka il100) smart clone cloners support** (thanks to Vitaly for RAWs) +* SubGHz: V2 Phoenix show counter value (upd: see above, now decrypted) +* SubGHz: **Add Keeloq IronLogic (aka IL100) smart clone remote copiers support** (thanks to Vitaly for RAWs) * SubGHz: **Fix CAME 24bit decoder** * SubGHz: Add 462.750 MHz & 868.46 MHz to default subghz freqs list -* SubGHz: **Tune holtek ht12x to decode holtek only** and not conflict with came 12bit +* SubGHz: **Tune Holtek HT12x to decode Holtek only** and not conflict with came 12bit * SubGHz: Fix Rename scene bug, that was replacing file name with random name when Rename is opened then closed then opened again * Display: Backlight option "always on" and RGB bug removed (PR #900 | by @Dmitry422) * NFC: Ultralight C - Attempt of authentication with default key (PR #898 | by @mishamyte)